Summary
The XStream® HDVR® Curved Reformat creates a new dataset by applying a set of transformations to an existing dataset, extending functionality as described in the Curved MPR chapter. The Curved MPR feature renders an image by applying a transformation to a number of planar slices through the dataset. The slices are then aligned along a common axis.
The Curved MPR feature is used to straighten the visualization of a curved structure to build a view that can provide better diagnostic imaging and analysis. A Curved MPR image can be thought of as a single dataset slice. The Curved Reformat feature generates a new dataset by taking multiple Curved MPR slices and stacking them together to form a new dataset. This dataset can then be manipulated and rendered as any other dataset using the XStream® HDVR® SDK features.
Review the Curved MPR feature for a better understanding of how it relates to Curved Reformat.
Trace of a curved vessel |
Recomputed volume with Curved Reformat |
Curved Reformat
To create a new reformatted series along this path, multiple groups of planar slices through the dataset are specified, along with details about how to align the slices with a vector axis. Each group of planar slices, once realigned, forms a new dataset slice. Each of the newly reformatted dataset slices are then stacked together to form a new dataset using the IRenderEngineContext::GetCurvedReformat() and IRenderQueue::GetCurvedReformat() methods based on the following parameters:
IVolumeDataContext **pData |
The address of an IVolumeDataContext pointer that will receive the newly generated dataset. |
const h_uint32 numProjPlanesPerSlice |
The number of MATRIX44D entries that define the planar slices that compose each new dataset slice. |
const h_uint32 numSlices |
The number of slices that will make up the new dataset. |
const h_uint32 ppWidth |
Width of the newly generated dataset. |
const h_uint32 ppHeight |
Height of the newly generated dataset. |
const h_uint32 measurementSlice |
The index (0-based) of the slice to specify for accurate measurement. For example, when 3 is specified, the fourth image in the volume will have the property that pixels in the X and Y directions will be accurately represented in millimeters. |
const h_float32 zoom |
The number of pixels per voxel in the non-curved dimension. If this is -1, the pixels per voxel value is calculated based upon the length of the curve. |
MATRIX44D **arrProjPlanes |
A two dimensional array composed of MATRIX44D objects. The dimensions are arrProjPlanes[numSlices][numProjPlanesPerSlice]. These are the matrices that define each Curved MPR slice that is assembled into the Curved Reformat dataset volume. |
Describes which axis is non-linear (and which is linear) during the curve straightening process, as well as the interpolation type used to render the image. |
// Parameters for new dataset
int thickness = 50;
float spacing = 0.25f;
int height = 50;
int numSlices = (int)(thickness / spacing);
MATRIX44D **arr2ProjPlanes = new (MATRIX44D *)[numSlices];
RENDER_PARAMS rp;
BCOM_ZEROMEMORY(&rp);
rp.Transform.setIdentity();
VECTOR3D vecZ = rp.Transform.getZVector();
vecZ.normalize();
if(vecZ.dot(new VECTOR3D(0.0, 0.0, 1.0)) > 0.0) {
vecZ = vecZ.scale(-1.0);
}
// get the points ahead of time for the tangent calculation
VECTOR3D *points = new VECTOR3D[m_NumPoints];
for(int i = 0; i < m_NumPoints; i++) {
points[i] = m_points[i].vector3D;
}
for(int j = 0; j < numSlices; j++) {
// for each slice
arr2ProjPlanes[j] = new MATRIX44D[m_NumPoints];
// now calculate the points for this slice
for (int i = 0; i < m_NumPoints; i++) {
// for each point on the slice
C3DHelpers 3dHelpers;
VECTOR3D tangent
3dHelpers.GetLineTangent(&tangent, &points, i, i == m_NumPoints - 1 ? i : i + 1, 0.0);
VECTOR3D normal = tangent.cross(rp.Transform.getZVector());
normal.normalize();
double offset = ((double)j) - (((double)numSlices) / 2.0);
VECTOR3D vecLoc = points[i].add(normal.scale(spacing * offset));
arr2ProjPlanes[j][i] = new MATRIX44D(vecZ, new VECTOR3D(), new VECTOR3D(), vecLoc);
}
}
double curvedLength = getCurvedLength(points);
int volWidth = (int)((curvedLength) / spacing);
int volHeight = (int)(height / spacing);
if (volWidth > 4096 || volHeight > 4096) {
// "Volume width or height ends up being greater than 4096. This is currently unsupported.");
}
VOLUME_DATA_PARAMS oldVDP;
m_vol.GetVolumeDataParams(&oldVDP, true);
IVolumeDataContext pVolumeData;
m_crVolume.GetCurvedReformat(&pVolumeData, m_NumPoints, numSlices, volWidth, volHeight,
m_NumPoints / 2, (float)(oldVDP.Spacing.y / spacing), arr2ProjPlanes, CURVED_MPR_TYPE_X_IS_CURVED_TRICUBIC);
To create a new reformatted series along this path, multiple groups of planar slices through the dataset are specified, along with details about how to align the slices with a vector axis. Each group of planar slices, once realigned, forms a new dataset slice. Each of the newly reformatted dataset slices are then stacked together to form a new dataset using the hdrcRenderEngineContext::getCurvedReformat() and hdrcRenderQueue::getCurvedReformat() methods based on the following parameters:
int numProjPlanesPerSlice |
The number of MATRIX44D entries that define the planar slices that compose each new dataset slice. |
int numSlices |
The number of slices that will make up the new dataset. |
int ppWidth |
Width of the newly generated dataset. |
int ppHeight |
Height of the newly generated dataset. |
int measurementSlice |
The the index (0-based) of the slice to specify for accurate measurement. For example, when three is specified, the fourth image in the volume will have the property that pixels in the X and Y directions will be accurately represented in millimeters. |
float zoom |
The number of pixels per voxel in the non-curved dimension. If this is -1, the pixels per voxel value is calculated based upon the length of the curve. |
MATRIX44D arrProjPlanes[][] |
A two dimensional array composed of MATRIX44D objects. The dimensions are arrProjPlanes[numSlices][numProjPlanesPerSlice]. These are the matrices that define each Curved MPR slice that is assembled into the Curved Reformat dataset volume. |
Describes which axis is non-linear (and which is linear) during the curve straightening process, as well as the interpolation type used to render the image. |
// Parameters for new dataset
int thickness = 50;
float spacing = 0.25f;
int height = 50;
int numSlices = (int)(thickness / spacing);
MATRIX44D[][] arr2ProjPlanes = new MATRIX44D[slices][];
RENDER_PARAMS rp = new RENDER_PARAMS();
rp.Transform.setIdentity();
VECTOR3D vecZ = rp.Transform.getZVector();
vecZ.normalize();
if(vecZ.dot(new VECTOR3D(0.0, 0.0, 1.0)) > 0.0) {
vecZ = vecZ.scale(-1.0);
}
// get the points ahead of time for the tangent calculation
VECTOR3D[] points = new VECTOR3D[m_points.length];
for(int i = 0; i < m_points.length; i++) {
points[i] = m_points[i].vector3D;
}
for(int j = 0; j < arr2ProjPlanes.length; j++) {
// for each slice
arr2ProjPlanes[j] = new MATRIX44D[m_points.length];
// now calculate the points for this slice
for (int i = 0; i < m_points.length; i++) {
// for each point on the slice
VECTOR3D tangent = hdrc3DHelpers.getLineTangent(points, i, i == arr2ProjPlanes[j].length - 1 ? i : i + 1, 0.0);
VECTOR3D normal = tangent.cross(rp.Transform.getZVector());
normal.normalize();
double offset = ((double)j) - (((double)arr2ProjPlanes.length) / 2.0);
VECTOR3D vecLoc = points[i].add(normal.scale(spacing * offset));
arr2ProjPlanes[j][i] = new MATRIX44D(vecZ, new VECTOR3D(), new VECTOR3D(), vecLoc);
}
}
double curvedLength = getCurvedLength(points);
int volWidth = (int)((curvedLength) / spacing);
int volHeight = (int)(height / spacing);
if (volWidth > 4096 || volHeight > 4096) {
// "Volume width or height ends up being greater than 4096. This is currently unsupported.");
}
VOLUME_DATA_PARAMS oldVDP = m_vol.getVolumeDataParams(true);
hdrcVolumeDataContext vdc = m_crVolume.getCurvedReformat( m_points.length, arr2ProjPlanes.length, volWidth, volHeight,
m_points.Count / 2, (float)(oldVDP.Spacing.y / spacing), arr2ProjPlanes, hdrcDefines.CURVED_MPR_TYPE_X_IS_CURVED_TRICUBIC);
The .NET example code is included below:
// Parameters for new dataset
int thickness = 50;
float spacing = 0.25f;
int height = 50;
int numSlices = (int)(thickness / spacing);
MATRIX44D[][] arr2ProjPlanes = new MATRIX44D[slices][];
RENDER_PARAMS rp = new RENDER_PARAMS();
rp.Transform.setIdentity();
VECTOR3D vecZ = rp.Transform.getZVector();
vecZ.normalize();
if(vecZ.dot(new VECTOR3D(0.0, 0.0, 1.0)) > 0.0) {
vecZ = vecZ.scale(-1.0);
}
// get the points ahead of time for the tangent calculation
VECTOR3D[] points = new VECTOR3D[m_points.length];
for(int i = 0; i < m_points.length; i++) {
points[i] = m_points[i].vector3D;
}
for(int j = 0; j < arr2ProjPlanes.length; j++) {
// for each slice
arr2ProjPlanes[j] = new MATRIX44D[m_points.length];
// now calculate the points for this slice
for (int i = 0; i < m_points.length; i++) {
// for each point on the slice
VECTOR3D tangent = hdrc3DHelpers.getLineTangent(points, i, i == arr2ProjPlanes[j].length - 1 ? i : i + 1, 0.0);
VECTOR3D normal = tangent.cross(rp.Transform.getZVector());
normal.normalize();
double offset = ((double)j) - (((double)arr2ProjPlanes.length) / 2.0);
VECTOR3D vecLoc = points[i].add(normal.scale(spacing * offset));
arr2ProjPlanes[j][i] = new MATRIX44D(vecZ, new VECTOR3D(), new VECTOR3D(), vecLoc);
}
}
double curvedLength = getCurvedLength(points);
int volWidth = (int)((curvedLength) / spacing);
int volHeight = (int)(height / spacing);
if (volWidth > 4096 || volHeight > 4096) {
// "Volume width or height ends up being greater than 4096. This is currently unsupported.");
}
VOLUME_DATA_PARAMS oldVDP = m_vol.getVolumeDataParams(true);
hdrcVolumeDataContext vdc = m_crVolume.getCurvedReformat( m_points.length, arr2ProjPlanes.length, volWidth, volHeight,
m_points.Count / 2, (float)(oldVDP.Spacing.y / spacing), arr2ProjPlanes, hdrcDefines.__Fields.CURVED_MPR_TYPE_X_IS_CURVED_TRICUBIC);