MarkupsLine .fcsv loads as MarkupsFiducials

If I create a Line Markups annotation, save it as “Markups Fidicials .fcsv” (the only format available to save Markups annotations in the “Save as” dialog) and load back in Slicer, it shows up not as MarkupsLine, but as MarkupsFiducials. Is this a bug?

My understanding is that currently reload of MarkupsLine, Angles, Planes are not supported. They are being loaded as individual fiducials. It don’t it is not a bug, but a feature that is not implemented yet.
@lassoan can give better feedback.

Yes, it has not been implemented yet. The main challenge is that we don’t want to invent a new file format for this and it is not easy to find a good standard one. The new DICOM-compliant json format looks promising but we did not have time for a thorough analysis. We also welcome suggestions for file formats from the community (any annotation file format that is used by a significant number of people can be a candidate).

Possible workaround: If you save the line/curve etc with a scene and reload using the MRML file, it will load as the correct type.

1 Like

Andras, I appreciate the concern, and I am the last one who would argue for inventing a new format … BUT the .fcsv format already exists, whether we like it or not, and the Line annotations are already saved, but cannot be reloaded.

It is a good idea to consider DICOM as an option, but the scope of use for Slicer is broader than medical imaging covered by DICOM, the annotations may need to exist without any images being annotated, and it just may never happen that DICOM will be able to meet the needs of all Slicer users.

I would argue that LineMarkups should be saved in a Slicer-specific format, but with the attributes that, when applicable, would allow to convert that general-purpose representation into something more specific. So for example, for DICOM, if Line is annotated on the images that are loaded from DICOM, it might be helpful to store the SeriesInstanceUID for the volume being annotated.

I asked this question because I work with a user who is submitting a very valuable dataset (at least, in my opinion) to TCIA (3D Ultrasound volumes, MR series, segmentations, and annotated locations of biopsy samples). The user initially was submitting the data that could only be loaded into the custom Matlab tools he had. We worked together to make sure that the 3D US DICOM files could be loaded into Slicer, and STL surfaces are in the same coordinate frame as images, and I also suggested that instead of a spreadsheet with biopsy locations he uses Slicer .fcsv, and based on his assessment, Line annotation is best suitable for this purpose. But it is not possible to reload it back.

He also identified the workaround suggested by @Sam_Horvath. After playing with it a bit, I think I am going to suggest to save all annotations for individual studies and save as a MRML scene, without images (I didn’t know that a scene can be reloaded without replacing the existing scene!). Then the consumers of the dataset would just need to follow the directions to compose the scene from the DICOM, STL and MRML bits … In this specific case, we could probably come up with a standard way to store all of the components in DICOM, but implementing all of the pieces that would be needed to do the conversion, figure out details of encoding, and support loading in Slicer are incompatible with the time constraints for the dataset submission, and will require non-trivial effort. Eventually, I hope we will get to do it, but for now my goal is to have the dataset that can be loaded using 3D Slicer, so we are prepared to do the conversion later.

Totally agree with these points - if you are okay with a Slicer-specific data format then MRML is totally the right answer.

While MRML retains the Markups as they are intended (angles, lines), it is a harder format to extract the coordinates from. The simplicity of fcsv is great, and it can be readily imported into other data analysis platforms. MRML harder to decipher.

While I will agree with the notion to NOT to invent a yet another format, I am wondering if we can’t find a compromise, and add another field to fcsv called “type” which would be line, curve, closedcurve, angle and plane. So when they are read in, they can be reconstructed as MarkupLine, MarkupAngle and MarkupPlane etc.

The measurements they report perhaps can be recalculated at the time of loading the data (length, angle, area)? So, while the measurement value itself is not saved in the file, it can still be reconstituted by reloading the data into the scene.

Would this be an acceptable half-solution?

1 Like

What do you think about saving current content of markup node into a json file like this:

