» Home

  » News

  » E57 User Group

  » Downloads

  » Test Data

  » Bugs

  » Documentation

  » Feature Categories

  » Extensions

  » Example

  » Supporting Partners

  » Supporting Products

  » Consultants

  » Licenses

E57 Writer Best Practices

This is a list of the best practices on interpreting the E2807 standard and using the libE57 library.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Click each item to expand its details.

  • IndexBounds and Structured Point Clouds
  • The standard states that the bounds are defined to be tight. In table 28, the standard defines rowMaximum to be "The maximum rowIndex value of any point represented by this IndexBounds object." The problem with this is that it doesn't meet the real world needs for structured point clouds.

    One of the best ways to judge a standard is its ability to round-trip the data completely. Given a PTX (size 100,100) file of a scan where rows 92 to 100 were all invalid, converting this to E57 and dropping out all of the invalid points and converting it back to PTX (size 91,100) would NOT reproduce the identical file. It would be short rows 92 to 100. The current standard fails the test.

    The best way to solve this problem is to set the IndexBounds to the grid size of the structured point cloud like this:

    // SimpleAPI

    e57::Data3D header; ... header.indexBounds.rowMaximum = nSizeRows - 1; header.indexBounds.rowMinimum = 0; header.indexBounds.columnMaximum = nSizeColumns - 1; header.indexBounds.columnMinimum = 0; header.indexBounds.returnMaximum = nSizeReturns - 1; header.indexBounds.returnMinimum = 0; ...

    // FoundationAPI

    StructureNode indexBounds = StructureNode(imf_); indexBounds.set("rowMinimum", IntegerNode(imf_, 0 )); indexBounds.set("rowMaximum", IntegerNode(imf_, nSizeRows - 1)); indexBounds.set("columnMinimum", IntegerNode(imf_, 0 )); indexBounds.set("columnMaximum", IntegerNode(imf_, nSizeColumns - 1)); indexBounds.set("returnMinimum", IntegerNode(imf_, 0 )); indexBounds.set("returnMaximum", IntegerNode(imf_, nSizeReturns - 1)); scan.set("indexBounds", indexBounds);
  • Converting Matrix to Quaternion
  • Creating a quaternion from a rotation matrix:

    	double mat[3][3];	//matrix
    	double w,x,y,z;		//quaternion
    ...	
    	double dTrace = mat[0][0] + mat[1][1] + mat[2][2] + 1.0;
    	if(dTrace > 0.000001)
    	{
    		double S = 2.0 * sqrt(dTrace);
    		x = (mat[2][1] - mat[1][2]) / S;
    		y = (mat[0][2] - mat[2][0]) / S;
    		z = (mat[1][0] - mat[0][1]) / S;
    		w = 0.25 * S;	
    	}
    	else if((mat[0][0] > mat[1][1]) && (mat[0][0] > mat[2][2]))
    	{
    		double S = sqrt(1.0 + mat[0][0] - mat[1][1] - mat[2][2]) * 2.0;
    		x = 0.25 * S;
    		y = (mat[1][0] + mat[0][1]) / S;
    		z = (mat[0][2] + mat[2][0]) / S;
    		w = (mat[2][1] - mat[1][2]) / S;
    	}
    	else if(mat[1][1] > mat[2][2])
    	{
    		double S = sqrt(1.0 + mat[1][1] - mat[0][0] - mat[2][2]) * 2.0;
    		x = (mat[1][0] + mat[0][1]) / S;
    		y = 0.25 * S;
    		z = (mat[2][1] + mat[1][2]) / S;
    		w = (mat[0][2] - mat[2][0]) / S;
    	}
    	else
    	{
    		double S = sqrt(1.0 + mat[2][2] - mat[0][0] - mat[1][1]) * 2.0;
    		x = (mat[0][2] + mat[2][0]) / S;
    		y = (mat[2][1] + mat[1][2]) / S;
    		z = 0.25 * S;
    		w = (mat[1][0] - mat[0][1]) / S;
    	}
    	
    // normalize the quaternion if the matrix is not a clean rigid body matrix or if it has scaler information.
    
    	double len = sqrt( w*w + x*x + y*y + z*z);
    	if(len != 0.)
    	{	
    		w /= len;
    		x /= len;
    		y /= len;
    		z /= len;
    	}
    	
    

    // SimpleAPI

    header.pose.rotation.w = w; header.pose.rotation.x = x; header.pose.rotation.y = y; header.pose.rotation.z = z;

    // FoundationAPI

    if( (w != 1.) || (x != 0.) || (y != 0.) || (z != 0.) ) { StructureNode pose = StructureNode(imf_); StructureNode rotation = StructureNode(imf_); rotation.set("w", FloatNode(imf_, w)); rotation.set("x", FloatNode(imf_, x)); rotation.set("y", FloatNode(imf_, y)); rotation.set("z", FloatNode(imf_, z)); pose.set("rotation", rotation); scan.set("pose", pose); }
  • Writing Guids
  • If you choose to use Window's GUID as the E57 guid, use the following:

    //create a string like "{6A91E935-5559-477b-BE0C-7CE4E0BDFB7C}"
    
    #if defined(_MSC_VER) 	// Only for windows
    //create guid
    	GUID		guid;
    	CoCreateGuid((GUID*)&guid);
    	
    //convert to string
    	OLECHAR wbuffer[64];
    	StringFromGUID2(guid,&wbuffer[0],64); 
    	
    //converts wchar to char
    	size_t	converted = 0;
    	char	strGuid[64];
    	wcstombs_s(&converted, strGuid, wbuffer, 64);
    	
    

    // SimpleAPI

    header.guid = (char*) strGuid;

    // FoundationAPI

    root_.set("guid", StringNode(imf_, (char*) strGuid)); #else // Non-windows # include "boost/uuid/uuid.hpp" # include "boost/uuid/uuid_generators.hpp" # include "boost/uuid/uuid_io.hpp" //create uuid boost::uuids::random_generator gen; boost::uuids::uuid Uuid = gen(); //convert to string std::stringstream ss; ss << Uuid; //add {} std::string strUuid = '{'; strUuid.append(ss.str()); strUuid.append(1, '}');

    // SimpleAPI

    header.guid = strUuid.c_str();

    // FoundationAPI

    root_.set("guid", StringNode(imf_, strUuid.c_str())); #endif
  • Writing GPS Date Time
  • Use this code to write the GPS Date Time fields.

    // SimpleAPI

    #if defined(_MSC_VER) // Only for windows // Init SYSTEMTIME with the current time SYSTEMTIME fileDateTime; header.acquisitionStart.SetSystemTime(fileDateTime); #else // Init these with a UTC time int year, month, day, hour, minute; float seconds; header.acquisitionStart.SetUTCDateTime(year, month, day, hour, minute, seconds); #endif

    // FoundationAPI

    #ifdef _C_TIMECONV_H_ // Init these with a UTC time unsigned short utc_year; //Universal Time Coordinated [year] unsigned char utc_month; //1-12 months unsigned char utc_day; //1-31 days unsigned char utc_hour; //hours unsigned char utc_minute; //minutes float utc_seconds;//seconds unsigned short gps_week; //GPS week (0-1024+) double gps_tow; //GPS time of week(0-604800.0) seconds bool result = TIMECONV_GetGPSTimeFromUTCTime( &utc_year, &utc_month, &utc_day, &utc_hour, &utc_minute, &utc_seconds, &gps_week, &gps_tow); double dateTimeValue = (gps_week * 604800.) + gps_tow; int isAtomicClockReferenced = 0; #endif StructureNode acquisitionStart = StructureNode(imf_); acquisitionStart.set("dateTimeValue", FloatNode(imf_, dateTimeValue)); acquisitionStart.set("isAtomicClockReferenced", IntegerNode(imf_, isAtomicClockReferenced)); scan.set("acquisitionStart", acquisitionStart);
  • Writing Scaled Integers
  • Use this code to setup scaled integer point data.

    //choose a scale factor for the data
    	#define DATA_SCALE_FACTOR	(.000001)		
    //minimum negative limit of the data
    	double minData;					
    //maximum positive limit of the data
    	double maxData;					
    
    

    // SimpleAPI

    // check for the case where abs(minData) > maxData double maxDist = abs(maxData) > abs(minData) ? abs(maxData) : abs(minData); long maxRange = NextHigherPowerOfTwo((maxData - minData)/DATA_SCALE_FACTOR) - 1; // Calculate the needed range header.pointFields.pointRangeMinimum = ((double) -(maxRange + 1)) * DATA_SCALE_FACTOR; header.pointFields.pointRangeMaximum = ((double) maxRange) * DATA_SCALE_FACTOR; header.pointFields.pointRangeScaledInteger = DATA_SCALE_FACTOR;

    // FoundationAPI

    double pointRangeScale = DATA_SCALE_FACTOR; double pointRangeOffset = 0.; // check for the case where abs(minData) > maxData double maxDist = abs(maxData) > abs(minData) ? abs(maxData) : abs(minData); long maxRange = NextHigherPowerOfTwo((maxData - minData)/pointRangeScale) - 1; // Calculate the needed range double pointRangeMinimum = ((double) -(maxRange + 1)) * pointRangeScale; double pointRangeMaximum = ((double) maxRange) * pointRangeScale; // make integer int64_t intRangeMinimum = (int64_t) floor((pointRangeMinimum - pointRangeOffset)/pointRangeScale +.5); int64_t intRangeMaximum = (int64_t) floor((pointRangeMaximum - pointRangeOffset)/pointRangeScale +.5); StructureNode proto = StructureNode(imf_); proto.set("cartesianX", ScaledIntegerNode(imf_, 0, intRangeMinimum, intRangeMaximum, pointRangeScale, pointRangeOffset)); proto.set("cartesianY", ScaledIntegerNode(imf_, 0, intRangeMinimum, intRangeMaximum, pointRangeScale, pointRangeOffset)); proto.set("cartesianZ", ScaledIntegerNode(imf_, 0, intRangeMinimum, intRangeMaximum, pointRangeScale, pointRangeOffset)); ... // other point data fields VectorNode codecs = VectorNode(imf_, true); CompressedVectorNode points = CompressedVectorNode(imf_, proto, codecs); scan.set("points", points);
  • Writing Cartesian Bounds
  • The standard only allows doubles to be used for cartesian bounds coordinates; however point data should use scaled integers to be efficient. This mismatch will cause round off errors that will be found by the validate57.exe tool. To fix this problem the bounds need to be passed through the same calculations as a scaled integer.

    //use the scale factor for the point data
    	#define DATA_SCALE_FACTOR	(.000001)
    //calculate the minimum/maximum from the point data.	
    	double MaxX,MinX,MaxY,MinY,MaxZ,MinZ;
    	
    

    // SimpleAPI

    //do this only if the point coordinate data is a scaled integer. header.cartesianBounds.xMaximum = ceil(MaxX / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.xMinimum = floor(MinX / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.yMaximum = ceil(MaxY / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.yMinimum = floor(MinY / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.zMaximum = ceil(MaxZ / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR; header.cartesianBounds.zMinimum = floor(MinZ / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR;

    // FoundationAPI

    //do this only if the point coordinate data is a scaled integer. StructureNode bbox = StructureNode(imf_); bbox.set("xMinimum", FloatNode(imf_, floor(MinX / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("xMaximum", FloatNode(imf_, ceil(MaxX / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("yMinimum", FloatNode(imf_, floor(MinY / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("yMaximum", FloatNode(imf_, ceil(MaxY / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("zMinimum", FloatNode(imf_, floor(MinZ / DATA_SCALE_FACTOR - 0.9999999) * DATA_SCALE_FACTOR)); bbox.set("zMaximum", FloatNode(imf_, ceil(MaxZ / DATA_SCALE_FACTOR + 0.9999999) * DATA_SCALE_FACTOR)); scan.set("cartesianBounds", bbox);
  • Writing Spherical Bounds
  • The standard only allows doubles to be used for spherical bounds coordinates; however range point data should use scaled integers to be efficient. This mismatch will cause round off errors that will be found by the validate57.exe tool. To fix this problem the bounds need to be passed through the same calculations as a scaled integer.

    //use the scale factor for the point data
    	#define DATA_SCALE_FACTOR	(.000001)
    //calculate the minimum/maximum from the point data.	
    	double MaxRange,MinRange,AzimuthStart,AzimuthEnd,MaxElevation,MinElevation;
    	
    

    // SimpleAPI

    //do this only if the point range coordinate data is a scaled integer. header.sphericalBounds.rangeMaximum = ceil( MaxRange / DATA_SCALE_FACTOR +0.9999999) * DATA_SCALE_FACTOR; header.sphericalBounds.rangeMinimum = floor( MinRange / DATA_SCALE_FACTOR -0.9999999) * DATA_SCALE_FACTOR; header.sphericalBounds.azimuthEnd = AzimuthEnd; header.sphericalBounds.azimuthStart = AzimuthStart; header.sphericalBounds.elevationMaximum = MaxElevation; header.sphericalBounds.elevationMinimum = MinElevation;

    // FoundationAPI

    //do this only if the point coordinate data is a scaled integer. StructureNode sbox = StructureNode(imf_); sbox.set("rangeMinimum", FloatNode(imf_, floor( MinRange / DATA_SCALE_FACTOR -0.9999999) * DATA_SCALE_FACTOR)); sbox.set("rangeMaximum", FloatNode(imf_, ceil( MaxRange / DATA_SCALE_FACTOR +0.9999999) * DATA_SCALE_FACTOR)); sbox.set("elevationMinimum", FloatNode(imf_, MinElevation)); sbox.set("elevationMaximum", FloatNode(imf_, MaxElevation)); sbox.set("azimuthStart", FloatNode(imf_, AzimuthStart)); sbox.set("azimuthEnd", FloatNode(imf_, AzimuthEnd)); scan.set("sphericalBounds", sbox);























This site is © Copyright 2010 E57.04 3D Imaging System File Format Committee, All Rights Reserved