Plane markup json files - how to create "baseToNode" and "orientation" matrices

Hi,
I’m looking into using slicer for the visualisation of results from another tool. Therefore, I’d like to import plane definitions to quickly inspect whether they are as expected.
I use a point and normal vector in world coordinates. I keep the rest of a slicer generated json file as template while replacing “center” and “normal”.
The “objectToBase” matrix seems straight forward enough the conversion between the image orientation to the default LPS orientation of the markups.
But it’s not immediately clear how the “baseToNode” and “orientation” matrices are defined and I can’t find a reference. I hoped I could omit those in the definition as the point and normal should be sufficient for defining a plane (to my needs). This does not seem to be the case so I assume I will have to calculate the matrices to make Slicer read my file.

Could someone point me to a resource of how these should behave or how they would be calculated based on the point and normal vector? Much appreciated!

Definition of these coordinate systems are here.

If you specify a plane by a single point ("planeType": "pointNormal") then you only need to specify:

  • plane position: position of the one and only control point
  • plane orientation: baseToNode matrix. Note that a plane in mathematical sense is specified by a position and a normal, but if you want to display a plane then you need to specify its rotation around that normal. baseToNode contains the two axis of the plane and the plane normal. If you don’t care about the rotation then you can choose an arbitrary orientation and use cross product with the normal vector to make it orthogonal to the normal vector. Translation component of baseToNode matrix is ignored (because position is set by the control point), so it can be set to 0.0, 0.0, 0.0.
  • plane bounds: specifies how far the plane extends in 4 directions (-x, +x, -y, +y) from the plane position

Example:

{
    "@schema": "https://raw.githubusercontent.com/slicer/slicer/master/Modules/Loadable/Markups/Resources/Schema/markups-schema-v1.0.3.json#",
    "markups": [
        {
            "type": "Plane", "coordinateSystem": "LPS", "planeType": "pointNormal",
            "baseToNode": [
                -0.7177763971220261, -0.48438183588553957, -0.5001712514716112, 0.0,
                0.3974111999339178, -0.8748566951587867, 0.2769297764830091, 0.0,
                -0.5717179216201003, -1.1102230246251565e-16, 0.820450253274623, 0.0,
                0.0, 0.0, 0.0, 1.0],
            "planeBounds": [-50.0, 50.0, -50.0, 50.0],
            "controlPoints": [{ "position": [60.6068749511193, -26.833897399804197, -175.25000000000006] }],
            "display": { "visibility": true, "opacity": 1.0, "color": [0.4, 1.0, 1.0] }
       }
    ]
}

Dear teacher,
Hello, Thanks for your excellent work!
I have a question, If I know the plane center(c1,c2,c3) and normal(n1,n2,n3), How can I calculate the ‘baseToNode’?

Hi team,

I seem to be struggling with the same issue coming to the end of my PhD on Sarcoma. (I have worked across disciplines to propose a method for bone histology registration because bone can be a little tricky… We are aiming to enable similar correlation to prostate research, Mirabela and PIMed).

I can get the planes for histology initalisaiton in python working, but these are only specified by a centre and normal… The baseToNode seems to be giving similar issues to this thread when I try import jsons to Slicer… Mind expanding on your comments about circumventing baseToNormal above Andras?

I missed posting this a little while ago. To wrap my problem up I managed to cobble something together that seemed to work for my issue… A wee function for calculating the baseToNode array:

def CalcBase2Node(self):
    '''Calculating the baseToNode matrix for .JSON import into Slicer'''
    # Normalize the plane normal vector
    self.normal /= np.linalg.norm(self.normal)

    # Choose an arbitrary vector not parallel to the self.normal (e.g., x-axis)
    arbitrary_vector = np.array([1, 0, 0])

    # Calculate a third axis of the plane coordinate system
    third_axis = np.cross(self.normal, arbitrary_vector)
    third_axis /= np.linalg.norm(third_axis)

    # Calculate the second axis of the plane coordinate system
    second_axis = np.cross(self.normal, third_axis)
    second_axis /= np.linalg.norm(second_axis)

    # Construct the rotation-translation matrix baseToNode
    base_to_node_matrix = np.eye(4)
    base_to_node_matrix[:3, 0] = third_axis
    base_to_node_matrix[:3, 1] = second_axis
    base_to_node_matrix[:3, 2] = self.normal
    base_to_node_matrix[:3, 3] = self.centre

    return base_to_node_matrix

This feeds a seraliser and produces .json files which import well into Slicer now.