{
  "@schema": "https://raw.githubusercontent.com/slicer/slicer/master/doc/markups-schema.json#",
  "markups": [ {
    "label": "Some Angle",
    "type": "angle",
    "coordinateSystem": "LPS",
    "controlPoints": [
        {
            "id": "1",
            "label": "A-1",
            "description": "",
            "associatedNodeID": "",
            "position": [12.3, 28.4, 11.2],
            "orientation": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
            "selected": True,
            "locked": False,
            "visibility": True,
            "positionStatus": "defined"
        },
        {
            "id": "2",
            "label": "A-2",
            "description": "",
            "associatedNodeID": "",
            "position": [12.3, 28.4, 11.2],
            "orientation": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
            "selected": True,
            "locked": False,
            "visibility": True,
            "positionStatus": "defined"
        },
        {
            "id": "3",
            "label": "A-3",
            "description": "",
            "associatedNodeID": "",
            "position": [12.3, 28.4, 11.2],
            "orientation": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
            "selected": True,
            "locked": False,
            "visibility": True,
            "positionStatus": "defined"
        }
    ],
    "measurements" : [
        {
            "name": "angle",
            "printableValueWithUnit": "23.5 deg",
            "value": 23.5,
            "units": "degrees",
            "description": "angle between first two and last two points",
            "printFormat": "",
            "quantityCode": "",
            "unitsCode": "",
            "methodCode": ""
        },
        {
            "name": "length",
            "printableValueWithUnit": "11.2 mm",
            "value": 11.2,
            "units": "mm",
            "description": "distance between first and last points",
            "printFormat": "",
            "quantityCode": "",
            "unitsCode": "",
            "methodCode": ""
        }
    ],
    "display": {
        "color": [1.0, 0.0, 0.0],
        "selectedColor": [1.0, 1.0, 0.0],
        "propertiesLabelVisibility": True,
        "pointLabelsVisibility": True,
        "textScale": 5.0,
        "glyphType": "sphere3D",
        "glyphScale": 5.0,
        "glyphSize": 3.0,
        "useGlyphScale": True,
        "sliceProjection": False,
        "sliceProjectionUseFiducialColor": True,
        "sliceProjectionOutlinedBehindSlicePlane": True,
        "sliceProjectionColor": [0.2, 0.4, 0.8],
        "sliceProjectionOpacity": 0.5,
        "lineColormap": "viridis",
        "LineThickness": 2.0,
        "LineColorFadingStart": 2.0,
        "LineColorFadingEnd": 2.0,
        "LineColorFadingSaturation": 2.0,
        "LineColorFadingHueOffset": 2.0
    },
    "behavior": {
          "snapMode": "snapToVisibleSurface",
          "handlesInteractive": False
    },
    "curvePoints": [
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9],
        [0.0, 0.1, 0.2],
        [0.2, 0.3, 0.5],
        [0.5, 0.8, 0.9]
    ] }
  ]
}

Some sections, such as display, behavior, and cuvePoints could be optional or not used for all types of markups.

Reading/writing speed in C++ and Python looks good - a few seconds for tens of thousands of points.

The file could store any number of markups (e.g., a folder could be selected in data module and all markups in that folder could be exported into a single json file).

You can import a json file and export a csv file with columns populated with control point properties in about 10 lines of Python code (or we could also add a csv export option). However, these csv files could not be imported as markups.

1 Like

Overall, I think it is definitely a much better approach than the current fcsv, but predictably it does open the cans of worms! :smiley:

Couple of quick thoughts:

  • Can you share the schema file? https://raw.githubusercontent.com/slicer/slicer/master/doc/markups-schema.json doesn’t work for me.
  • I understand the reasons behind keeping associatedNodeID, but it would be nice to have some reference that is not scene-specific. Can we add some reference to the DICOM series, if available? A related question is what else, if anything, should be recorded about the state of the viewers at the time given markup was created.
  • Would it make sense to have a UID assigned to each element in the scene to disambiguate the references? Or maybe better to have scene assigned a UID, and then items within the scene can be interpreted in the context of the scene UID?
  • FrameOfReferenceUID from DICOM, if available, could be valuable to include.
  • Should we consider including any information about the authoring of the annotation? (e.g., information about the user, whether the annotation was generated manually or automaticaly)

