(io) add API support for fiducials in data::Series
Description
- Add support for points and distances (or line)
- Shape ? (for contours)
Functional specifications
Ideally, the API should be convenient enough so that transition to data::Landmarks and distances to fiducials should seamless.
- The generic modules, such as Patient, Study, etc... will be copied from the parent ImageSeries/ModelSeries.
- SOP Class UID is "1.2.840.10008.5.1.4.1.1.66.2".
- TODO: SOP Instance UID.
- Content Date and Content Time are set to current timestamp the first time a landmark/distance is placed.
- Instance Number will be set to 0.
- Content Label will be set to "Fiducials".
- Content Description will be set to "".
- One Fiducial Set will be equivalent to one of our landmark groups. Inside Fiducial Set Sequence:
- If Frame of Reference UID isn't known or the fiducial group spans across multiple frames, Referenced Image Sequence contains all the relevant frames. Else, Frame of Reference UID is used.
- Inside Fiducial Sequence:
- Shape Type is POINT for landmarks and LINE for distances.
- For landmarks, Fiducial Identifier is
<GROUP>_<INDEX>
, where is the group name and the index of the landmark; for distances, it is the distance between the two points in millimeters. - If Frame of Reference UID isn't known or the fiducial group spans across multiple frames, Graphic Coordinates Data Sequence contains the 2D positions in each frame. Else, Contour Data contains the 3D positions and Number of Contour Points contains the number of points.
- Eventually, Fiducial UID could be used to identify a fiducial in order to modify them more conveniently.
Technical specifications
A new class, FiducialsSeries, inheriting from Series, will be created. ImageSeries and ModelSeries will have a FiducialsSeries as a member, it can't be accessed via a method getFiducials
. FiducialsSeries will contain the necessary methods to fetch/modify the Space Fiducials dataset such as getContentData
, setContentTime
, etc.
The code may be inspired by https://git.ircad.fr/sight/sight/-/blob/dev/libs/io/dicom/writer/ie/SpatialFiducials.cpp.
In Spatial Fiducial IOD, there are sequences in sequences. There are also sequences in sequences in the Enhanced US Volume IOD, however it was sequences with 0 or 1 element, effectively acting like std::optional
. Meanwhile, in Spatial Fiducial, there are sequences in sequences with potentially more than 1 element, effectively acting like a std::vector
. This isn't handled by SeriesImpl, so new methods will have to be created. One of these methods would be for example template<typename... As> auto getValue(std::size_t instance, std::array<std::size_t, sizeof...(As) - 1> indices) const
, which could be called like m_pimpl->getValue<gdcm::Keywords::FiducialSetSequence, gdcm::Keywords::FiducialSequence, gdcm::Keywords::GraphicCoordinatesDataSequence, gdcm::Keywords::GraphicData>(0, {0, 1, 2})
, which would get the graphic data of the third element in graphic coordinates data sequence of the second fiducial in the first fiducial set.
This sequences in sequences problem also is a problem when designing the high-level methods in FiducialsSeries. Basically, we have two ways to do it:
- Allow to "directly" get/set the values. For example, if I would modify the third element in graphic coordinates sequence of the second fiducial in the first fiducial set, I would call
void setGraphicData(std::size_t fiducialSetNumber, std::size_t fiducialNumber, std::size_t graphicCoordinatesDataNumber, const std::vector<Point2>& graphicData);
likesetGraphicData(0, 1, 2, {{0, 1}, {2, 3}})
. - Create high-level structures that represent the inner state of the dataset. For example, creating the structures
FiducialSet
,Fiducial
, andGraphicCoordinatesData
. It would then be possible to directly replace the entire sequence, or only one item inside, or append an item inside. With this approach, one can:- set all the existing fiducial sets with
setFiducialSets(std::vector{FiducialSet{/*...*/}, /*...*/});
- set one fiducial set in a particular index
setFiducialSet(0, FiducialSet{/*...*/})
; - append a new fiducial set
appendFiducialSet(FiducialSet{/*...*/});
- All the sequences have these methods, so one can also set a graphic coordinates data with
setGraphicCoordinateData(0, 1, 2, GraphicCoordinateData{/*...*/});
- To set a particular field, it is possible to do a get/set couple. For example, to do the previous operation, one could do:
-
std::vector<FiducialSet> fiducialSets = getFiducialSets(); fiducialSets[0].fiducialSequence[1].graphicCoordinatesDataSequence->at(2).graphicData = {{0, 1}, {2, 3}}; setFiducialSets(fiducialSets);
-
FiducialSet fiducialSet = getFiducialSet(0); fiducialSet.fiducialSequence[1].graphicCoordinatesDataSequence->at(2).graphicData = {{0, 1}, {2, 3}}; setFiducialSet(0, fiducialSet);
-
std::vector<Fiducial> fiducials = getFiducials(0); fiducials[1].graphicCoordinatesDataSequence->at(2).graphicData = {{0, 1}, {2, 3}}; setFiducials(0, fiducials);
-
Fiducial fiducial = getFiducial(0, 1); fiducial.graphicCoordinatesDataSequence->at(2).graphicData = {{0, 1}, {2, 3}}; setFiducial(0, 1, fiducial);
-
std::vector<GraphicCoordinatesData> graphicCoordinatesDataSequence = *getGraphicCoordinatesDataSequence(0, 1); graphicCoordinatesDataSequence[2].graphicData = {{0, 1}, {2, 3}}; setGraphicCoordinatesDataSequence(0, 1, graphicCoordinatesDataSequence);
-
GraphicCoordinatesData graphicCoordinatesData = getGraphicCoordinatesData(0, 1, 2); graphicCoordinatesData.graphicData = {{0, 1}, {2, 3}}; setGraphicCoordinatesData(0, 1, 2, graphicCoordinatesData);
-
- set all the existing fiducial sets with
Test plan
- Unit tests