Coordinate systems are the respective transformation conventions sometimes cause confusion for both Suite and SDK users.
First of all, all data showing up in the data widget have a 4x4 matrix dictating where that data is located in space. This matrix can be edited through various ways, including the Edit Transformation algorithm, or the Sweep Properties for ultrasound sweeps.
For all images, the matrix is always relative to the image center, not any of its corners.
There are two transformation conventions in the framework:
- FROM WORLD: The matrix maps from the world coordinate system to the data coordinate system, i.e. x_data = matrix * x_world. This is the general case for images, except ultrasound sweeps and cone beam data.
- TO WORLD: The matrix maps from the data coordinate system to the world coordinate system, i.e. x_world = matrix * x_data. This is used for all other data types.
The reason for this distinction is that for same use cases (in particular MPR reslicing), having the inverse in memory is computationally much more efficient.
The Edit Transformation algorithm uses the FROM WORLD convention.
For ultrasound sweeps, the data matrix is called the registration. It basically moves the entire sweep around in space. In addition, ultrasound sweeps also have a calibration matrix, which usually describes the geometric relation between the image center and a tracking target. The final transformation of the i-th ultrasound frame of a sweep is thus: M[i] = R * T[i] * C, where R is the registration, C the calibration, and T[i] the i-th tracking matrix. In fact, the tracking matrices are stored as a TrackingStream within the ultrasound sweep. In the Sweep Properties, both R and C can be edited in the TO WORLD convention.
SDK users are directed to the General Design Documentation / Coordinate Systems page for more details.
Because there are plenty of ways to map Euler angles to a 3x3 rotation matrix, we share the code here:
mat4 Pose::eulerToMat(const vec3& trans, const vec3& rot)
{
mat4 M = mat4::Identity();
M.topRightCorner<3, 1>() = trans;
if (!rot.isZero())
M.topLeftCorner<3, 3>() = eulerToMat(rot);
return M;
}
mat3 Pose::eulerToMat(const vec3& rot)
{
vec3 rotRad = rot * (M_PI / 180.0);
double cx = cos(rotRad[0]), sx = sin(rotRad[0]);
double cy = cos(rotRad[1]), sy = sin(rotRad[1]);
double cz = cos(rotRad[2]), sz = sin(rotRad[2]);
mat3 M;
M <<
cy*cz, cz*sx*sy - cx*sz, sx*sz + cx*cz*sy,
cy*sz, sx*sy*sz + cx*cz, cx*sy*sz - cz*sx,
-sy, cy*sx, cx*cy;
return M;
}