Somewhat related, here’s the white paper summarizing DICOM SR capabilities related to planar annotations. We’ve been working on this in the context of the IDC project.

I don’t want to derail or delay your initiative by adding more requirements, just a couple of thoughts to consider. This is definitely a great start, but we should discuss this more. I suggest we split this off into a separate conversation?

So this format would also contain the regular MarkupsFiducials as well, or do we retain them as fcsv?

There is no schema yet. We can create that once we have a good idea of the final format.

This is what is in the markups node now. I agree that it is not very useful, as node IDs can only be used with a scene file. How would you recommend to refer to a DICOM series? Would a series instance UID be enough?

You mean using a universal unique identifier (UUID) to each node in the scene? What if you load the same scene twice? Would you generate new UUIDs?

We could definitely allow a field like that in the file. It might be tricky to get this information in general (data may be loaded from multiple sources and so there could be several frame of references), but there could be modules that implement specific workflows and can determine this information reliably.

Yes, sure. Do you have any suggestion of what information should be recorded exactly?

We could still allow saving markup fiducials as .fcsv, in addition to the new .mrk.json. Similarly how a volume can be saved in .nrrd or .mha format.

1 Like

Maybe better to record SOPInstanceUIDs for individual points, initialized from the slice from the foreground volume in the viewer that was used to place the point?

How about a new UUID assigned to a scene every time the scene is saved, and keeping the individual nodes IDs unchanged?

For example, whether annotations were created manually or by an automated tool, and maybe also have an option to include some kind of user identification (either collected from the system, or self-declared by the user in the settings).

I’ve submitted a pull request that implements storage of markup data and display node in JSON file:

The file content right now is a trivial mapping of MRML node properties to JSON name:value pairs, but there are undergoing discussions to adopt DICOM SR compliant JSON representation instead. See more information and examples in this DICOM supplement.

1 Like

I tried loading the .json’s without the MRML node into a blank scene, and I got these errors:
It is with today’s build (on windows 10).

vtkMRMLMarkupsJsonStorageNode::ReadDataInternal failed: error parsing the file  'C:/Users/murat/Desktop/A.mrk.json


vtkMRMLMarkupsStorageNode::ReadDataInternal failed: error opening the file 'C:/Users/murat/Desktop/A.mrk.json


vtkMRMLMarkupsJsonStorageNode::ReadDataInternal failed: error parsing the file  'C:/Users/murat/Desktop/C.mrk.json


vtkMRMLMarkupsStorageNode::ReadDataInternal failed: error opening the file 'C:/Users/murat/Desktop/C.mrk.json

I cannot reproduce this issue using Slicer 4.11.0-2020-05-30 (revision 29099 / e53e8af) win-amd64. The dashboard looks good, too: vtkMRMLMarkupsStorageNodeTest2 test checks if a node can be saved and reloaded and it has not failed on any platforms.

Can you upload somewhere the files that fail to load and post the link here?

Do the files match the json schema? (you can check with this online tool)

Here you go: https://app.box.com/s/6aguthyvz8o1p7kc0pvwuyiv29noyjne

At least the angles json checked out with the online tool. But I still cannot load them

Some control points in these files have invalid orientation, which prevents loading them. How did you create the markups?

Weird. I just randomly clicked a few points in an empty scene to test the save and load.

I’ve pushed a fix today. Let me know if you can still reproduce the problem in the Preview Release tomorrow or later.

I just downloaded the newest nightly build and realized the markup fudicials are saved as mrk.json files. If possible, I am in favor keeping the option to save them as .fscv files as mentioned by Murat that is a convenient file format to be used elsewhere. Thank you.