When the standard defined the IndexBounds structure the standard states that the bounds are defined to be tight to the data given. 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 need to know the index limits for structured point clouds. In order to know all the invalid points that occurred outside the indexBounds mim/max bounds, we need to know what the index limits were when it was scanned.
For example, given a PTX (size 100,100) file of a scan where rows 92 to 100 were all invalid, the IndexBounds.rowMaximum would be set to 91 because that was the maximum row that had valid data. Converting this data back to PTX (size 91,100) would NOT reproduce the identical file. It would be missing the invalid points on rows 92 to 100.
Work Around
The best way to solve this problem is to set the IndexBounds to the grid size of the structured point cloud like this:
// 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);
Solution
The real solution is to add an SP1 extension that defines a new IndexLimits structure with the grid size of the structured point cloud. Then the IndexBounds structure can be the bounds that exists in the real data and IndexLimits can be the grid size that was scanned.
// FoundationAPI
StructureNode indexLimits = StructureNode(imf_);
indexLimits.set("sp1:rowMinimum", IntegerNode(imf_, 0 ));
indexLimits.set("sp1:rowMaximum", IntegerNode(imf_, nSizeRows - 1));
indexLimits.set("sp1:columnMinimum", IntegerNode(imf_, 0 ));
indexLimits.set("sp1:columnMaximum", IntegerNode(imf_, nSizeColumns - 1));
indexLimits.set("sp1:returnMinimum", IntegerNode(imf_, 0 ));
indexLimits.set("sp1:returnMaximum", IntegerNode(imf_, nSizeReturns - 1));
scan.set("sp1:indexLimits", indexLimits);
Then the reader would have to look at both structures in order to get the point cloud size.
// FoundationAPI
double rowMaximum = 0.;
double rowMinimum = 0.;
double columnMaximum = 0.;
double columnMinimum = 0.;
double returnMaximum = 0.;
double returnMinimum = 0.;
if(scan.isDefined("sp1:indexLimits"))
{
StructureNode xbox(scan.get("sp1:indexLimitss"));
if(xbox.isDefined("sp1:rowMaximum"))
{
rowMinimum = IntegerNode(xbox.get("sp1:rowMinimum")).value();
rowMaximum = IntegerNode(xbox.get("sp1:rowMaximum")).value();
}
if(xbox.isDefined("sp1:columnMaximum"))
{
columnMinimum = IntegerNode(xbox.get("sp1:columnMinimum")).value();
columnMaximum = IntegerNode(xbox.get("sp1:columnMaximum")).value();
}
if(xbox.isDefined("sp1:returnMaximum"))
{
returnMinimum = IntegerNode(xbox.get("sp1:returnMinimum")).value();
returnMaximum = IntegerNode(xbox.get("sp1:returnMaximum")).value();
}
}
else 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();
}
}
long nSizeRows = rowMaximum - rowMinimum + 1;
long nSizeColumns = columnMaximum - columnMinimum + 1;
long nSizeReturns = returnMaximum - returnMinimum + 1;
The standard only allows double types to be used as the cartesian and spherical bounds coordinates. However, point data can be any type including scaled integer. This mismatch will cause round off errors when comparing the bounds with the point data.
Work Around
The best way to solve this problem is to pass the bounds through the same calculations as a scaled integer if the point data is a scaled integer.
// FoundationAPI
//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;
//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);
Solution
The real solution is to allow the bounds to be same type as the point data. The SP1 extension will document this. The reader will need to check the type of the bounds and convert them properly.
// 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();
}
}
The standard defined an Image2D::associatedData3DGuid for the Data3D object that was being acquired when the Image2D picture was taken. This allows for a 1 to 1 association between the scan and an image and a 1 to many association between a single scan and many images. However, the many scans with a single image case is not addressed.
Work Around
No work around is available.
Solution
The real solution is to add an SP1 extension that defines a new sp1:associatedPoseGuid for both the images2D and Data3D structures where the scan and image can be grouped together into a pose position where they were taken.