E57 Reader 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.
-
Using IntensityLimits
Since the intensity unit is unspecified it is necessary to normalize it into a known range before converting it into other usable forms.
// SimpleAPI
double intOffset = header.intensityLimits.intensityMinimum;
double intRange = header.intensityLimits.intensityMaximum - intOffset;
if(intRange <= 0.) intRange = 1.;
// FoundationAPI
double intensityMinimum = 0.;
double intensityMaximum = 1.;
if(scan.isDefined("intensityLimits"))
{
StructureNode intbox(scan.get("intensityLimits"));
if( intbox.get("intensityMaximum").type() == E57_SCALED_INTEGER ) {
intensityMaximum = (double) ScaledIntegerNode(intbox.get("intensityMaximum")).scaledValue();
intensityMinimum = (double) ScaledIntegerNode(intbox.get("intensityMinimum")).scaledValue();
}
else if( intbox.get("intensityMaximum").type() == E57_FLOAT ){
intensityMaximum = FloatNode(intbox.get("intensityMaximum")).value();
intensityMinimum = FloatNode(intbox.get("intensityMinimum")).value();
}
else if( intbox.get("intensityMaximum").type() == E57_INTEGER) {
intensityMaximum = (double) IntegerNode(intbox.get("intensityMaximum")).value();
intensityMinimum = (double) IntegerNode(intbox.get("intensityMinimum")).value();
}
}
double intOffset = intensityMinimum;
double intRange = intensityMaximum - intOffset;
if(intRange <= 0.) intRange = 1.;
...
//for each point this converts intensity data into 0. to 1. range
double intensity = (intensityData[i] - intOffset) / intRange;
-
Using ColorLimits
Since the color unit is unspecified it is necessary to convert it into a known range before using it.
// SimpleAPI
double redOffset = header.colorLimits.colorRedMinimum;
double redRange = header.colorLimits.colorRedMaximum - redOffset;
if(redRange <= 0.) redRange = 1.;
double greenOffset = header.colorLimits.colorGreenMinimum;
double greenRange = header.colorLimits.colorGreenMaximum - greenOffset;
if(greenRange <= 0.) greenRange = 1.;
double blueOffset = header.colorLimits.colorBlueMinimum;
double blueRange = header.colorLimits.colorBlueMaximum - blueOffset;
if(blueRange <= 0.) blueRange = 1.;
// FoundationAPI
double colorRedMinimum = 0.;
double colorRedMaximum = 0.;
double colorGreenMinimum = 0.;
double colorGreenMaximum = 0.;
double colorBlueMinimum = 0.;
double colorBlueMaximum = 0.;
if(scan.isDefined("colorLimits"))
{
StructureNode colorbox(scan.get("colorLimits"));
if( colorbox.get("colorRedMaximum").type() == E57_SCALED_INTEGER ) {
colorRedMaximum = (double) ScaledIntegerNode(colorbox.get("colorRedMaximum")).scaledValue();
colorRedMinimum = (double) ScaledIntegerNode(colorbox.get("colorRedMinimum")).scaledValue();
colorGreenMaximum = (double) ScaledIntegerNode(colorbox.get("colorGreenMaximum")).scaledValue();
colorGreenMinimum = (double) ScaledIntegerNode(colorbox.get("colorGreenMinimum")).scaledValue();
colorBlueMaximum = (double) ScaledIntegerNode(colorbox.get("colorBlueMaximum")).scaledValue();
colorBlueMinimum = (double) ScaledIntegerNode(colorbox.get("colorBlueMinimum")).scaledValue();
}
else if( colorbox.get("colorRedMaximum").type() == E57_FLOAT ){
colorRedMaximum = FloatNode(colorbox.get("colorRedMaximum")).value();
colorRedMinimum = FloatNode(colorbox.get("colorRedMinimum")).value();
colorGreenMaximum = FloatNode(colorbox.get("colorGreenMaximum")).value();
colorGreenMinimum = FloatNode(colorbox.get("colorGreenMinimum")).value();
colorBlueMaximum = FloatNode(colorbox.get("colorBlueMaximum")).value();
colorBlueMinimum = FloatNode(colorbox.get("colorBlueMinimum")).value();
}
else if( colorbox.get("colorRedMaximum").type() == E57_INTEGER) {
colorRedMaximum = (double) IntegerNode(colorbox.get("colorRedMaximum")).value();
colorRedMinimum = (double) IntegerNode(colorbox.get("colorRedMinimum")).value();
colorGreenMaximum = (double) IntegerNode(colorbox.get("colorGreenMaximum")).value();
colorGreenMinimum = (double) IntegerNode(colorbox.get("colorGreenMinimum")).value();
colorBlueMaximum = (double) IntegerNode(colorbox.get("colorBlueMaximum")).value();
colorBlueMinimum = (double) IntegerNode(colorbox.get("colorBlueMinimum")).value();
}
}
double redOffset = colorRedMinimum;
double redRange = colorRedMaximum - redOffset;
if(redRange <= 0.) redRange = 1.;
double greenOffset = colorGreenMinimum;
double greenRange = colorGreenMaximum - greenOffset;
if(greenRange <= 0.) greenRange = 1.;
double blueOffset = colorBlueMinimum;
double blueRange = colorBlueMaximum - blueOffset;
if(blueRange <= 0.) blueRange = 1.;
...
//for each point this converts color data into 0 to 255 range
UCHAR red = (UCHAR) (((redData[i] - redOffset) * 255) / redRange);
UCHAR green = (UCHAR) (((greenData[i] - greenOffset) * 255) / greenRange);
UCHAR blue = (UCHAR) (((blueData[i] - blueOffset) * 255) / blueRange);
-
Using IndexBounds for Structured Size
The structure point cloud size can be calculated using the following:
// SimpleAPI
int nSizeRows = header.indexBounds.rowMaximum - header.indexBounds.rowMinimum + 1;
int nSizeColumns = header.indexBounds.columnMaximum - header.indexBounds.columnMinimum + 1;
int nSizeReturns = header.indexBounds.returnMaximum - header.indexBounds.returnMinimum + 1;
// FoundationAPI
int rowMaximum = 0.;
int rowMinimum = 0.;
int columnMaximum = 0.;
int columnMinimum = 0.;
int returnMaximum = 0.;
int returnMinimum = 0.;
if(scan.isDefined("indexBounds"))
{
StructureNode ibox(scan.get("indexBounds"));
if(ibox.isDefined("rowMaximum"))
{
rowMinimum = IntegerNode(ibox.get("rowMinimum")).value();
rowMaximum = IntegerNode(ibox.get("rowMaximum")).value();
}
if(ibox.isDefined("columnMaximum"))
{
columnMinimum = IntegerNode(ibox.get("columnMinimum")).value();
columnMaximum = IntegerNode(ibox.get("columnMaximum")).value();
}
if(ibox.isDefined("returnMaximum"))
{
returnMinimum = IntegerNode(ibox.get("returnMinimum")).value();
returnMaximum = IntegerNode(ibox.get("returnMaximum")).value();
}
}
int nSizeRows = rowMaximum - rowMinimum + 1;
int nSizeColumns = columnMaximum - columnMinimum + 1;
int nSizeReturns = returnMaximum - returnMinimum + 1;
-
Converting Quaternion to Matrix
Creating a rotation matrix from a quaternion:
// SimpleAPI
double w = header.pose.rotation.w;
double x = header.pose.rotation.x;
double y = header.pose.rotation.y;
double z = header.pose.rotation.z;
// FoundationAPI
double w,x,y,z;
if(scan.isDefined("pose"))
{
StructureNode pose(scan.get("pose"));
if(pose.isDefined("rotation"))
{
StructureNode rotation(pose.get("rotation"));
w = FloatNode(rotation.get("w")).value();
x = FloatNode(rotation.get("x")).value();
y = FloatNode(rotation.get("y")).value();
z = FloatNode(rotation.get("z")).value();
}
}
// make rotation matrix
double mat[3][3];
mat[0][0] = 1. - 2.*y*y - 2.*z*z;
mat[0][1] = 2.*x*y - 2.*z*w;
mat[0][2] = 2.*x*z + 2.*y*w;
mat[1][0] = 2.*x*y + 2.*z*w;
mat[1][1] = 1. - 2.*x*x - 2.*z*z;
mat[1][2] = 2.*y*z - 2.*x*w;
mat[2][0] = 2.*x*z - 2.*y*w;
mat[2][1] = 2.*y*z + 2.*x*w;
mat[2][2] = 1. - 2.*x*x - 2.*y*y;
-
Reading Guids
If the E57 writer used a windows GUID then this code will recover the GUID structure from a string.
#if defined(_MSC_VER) // Only for windows
GUID guid; //--- Here is the GUID
// FoundationAPI
ustring guid = StringNode(root_.get("guid")).value();
CString strGuid = (_bstr_t) guid; //converts char to wchar
// SimpleAPI
CString strGuid = (_bstr_t) header.guid.c_str();
//add {} if missing
CString winGuid = strGuid;
if( strGuid[0] != _T('{'))
{
winGuid = _T("{");
winGuid += strGuid;
winGuid += _T("}");
}
//looking for a string like "{6A91E935-5559-477b-BE0C-7CE4E0BDFB7C}"
if( winGuid.GetLength() == 38 &&
winGuid[0] == _T('{') &&
winGuid[9] == _T('-') &&
winGuid[14] == _T('-') &&
winGuid[19] == _T('-') &&
winGuid[24] == _T('-') &&
winGuid[37] == _T('}'))
{
//convert to GUID
HRESULT hr = IIDFromString((LPOLESTR) winGuid,(LPIID) &guid);
}
else
{
... //guid is not a windows guid
}
#else //Non-windows
# include "boost/uuid/uuid.hpp"
# include "boost/uuid/uuid_generators.hpp"
# include "boost/uuid/uuid_io.hpp"
boost::uuids::uuid Uuid; //--- Here is the UUID
// FoundationAPI
string strUuid = StringNode(root_.get("guid")).value();
// SimpleAPI
string strUuid = header.guid.c_str();
//remove {} if present
if(strUuid[0] == '{')
strUuid.erase(1,0);
//looking for a string like "6A91E935-5559-477b-BE0C-7CE4E0BDFB7C"
if( strUuid.length() >= 36 &&
strUuid[8] == '-' &&
strUuid[13] == '-' &&
strUuid[18] == '-' &&
strUuid[23] == '-')
{
//convert to uuid
std::stringstream ss;
ss << strUuid;
ss >> Uuid;
}
else
{
... //uuid is not a windows guid
}
#endif
-
Reading GPS Date Time
Use this code to read the GPS Date Time fields.
// SimpleAPI
#if defined(_MSC_VER) // Only for windows
SYSTEMTIME fileDateTime;
header.acquisitionStart.dateTimeValue.GetSystemTime(fileDateTime);
#else
int year, month, day, hour, minute;
float seconds;
header.acquisitionStart.dateTimeValue.GetUTCDateTime(year, month, day, hour, minute, seconds);
#endif
// FoundationAPI
double dateTimeValue = 0.;
int isAtomicClockReferenced = 0;
if(scan.isDefined("acquisitionStart"))
{
StructureNode acquisitionStart(scan.get("acquisitionStart"));
dateTimeValue = FloatNode(acquisitionStart.get("dateTimeValue")).value();
isAtomicClockReferenced = (int32_t) IntegerNode(acquisitionStart.get("isAtomicClockReferenced")).value();
}
#ifdef _C_TIMECONV_H_
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
gps_week = ((int)floor(dateTimeValue))/604800;
gps_tow = dateTimeValue - gps_week*604800.;
bool result = TIMECONV_GetUTCTimeFromGPSTime( gps_week, gps_tow,
&utc_year, &utc_month, &utc_day, &utc_hour, &utc_minute, &utc_seconds);
#endif
-
Reading Scaled Integers
Use this code to recover the scaled integer point data range. However, the point data has already been converted when you get the data.
// SimpleAPI
double pointRangeScaledInteger = header.pointFields.pointRangeScaledInteger;
double pointRangeMinimum = header.pointFields.pointRangeMinimum;
double pointRangeMaximum = header.pointFields.pointRangeMaximum;
// FoundationAPI
double pointRangeScaledInteger = 0.; //FloatNode
double pointRangeMinimum = 0.;
double pointRangeMaximum = 0.;
if( proto.isDefined("cartesianX"))
{
if( proto.get("cartesianX").type() == E57_SCALED_INTEGER )
{
double scale = ScaledIntegerNode(proto.get("cartesianX")).scale();
double offset = ScaledIntegerNode(proto.get("cartesianX")).offset();
int64_t minimum = ScaledIntegerNode(proto.get("cartesianX")).minimum();
int64_t maximum = ScaledIntegerNode(proto.get("cartesianX")).maximum();
pointRangeMinimum = (double) minimum * scale + offset;
pointRangeMaximum = (double) maximum * scale + offset;
pointRangeScaledInteger = scale;
}
else if( proto.get("cartesianX").type() == E57_FLOAT )
{
pointRangeMinimum = FloatNode(proto.get("cartesianX")).minimum();
pointRangeMaximum = FloatNode(proto.get("cartesianX")).maximum();
}
}
-
Reading Cartesian Bounds
Use this code to read the CartesianBounds data.
FoundationAPI
double MaxX,MinX,MaxY,MinY,MaxZ,MinZ;
if(scan.isDefined("cartesianBounds"))
{
StructureNode bbox(scan.get("cartesianBounds"));
if( bbox.get("xMinimum").type() == E57_SCALED_INTEGER ) {
MinX = (double) ScaledIntegerNode(bbox.get("xMinimum")).scaledValue();
MaxX = (double) ScaledIntegerNode(bbox.get("xMaximum")).scaledValue();
MinY = (double) ScaledIntegerNode(bbox.get("yMinimum")).scaledValue();
MaxY = (double) ScaledIntegerNode(bbox.get("yMaximum")).scaledValue();
MinZ = (double) ScaledIntegerNode(bbox.get("zMinimum")).scaledValue();
MaxZ = (double) ScaledIntegerNode(bbox.get("zMaximum")).scaledValue();
}
else if( bbox.get("xMinimum").type() == E57_INTEGER ) {
MinX = (double) IntegerNode(bbox.get("xMinimum")).value();
MaxX = (double) IntegerNode(bbox.get("xMaximum")).value();
MinY = (double) IntegerNode(bbox.get("yMinimum")).value();
MaxY = (double) IntegerNode(bbox.get("yMaximum")).value();
MinZ = (double) IntegerNode(bbox.get("zMinimum")).value();
MaxZ = (double) IntegerNode(bbox.get("zMaximum")).value();
}
else if( bbox.get("xMinimum").type() == E57_FLOAT ){
MinX = FloatNode(bbox.get("xMinimum")).value();
MaxX = FloatNode(bbox.get("xMaximum")).value();
MinY = FloatNode(bbox.get("yMinimum")).value();
MaxY = FloatNode(bbox.get("yMaximum")).value();
MinZ = FloatNode(bbox.get("zMinimum")).value();
MaxZ = FloatNode(bbox.get("zMaximum")).value();
}
}
SimpleAPI
double MinX = header.cartesianBounds.xMinimum;
double MaxX = header.cartesianBounds.xMaximum;
double MinY = header.cartesianBounds.yMinimum;
double MaxY = header.cartesianBounds.yMaximum;
double MinZ = header.cartesianBounds.zMinimum;
double MaxZ = header.cartesianBounds.zMaximum;
-
Reading Spherical Bounds
Use this code to read the SphericalBounds data.
FoundationAPI
double MaxRange,MinRange,AzimuthStart,AzimuthEnd,MaxElevation,MinElevation;
if(scan.isDefined("sphericalBounds"))
{
StructureNode sbox(scan.get("sphericalBounds"));
if( sbox.get("rangeMinimum").type() == E57_SCALED_INTEGER ) {
MinRange = (double) ScaledIntegerNode(sbox.get("rangeMinimum")).scaledValue();
MaxRange = (double) ScaledIntegerNode(sbox.get("rangeMaximum")).scaledValue();
}
else if( sbox.get("rangeMinimum").type() == E57_FLOAT ){
MinRange = FloatNode(sbox.get("rangeMinimum")).value();
MaxRange = FloatNode(sbox.get("rangeMaximum")).value();
}
if( sbox.get("elevationMinimum").type() == E57_SCALED_INTEGER ) {
MinElevation = (double) ScaledIntegerNode(sbox.get("elevationMinimum")).scaledValue();
MaxElevation = (double) ScaledIntegerNode(sbox.get("elevationMaximum")).scaledValue();
}
else if( sbox.get("elevationMinimum").type() == E57_FLOAT ){
MinElevation = FloatNode(sbox.get("elevationMinimum")).value();
MaxElevation = FloatNode(sbox.get("elevationMaximum")).value();
}
if( sbox.get("azimuthStart").type() == E57_SCALED_INTEGER ) {
AzimuthStart = (double) ScaledIntegerNode(sbox.get("azimuthStart")).scaledValue();
AzimuthEnd = (double) ScaledIntegerNode(sbox.get("azimuthEnd")).scaledValue();
}
else if( sbox.get("azimuthStart").type() == E57_FLOAT ){
AzimuthStart = FloatNode(sbox.get("azimuthStart")).value();
AzimuthEnd = FloatNode(sbox.get("azimuthEnd")).value();
}
}
SimpleAPI
double MinRange = header.sphericalBounds.rangeMinimum;
double MaxRange = header.sphericalBounds.rangeMaximum;
double MinElevation = header.sphericalBounds.elevationMinimum;
double MaxElevation = header.sphericalBounds.elevationMaximum;
double AzimuthStart = header.sphericalBounds.azimuthStart;
double AzimuthEnd = header.sphericalBounds.azimuthEnd;
-
Reading Return Index
The standard states that returnIndex is zero based. That is, 0 is the first return, 1 is the second, and so on. Shall be in the interval [0, returnCount - 1]. That means the indexBounds.returnMinimum will always be 0.
The returnCount is the total number of returns for the pulse that is corresponds to. Shall be in the interval of [0, indexBounds.returnMaximum + 1]. This means the a returnCount of 0 is an invalid point.
So for a scanner returning upto 3 return points, the returnIndex,returnCount would be 0,3 for the first return, 1,3 for the second return, and 2,3 for the last return. indexBounds.returnMinimum will be 0 and indexBounds.returnMaximum will be 2;
int nSize = 1024; //buffer size;
int8_t *pReturnIndex = NULL;
int8_t *pReturnCount = NULL;
// SimpleAPI
int returnMaximum = 0.;
int returnMinimum = 0.;
if(header.pointFields.returnIndexField)
{
pReturnIndex = new int8_t[nSize];
returnMinimum = (int) header.indexBounds.returnMinimum;
returnMaximum = (int) header.indexBounds.returnMaximum;
}
if(header.pointFields.returnCountField)
pReturnCount = new int8_t[nSize];
...
e57::CompressedVectorReader reader = eReader->SetUpData3DPointsData(
scanIndex, // data block index given by the NewData3D
nSize, // size of each element buffer.
pCartesianX, // pointer to a buffer with the X coordinate (in meters) of the point in Cartesian coordinates
pCartesianY, // pointer to a buffer with the Y coordinate (in meters) of the point in Cartesian coordinates
pCartesianZ, // pointer to a buffer with the Z coordinate (in meters) of the point in Cartesian coordinates
pXYZInvalid, // Value = 0 if the point is considered valid, 1 or 2 otherwise
pIntData, // pointer to a buffer with the Point response intensity. Unit is unspecified
pIntInvalid, // Value = 0 if the intensity is considered valid, 1 otherwise
pRedData, // pointer to a buffer with the Red color coefficient. Unit is unspecified
pGreenData, // pointer to a buffer with the Green color coefficient. Unit is unspecified
pBlueData, // pointer to a buffer with the Blue color coefficient. Unit is unspecified
pColorInvalid, // Value = 0 if the color is considered valid, 1 otherwise
pRangeData, // pointer to a buffer with the range (in meters) of points in spherical coordinates. Shall be non-negative
pAzimuthData, // pointer to a buffer with the Azimuth angle (in radians) of point in spherical coordinates
pElevationData, // pointer to a buffer with the Elevation angle (in radians) of point in spherical coordinates
pRAEInvalid, // Value = 0 if the range is considered valid, 1 otherwise
pRowIndex, // pointer to a buffer with the row number of point (zero based).
// This is useful for data that is stored in a regular grid. Shall be in the interval (0, 2^63).
pColumnIndex, // pointer to a buffer with the column number of point (zero based).
// This is useful for data that is stored in a regular grid. Shall be in the interval (0, 2^63).
pReturnIndex, // pointer to a buffer with the number of this return (zero based).
// That is, 0 is the first return, 1 is the second, and so on. Shall be in the interval (0, returnCount).
// Only for multi-return sensors.
pReturnCount, // pointer to a buffer with the total number of returns for the pulse that this corresponds to.
// Shall be in the interval (0, 2^63). Only for multi-return sensors.
pTimeStamp, // pointer to a buffer with the time (in seconds) since the start time for the data,
// which is given by acquisitionStart in the parent Data3D Structure. Shall be non-negative
pTimeInvalid // Value = 0 if the timeStamp is considered valid, 1 otherwise
);
...
// FoundationAPI
StructureNode scan(data3D_.get(dataIndex));
int returnMaximum = 0.;
int returnMinimum = 0.;
if(scan.isDefined("indexBounds"))
{
StructureNode ibox(scan.get("indexBounds"));
...
if(ibox.isDefined("returnMaximum"))
{
returnMinimum = IntegerNode(ibox.get("returnMinimum")).value();
returnMaximum = IntegerNode(ibox.get("returnMaximum")).value();
}
}
CompressedVectorNode points(scan.get("points"));
StructureNode prototype(points.prototype());
...
int64_t protoCount = prototype.childCount();
int64_t protoIndex;
vector destBuffers;
for( protoIndex = 0; protoIndex < protoCount; protoIndex++)
{
ustring name = prototype.get(protoIndex).elementName();
...
if((name.compare("returnIndex") == 0) && prototype.isDefined("returnIndex"))
{
pReturnIndex = new int8_t[count];
destBuffers.push_back(SourceDestBuffer(imf_, "returnIndex", pReturnIndex, (unsigned) count, true, false));
}
else if((name.compare("returnCount") == 0) && prototype.isDefined("returnCount"))
{
pReturnCount = new int8_t[count];
destBuffers.push_back(SourceDestBuffer(imf_, "returnCount", pReturnCount, (unsigned) count, true, false));
}
...
}
CompressedVectorReader reader = points.reader(destBuffers);
...
// Both
unsigned size = 0;
while(size = reader.read())
{
for(long i = 0; i < size; i++)
{
Point P(pCartesianX[i],pCartesianY[i],pCartesianZ[i]);
if(pReturnIndex)
{
int ret = (pReturnIndex[i] - returnMinimum);
//cannot deal with more than one return per point
// use only the first return
if(ret > 0) continue; //discard other returns
// or use only the last return
if(ret != (pReturnCount[i] - 1)) continue; //discard all others
... //use this point
}
...
}
}
This site is © Copyright 2010 E57.04 3D Imaging System File Format Committee, All Rights Reserved
|