First the standard disclaimer: mrb files are meant to be a application-specific format not an interchange format, so they are subject to change without notice as Slicer’s implementation changes. You have been warned
For interchange we suggest using DICOM, particularly SEG objects if you are interested in segmentation (see dicom4qi for example).
That said, certain things are likely to persist. Basically yes, .mrb is likely to always be a .zip file containing a top level mrml scene file. All of the storable nodes are redirected into the Data directory and the result is zipped up. Everything else about it is basically an implementation detail byproduct of this code.
If you are determined to parse the structure it should be pretty easy to do - just read the mrml xml and all the names, datatypes, filenames, etc will be there. It’s essentially just a serialization of the application state.
Unfortunately, the plugin is not ideal for the workflow in our lab. So I do have some more questions regarding the mrb format - or perhaps, slicer in general.
What is the data-model for segmentations?
As documented here, I understand that one segmentation consists of multiple segments. But what is a segmentation/ segment then really? Intuitively, I would expect just multiple segmentations being associated with an image, each being defined by a single mask with the same shape as the image volume.
Why are there both Segmentation- and LabelMapVolume-Nodes within the mrml-document? Aren’t label-maps just a different representation for a set of multiple segmentations? Or in what way are they distinct from one another?
(Some questions might arise from my lack of usage-experience with slicer.)
Could you write about your workflow? It would help in understanding what you would like to do and what would be the best potential solutions.
Segmentation defines multiple, potentially overlapping regions. Internally, it can use various representations (binary labelmap, closed surface, fractional labelmap, set of parallel contours, ribbons, etc.) and keep them all in sync. See an overview of how it is related to other nodes here.
Normally you only have segmentation in the scene because segmentation can do everything that a labelmap, but much more (it can store overlapping segments, can be displayed directly in 3D, can be edited in Segment Editor, etc.). If some external software creates a labelmap volume then you can load it directly as segmentation.
Together with the input from a colleague I conclude the following:
What I intuitively named a segmentation is a segment is slicer-terms
A segmentation in slicer terms is just a set of multiple segments and every segment has to be part of a segmentation. (Presumably, segmentations have been introduced to slicer to help organize a large number of segments)
A mrb record can have an arbitrary number of segmentations, each containing an arbitrary number of segments.
A label-map can be actively derived from a set of segmentations (or segments?) and saved within the slicer data-model.
As label-maps are derived from segmentations (in some point in time of their edit-history), they are potentially outdated w.r.t. the segmentations
Is that about right?
(Side-Note: Having all this information about the data-model as e.g. an UML-diagram would be really helpful)
About our workflow: We currently need to re-visit and slightly correct the segmentations within a large number of scans (thousands). Just loading and saving those segmentations into/from slicer using the plugin takes just too long as the number of required steps/clicks is quite high.
A labelmap is a lossy representation of a segmentation (no overlap information).
Labelmaps or other data can also be imported back into segmentations. So which one is up to date depends on what modules you need to use to process data.
Yes please do! (haha)
Sounds like a good opportunity to make a custom scripted module to simplify things.
CaseIterator extension was developed for exactly this. You can probably use it as is, but since it is a Python scripted module you can also easily customize and/or extend it to do exactly what you need.
The extension manager website can be overwhelmed with requests sometimes. If you have trouble installing an extension then click on Install button again, until download and installation succeeds. We plan to migrate to a new server backend soon, which should not have these inconveniences anymore.
One more question: What is the extent being associated with a segment? It gets written into the nrrd header for each segment and is a space-separated list for six integer numbers. (E.g.: Segment5_Extent: '23 333 14 237 0 0')
SegmentN_Extent defines where within the volume the segment has non-zero voxels according usual VTK extent conventions (xmin, xmax, ymin, ymax, zmin, zmax). It is a hint used for optimizing segmentation reading performance.
What is the relationship between all of these values?:
“space origin” and “space directions” from image volume nrrd header
“space origin”, “space directions” and “Segmentation_ReferenceImageExtentOffset” from segmentation volume nrrd header
the attributes “dimensions”, “xyzOrigin”, “uvwExtents”, “uvwDimensions”, “uvwOrigin” and “sliceToRAS” of a node within the mrml file
voxel coordinates within the image- and segmentation-volumes
I assume, most of those values can be derived from basic information about the image volume and a segmentation volume but I struggle to get the complete picture of that.
These are all standard nrrd fields that describe image geometry, except Segmentation_ReferenceImageExtentOffset (which is added to allow extent to start at any voxel position; this is needed, since VTK allows non-zero extent start values, but nrrd format always assumes extents start from 0).
These are slice view properties, define the size and origin of slice display (XYZ) and texture image (UVW) coordinate systems.
IJK to RAS (or IJK to LPS) transform specifies mapping between voxel and physical coordinate system. You can compute these transforms from origin, spacing, and axis directions (e.g., defined by space origin, space directions values in a NRRD file).
When trying to read the image-volume nrrd and overlay it with a segmentation volume nrrd, I’m currently getting a slight mismatch of 22 voxels in the anterior-posterior axis. My assumption was, that I must only care about the Segmentation_ReferenceImageExtentOffset nrrd header field within the segmentation volume to rebuild the full segmentation volume - is that correct?
My python code for that looks roughly like this:
# read 3D image volume
image_nrrd_data, image_nrrd_header = nrrd.read(...)
# Read 4D segmentation mask
segmentations_nrrd_data, segmentations_nrrd_header = nrrd.read(...)
# Read segmentation volume offset w.r.t. image volume origin
offset = [
int(s)
for s in segmentations_nrrd_data["Segmentation_ReferenceImageExtentOffset"].split()
]
# iterate over all segments
for segment_index in range(...):
segment_mask = segmentations_nrrd_data[segment_index]
# Build 3D mask with full size
mask = np.zeros(image_nrrd_data.shape, dtype=np.bool)
mask[
offset[0] : offset[0] + segment_mask.shape[0],
offset[1] : offset[1] + segment_mask.shape[1],
offset[2] : offset[2] + segment_mask.shape[2],
] = segment_mask
# save mask for that segment...
Note, that I’m not taking into consideration information from the nrrd headers SegmentX_Extent, space_direction or space origin.