From d5ca398ddc01d837548caf8ee956a66e39928100 Mon Sep 17 00:00:00 2001 From: tmaurer3 Date: Fri, 31 Aug 2018 17:00:08 -0700 Subject: [PATCH] add test sample --- src/Test_C_Api.cpp | 230 +++++++++++++++++++++++++++++ testData/SMALL_AUTZEN_LAS_All.bin | Bin 0 -> 2764 bytes testData/SMALL_AUTZEN_LAS_All.slpk | Bin 0 -> 9637 bytes 3 files changed, 230 insertions(+) create mode 100644 testData/SMALL_AUTZEN_LAS_All.bin create mode 100644 testData/SMALL_AUTZEN_LAS_All.slpk diff --git a/src/Test_C_Api.cpp b/src/Test_C_Api.cpp index ee8e76b..fca0fac 100644 --- a/src/Test_C_Api.cpp +++ b/src/Test_C_Api.cpp @@ -42,10 +42,240 @@ using namespace lepcc; int ReadBlobSize(lepcc_ContextHdl ctx, FILE* fp); +int HasError(ErrCode errCode, const string& fctName) +{ + if (errCode != ErrCode::Ok) + printf("Error in main(): %s failed. Error code = %d\n", fctName.c_str(), errCode); + return (int)errCode; +} + // -------------------------------------------------------------------------- ; int main(int argc, char* argv[]) { + { + // open a small test binary slpk blob which has some LEPCC blobs embedded + + string fnIn = "../testData/SMALL_AUTZEN_LAS_All.slpk"; + string fnGT = "../testData/SMALL_AUTZEN_LAS_All.bin"; // decoded ground truth stored as raw binary arrays + bool bWriteGT = false; // toggle between write or read the ground truth + + FILE* fp = 0; + fp = fopen(fnIn.c_str(), "rb"); + if (!fp) + { + printf("Error in main(): Cannot read from file %s.\n", fnIn.c_str()); + return 0; + } + + fseek(fp, 0, SEEK_END); + size_t len = ftell(fp); + rewind(fp); + + vector byteVec(len, 0); + fread(&byteVec[0], 1, len, fp); + fclose(fp); + + FILE* fpGT = 0; + fpGT = fopen(fnGT.c_str(), bWriteGT ? "wb" : "rb"); + if (!fpGT) + { + printf("Error in main(): Cannot open file %s.\n", fnGT.c_str()); + return 0; + } + + // search for LEPCC blobs + + vector magicStrings; + magicStrings.push_back("LEPCC "); + magicStrings.push_back("ClusterRGB"); + magicStrings.push_back("Intensity "); + + lepcc_ContextHdl ctx = lepcc_createContext(); + int nInfo = lepcc_getBlobInfoSize(); + ErrCode errCode; + + size_t magicLen = 10; + for (size_t pos = 0; pos < len - magicLen; pos++) + { + for (size_t blobType = 0; blobType < magicStrings.size(); blobType++) + { + if (0 == memcmp(&byteVec[pos], magicStrings[blobType].c_str(), magicLen)) + { + const Byte* ptr = &byteVec[pos]; + uint32 blobSize = 0; + lepcc_blobType bt; + errCode = (ErrCode)lepcc_getBlobInfo(ctx, ptr, nInfo, &bt, &blobSize); + if (HasError(errCode, "lepcc_getBlobInfo()")) + return 0; + + switch (blobType) + { + case 0: + { + uint32 nPts = 0; + errCode = (ErrCode)lepcc_getPointCount(ctx, ptr, len - pos, &nPts); + if (HasError(errCode, "lepcc_getPointCount()")) + return 0; + + vector ptVec(nPts); + errCode = (ErrCode)lepcc_decodeXYZ(ctx, &ptr, len - pos, &nPts, (double*)(&ptVec[0])); + if (HasError(errCode, "lepcc_decodeXYZ()")) + return 0; + + printf("decode xyz blob succeeded.\n"); + + if (bWriteGT) + { + fwrite(&nPts, 4, 1, fpGT); + fwrite(&ptVec[0], sizeof(Point3D), nPts, fpGT); + } + else + { + uint32 nPts2 = 0; + fread(&nPts2, 4, 1, fpGT); + if (nPts2 != nPts) + { + printf("Error in main(): mismatch in number of elements %d vs %d\n", nPts, nPts2); + return 0; + } + vector ptVec2(nPts); + fread(&ptVec2[0], sizeof(Point3D), nPts, fpGT); + + // compare + double dxMax(0), dyMax(0), dzMax(0); + for (int i = 0; i < (int)nPts; i++) + { + const Point3D* p = &ptVec[i]; + const Point3D* q = &ptVec2[i]; + + double dx = abs(q->x - p->x); + double dy = abs(q->y - p->y); + double dz = abs(q->z - p->z); + + dxMax = max(dx, dxMax); + dyMax = max(dy, dyMax); + dzMax = max(dz, dzMax); + } + + printf("number of points = %d, dxMax = %f, dyMax = %f, dzMax = %f\n", nPts, dxMax, dyMax, dzMax); + } + } + break; + + case 1: + { + uint32 nPts = 0; + errCode = (ErrCode)lepcc_getRGBCount(ctx, ptr, len - pos, &nPts); + if (HasError(errCode, "lepcc_getRGBCount()")) + return 0; + + vector rgbVec(nPts); + errCode = (ErrCode)lepcc_decodeRGB(ctx, &ptr, len - pos, &nPts, (Byte*)(&rgbVec[0])); + if (HasError(errCode, "lepcc_decodeRGB()")) + return 0; + + printf("decode RGB blob succeeded.\n"); + + if (bWriteGT) + { + fwrite(&nPts, 4, 1, fpGT); + fwrite(&rgbVec[0], sizeof(RGB_t), nPts, fpGT); + } + else + { + uint32 nPts2 = 0; + fread(&nPts2, 4, 1, fpGT); + if (nPts2 != nPts) + { + printf("Error in main(): mismatch in number of elements %d vs %d\n", nPts, nPts2); + return 0; + } + vector rgbVec2(nPts); + fread(&rgbVec2[0], sizeof(RGB_t), nPts, fpGT); + + // compare + double dxMax(0), dyMax(0), dzMax(0); + for (int i = 0; i < (int)nPts; i++) + { + const RGB_t* p = &rgbVec[i]; + const RGB_t* q = &rgbVec2[i]; + + double dx = abs(q->r - p->r); + double dy = abs(q->g - p->g); + double dz = abs(q->b - p->b); + + dxMax = max(dx, dxMax); + dyMax = max(dy, dyMax); + dzMax = max(dz, dzMax); + } + + printf("number of points = %d, dxMax = %f, dyMax = %f, dzMax = %f\n", nPts, dxMax, dyMax, dzMax); + } + } + break; + + case 2: + { + uint32 nPts = 0; + errCode = (ErrCode)lepcc_getIntensityCount(ctx, ptr, len - pos, &nPts); + if (HasError(errCode, "lepcc_getIntensityCount()")) + return 0; + + vector intensityVec(nPts); + errCode = (ErrCode)lepcc_decodeIntensity(ctx, &ptr, len - pos, &nPts, (unsigned short*)(&intensityVec[0])); + if (HasError(errCode, "lepcc_decodeIntensity()")) + return 0; + + printf("decode intensity blob succeeded.\n"); + + if (bWriteGT) + { + fwrite(&nPts, 4, 1, fpGT); + fwrite(&intensityVec[0], sizeof(unsigned short), nPts, fpGT); + } + else + { + uint32 nPts2 = 0; + fread(&nPts2, 4, 1, fpGT); + if (nPts2 != nPts) + { + printf("Error in main(): mismatch in number of elements %d vs %d\n", nPts, nPts2); + return 0; + } + vector intensityVec2(nPts); + fread(&intensityVec2[0], sizeof(unsigned short), nPts, fpGT); + + // compare + double dxMax(0); + for (int i = 0; i < (int)nPts; i++) + { + int intensity0 = intensityVec[i]; + int intensity1 = intensityVec2[i]; + double dx = abs(intensity1 - intensity0); + dxMax = max(dx, dxMax); + } + + printf("number of points = %d, dxMax = %f\n", nPts, dxMax); + } + } + break; + + default: + printf("Error in main(): Test for this LEPCC blob type not implemented.\n"); + } + + pos += blobSize - 1; + } + } + } + + fclose(fpGT); + return 0; + } + + // -------------- see below for examples on how to encode and decode -------- + double maxXErr = 9e-8, maxYErr = maxXErr; double maxZErr = 0.01; diff --git a/testData/SMALL_AUTZEN_LAS_All.bin b/testData/SMALL_AUTZEN_LAS_All.bin new file mode 100644 index 0000000000000000000000000000000000000000..71013f24800ad281c64bbdec89e24441fa6e4d6f GIT binary patch literal 2764 zcmX}u3p7>h76zAamV=-GeSYF&os1ier= z2p>8yN^yZQM`xFw>Y4&S7jQh`Fg%lA-;vQR;R*?c@V0ALOhTd` zJSBLAWeVISEJ5efGLG7ReRNj=F8@`{I~{%}&MNRpEl1~8P1}|P@A-VP`T|@rD1Tdw z4@cEKtd(!VjRMZg^C5MGJh)2IPP5%XjAohXtlR-_-1d0$5MuYPcB>eh*uBjTOa08orV+OV({~9IbH29iVW<}+2bCtExb7nKT>f#~4 zTKF$(iTDn@KKjDG(Z-B!eW22n0gqU-tD1M=diRQH|JP$d9`LZZQ`hgppZhNQ=Dv*4 z0`sPmQScWfr@4FZP)$dJ-@V0jLxGQ}16(g)lly(?`fiMtAC0zQ@FSs42YE02R&4EK z!f10rwI~SQ<80GV4gc0U+f$Y?x@mcO$8vb@p|&30g+9UcTG}XSPJ+j^-gS8>jsL`` zOIr3M4j$4UT*teR57uEVAtN>6ZF=h$*TS93E^}8>8BO`Fd9xBeXV`9lccIQ!jON(6 zeAnMCtH8yJm!Ro2P( zVeKso)lYDt{+5g`F7C-Jg8S`$_vdH0v>)h%?wqsmwP}g-zDVQ48U3m6wT}jTFd#YN z8(fp=r-cuHj5o0GO8s@qgFyab*;{F{%AqAN{GH;r7K=+ zO~-KfuHOd7$-@7s52JgGuT9?ox3Jx{n|Gm}0a)(}RadzH_iUK|VFp~d@9H^f*H%k!z`adJ zKCyu7s`sz_-pSExdt3}h!~g5-A3ql^++Qn$XpzH-$NXGMSbZhJ4ldMfB%`vWP4RsH zbcSlB+ry>z-5yoldOp60DD?3z>=*LU3wz3Y`Ta8W5!Z2Wp|AAB)L>Qb5IQLru{knSInSn+c}J#m&yI9!;|H5}bx)M``?f09(dMZtynd6lDs)wTLN;c}*0 z7Qe#F>H{(*9gJpdj&JAZzfw%cxSeomp4+^B3785eUHjI=!VfH!u{iyWqh{8TdSl?L zxr@u<;KDv07twhh)z=f@0}s?M@h;56UJ=cywAxqAuM?T3a1g#q_wit86-SdcKX;SB zA9hTuPlZe8zohHn&Moj}PxqwLaOvDzyP~P(Fx)lDzBB_aud|JY>R6sp~N+bNzXV z6I{M8Bls3vTA$?HnsX1~^$r8?Zo`FqPKv0{xe}|f@VZAbmnz`G9ME9Y$n3LrKK#Yk zyls`zbu<}G_BNl-&*4G+dja?1!dwjy(OQRool>|qnPpG~w@dBFDg2Y8y4;H=eEudC z$`N&N;dexpQ6>N7OXk428}`$lN_{e;O+M|T_S>*cTMt#H54K01An(I_0Z z4({SwruG6Z)Y+fW5s#Hr6X4s8G9B9Bo+-=gKg2RBcWg&SE?lSNOx7E?8b42NbM#TN zgAD&%@z!MCt4_G|cPFTMp(q<}`!-AeJ^bv@A~mhc92M^v&OZas|B-I|5x&&rrvv6? z9Ca>qSK15T-Z;GR3tY%26u)<6iCz=ozWtj&^uudbJ!??u=V&ON`3U()Mv_THhB%RU zGLI+{E22-PkSMZ%n2`xY1OKh@Dj@6ec^&>@5UYsyBoa$JNG91&3`jCLj$>rWRFX!< z6EBiaGVtA=C=)wkLX3z4F~nI8A(CKa6SyX6!C4K73O-FFkz_jFN03oiVKE}Qh%F*B r5TS?jn-DpYM^=)h_+CiFM2j3D5;7Mt<~ZIN`L^L4C3xK+BINo%?hu+A literal 0 HcmV?d00001 diff --git a/testData/SMALL_AUTZEN_LAS_All.slpk b/testData/SMALL_AUTZEN_LAS_All.slpk new file mode 100644 index 0000000000000000000000000000000000000000..045159290c777c9e45ce9b5dbf60abc9624491c1 GIT binary patch literal 9637 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