Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDCM Secondary Capture spatial metadata #4521

Merged

Conversation

thewtex
Copy link
Member

@thewtex thewtex commented Mar 15, 2024

Support reading Secondary Capture spatial metadata with GDCM. Addresses issue #4109 , reading Visible Male RGB secondary capture spatial metadata. Also targeting support for highdicom generated SC re: ImagingDataCommons/highdicom#247.

This is a follow-up to #4111.

The corresponding patch was submitted to GDCM in malaterre/GDCM#158 and malaterre/GDCM#167.

Add tests with the Visible Male data.

@github-actions github-actions bot added type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots area:ThirdParty Issues affecting the ThirdParty module area:IO Issues affecting the IO module labels Mar 15, 2024
@thewtex thewtex changed the title Gdcm sc image metadata GDCM Secondary Capture image metadata Mar 19, 2024
@thewtex thewtex changed the title GDCM Secondary Capture image metadata GDCM Secondary Capture spatial metadata Mar 19, 2024
@thewtex thewtex marked this pull request as ready for review March 19, 2024 19:53
@thewtex thewtex requested a review from hjmjohnson March 19, 2024 20:17
Copy link
Contributor

@pieper pieper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, thanks for picking it up 👍

It would be great if someone with more knowledge of the ITK internals wants to also have a look, but I trust @thewtex has that covered so I'll approve.

@thewtex thewtex mentioned this pull request Mar 20, 2024
7 tasks
@thewtex thewtex marked this pull request as draft March 21, 2024 21:39
thewtex and others added 5 commits March 25, 2024 06:10
> The path 'Modules/ThirdParty/GDCM/src/gdcm/Source/DataDictionary/privatedicts.xml' has size 1033 KiB, greater than allowed 1024 KiB.
Code extracted from:

    https://github.com/malaterre/GDCM.git

at commit 06091299f2227f8470a4468821d231b760337ca0 (release).
# By GDCM Upstream
* upstream-GDCM:
  GDCM 2024-03-25 (06091299)
This enables support for reading image orientation patient and image
position patient from DICOM secondary capture images.

This addresses needs such as reading Visible Human DICOM color images in
3D Slicer:

  NA-MIC/ProjectWeek#875

and reading highdicom derived secondary capture DICOM in ITK-Snap:

  ImagingDataCommons/highdicom#247

Update the RGB tests' baseline that were using a .png baseline because they now
output spatial metadata.
This is using a Visible Male RGB slice secondary capture DICOM created by David Clunie as an input,
described in Issue InsightSoftwareConsortium#4109.
@thewtex thewtex marked this pull request as ready for review March 25, 2024 11:32
@thewtex
Copy link
Member Author

thewtex commented Mar 25, 2024

Support has been merged into GDCM, thanks @malaterre @issakomi

@fedorov
Copy link
Member

fedorov commented Mar 25, 2024

@thewtex thank you for working on this!

I will let @pieper to re-review and approve this.

@fedorov
Copy link
Member

fedorov commented Mar 25, 2024

@thewtex I believe we discussed it at the last meeting that ITK used in Slicer (and in dcmqi) will be updated after ITK upstream update. Is this the plan, or should that be handled separately?

@thewtex
Copy link
Member Author

thewtex commented Mar 25, 2024

@thewtex I believe we discussed it at the last meeting that ITK used in Slicer (and in dcmqi) will be updated after ITK upstream update. Is this the plan, or should that be handled separately?

I think @jcfr knows the ITK/Slicer update plans.

This patch will be included in ITK 5.4rc3, tentatively this week.

@thewtex thewtex merged commit ac1ebe6 into InsightSoftwareConsortium:master Mar 26, 2024
12 checks passed
@thewtex
Copy link
Member Author

thewtex commented Mar 26, 2024

Merging for the 5.4rc3 release.

@seanm
Copy link
Contributor

seanm commented Mar 28, 2024

@thewtex I've just bisected f545dd8 as the cause of a test failure in our application. We have some tests that write out DICOM files and compare the result to known-good DICOM files, and the comparison now fails. I haven't yet investigated deeper than doing the bisect. But just FYI...

@jcfr
Copy link
Contributor

jcfr commented Mar 28, 2024

I think @jcfr knows the ITK/Slicer update plans.

Ditto 🚀 I will prepare the upgrade (ITK, SimpleITK and DCMTK) but will wait to hear back from @seanm before moving forward with the integration.

@malaterre
Copy link
Member

malaterre commented Mar 29, 2024

@thewtex I've just bisected f545dd8 as the cause of a test failure in our application. We have some tests that write out DICOM files and compare the result to known-good DICOM files, and the comparison now fails. I haven't yet investigated deeper than doing the bisect. But just FYI...

Could you include more context. What is the test supposed to validate? Make sure to read DICOM CP 2330 and cc me if regression remains. thanks

@thewtex
Copy link
Member Author

thewtex commented Apr 3, 2024

@seanm as @malaterre mentioned, there may be differences in output spatial metadata per DICOM CP 2330, but these are expected.

@seanm
Copy link
Contributor

seanm commented Apr 5, 2024

So my coworker has reduced it to a small test case. Attached here: repro-case.zip

For some DICOM files at least, itk::GDCMImageIO is giving different results for spacing before and after this change.

With 193a2ed (aka f545dd8~1) the test app outputs:

Origin = [0, 0, 0]
Spacing = [0.5, 0.5, 1]
Size = [3, 4, 3]

With f545dd8 it outputs:

Origin = [0, 0, 0]
Spacing = [1, 1, 1]
Size = [3, 4, 3]

@issakomi
Copy link
Member

issakomi commented Apr 5, 2024

So my coworker has reduced it to a small test case. Attached here: repro-case.zip

For some DICOM files at least, itk::GDCMImageIO is giving different results for spacing before and after this change.

With 193a2ed (aka f545dd8~1) the test app outputs:

Origin = [0, 0, 0]
Spacing = [0.5, 0.5, 1]
Size = [3, 4, 3]

With f545dd8 it outputs:

Origin = [0, 0, 0]
Spacing = [1, 1, 1]
Size = [3, 4, 3]

S. here:

  case MediaStorage::SecondaryCaptureImageStorage:
    if( ImageHelper::SecondaryCaptureImagePlaneModule ) {
      // Make SecondaryCaptureImagePlaneModule act as ForcePixelSpacing
      // This is different from Basic Pixel Spacing Calibration Macro Attributes
      t = Tag(0x0028,0x0030);
    } else {
      t = Tag(0x0018,0x2010);
    }
    break;

This change was not in the first PR malaterre/GDCM#158 (and not in the PR malaterre/GDCM#167) and the issue wouldn't happen with the PRs, it appears in the very final version.

@thewtex
Copy link
Member Author

thewtex commented Apr 9, 2024

@seanm thanks for checking and providing the reproducible example data.

@issakomi thanks for the investigation.

@dclunie can you confirm that this is the expected output?

@thewtex thewtex deleted the gdcm-sc-image-metadata branch April 9, 2024 19:48
@seanm
Copy link
Contributor

seanm commented Apr 9, 2024

The DICOM files I provided were created by our app (via ITK, via GDCM), and the intended spacing when they were created was 0.5. So unless there is/was also a bug when writing files, the newer reader behaviour seems wrong to me.

@dclunie
Copy link

dclunie commented Apr 10, 2024

@issakomi given that your test set contains Nominal Scanned Pixel Spacing (0018,2010) only (with a 0.5mm value) and no other spacing related attributes, I would say the correct behavior for that test set is to return 0.5.

I assume that a return value of 1 is the "default" (which I am not excited about since there is no reason it should be assumed to be 1 or anything else ... I might have used 0 to force a failure if it is used).

Same goes for the third spacing value - there is no reason it should be 1 (and not, for example, 0) since there is no between slices spacing information in your test files.

This has nothing to do with the case when Image Position (Patient), Image Orientation (Patient) and Pixel Spacing (0028,0030) are present, which would then override any Nominal Scanned Pixel Spacing (0018,2010) present (which ideally it would not be in that situation).

FYI, the optional presence of the ImagePlane Module and accompanying note about relationship of spacing attributes is now standardized.

@issakomi
Copy link
Member

@issakomi given that your test set contains Nominal Scanned Pixel Spacing (0018,2010) only (with a 0.5mm value) and no other spacing related attributes, I would say the correct behavior for that test set is to return 0.5.

Thank you. Yes, in fact the dataset only has a Nominal Scanned Pixel Spacing (0018,2010) and this attribute is ignored. This is erroneous.

This has nothing to do with the case when Image Position (Patient), Image Orientation (Patient) and Pixel Spacing (0028,0030) are present, which would then override any Nominal Scanned Pixel Spacing (0018,2010) present (which ideally it would not be in that situation).

This was proposed by malaterre/GDCM#158 and malaterre/GDCM#167. I am not sure why the final version is different.

thewtex added a commit to thewtex/ITK that referenced this pull request Apr 11, 2024
This updates the Lily.mha baseline to including spacing data and adds a
new DICOM Secondary Capture input and check for an output spacing for 0.5, 0.5 instead
of 1.0, 1.0 per the discussion in
InsightSoftwareConsortium#4521.

Co-authored-by: Sean McBride <[email protected]>
@thewtex
Copy link
Member Author

thewtex commented Apr 11, 2024

A follow-up can be found:

This was proposed by malaterre/GDCM#158 and malaterre/GDCM#167. I am not sure why the final version is different.

I am not sure, but there could be a reasonable attempt to make the function ImageHelper::GetSpacingTagFromMediaStorage do the work of returning the spacing tag for secondary capture, according to its name. Since the logic required makes it insufficient to return a single tag, I added a warning to flag that this function should not be called with Secondary Capture images. This could be elevated to throwing an exception if desired.

Thank you, @seanm, for finding and reporting the regression and providing the reproducible example. This has been added to the test suite.

Thank you, @issakomi , @malaterre , @pieper , for working on the patches to improve DICOM support.

Thank you, @dclunie, @fedorov for working on clarification and improvements to the standard. This has been a long-time thorn and impediment for DICOM adoption. I am glad we are making progress.

Please review the patch by Monday: the community is understandably anxious to see the ITK 5.4 release minted, and we want to get it out.

hjmjohnson pushed a commit that referenced this pull request Apr 12, 2024
This updates the Lily.mha baseline to including spacing data and adds a
new DICOM Secondary Capture input and check for an output spacing for 0.5, 0.5 instead
of 1.0, 1.0 per the discussion in
#4521.

Co-authored-by: Sean McBride <[email protected]>
@seanm
Copy link
Contributor

seanm commented Apr 12, 2024

Thank you, @seanm, for finding and reporting the regression and providing the reproducible example. This has been added to the test suite.

We've tried git master and we're back to the same behaviour as ITK 5.3. Thanks for fixing that!

However like @dclunie said:

there is no between slices spacing information in your test files

Indeed our files have neither 0018|0050 nor 0018|0088. We created those DICOM files via ITK/GDCM. What did we fail to do to have them?

Same goes for the third spacing value - there is no reason it should be 1 (and not, for example, 0)

We agree 0.0 would be a better fallback, because 1.0 mm is a very common scan thickness, and one might think it's correct.

@thewtex
Copy link
Member Author

thewtex commented Apr 14, 2024

@seanm thanks for verifying!

The tag used for that dataset is Nominal Scanned Pixel Spacing (0018,2010).

Setting the spacing to 0.0 would not be correct. Assigning an invalid default value to the spacing would be a hack approach to indicate that the spacing is not explicitly defined in the DICOM metadata. If the desire is to check whether spacing is explicitly defined, then it should be done at the application level, or an ivar on the DICOM ImageIO class to return a boolean that indicates as much. Or, use a different format that consistently and explicitly encodes spacing. A spacing of 1.0 is a reasonable default, will not result in Nan's and Inf's in analysis, and is consistent with the behavior of other ITK image IO interfaces.

@@ -454,6 +456,8 @@ GDCMImageIO::InternalReadImageInformation()

// In general this should be relatively safe to assume
gdcm::ImageHelper::SetForceRescaleInterceptSlope(true);
// Secondary capture image orientation patient and image position patient support
gdcm::ImageHelper::SetSecondaryCaptureImagePlaneModule(true);
Copy link
Member

@issakomi issakomi Apr 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thewtex Maybe something like

#if (!defined(ITK_USE_SYSTEM_GDCM) || ((GDCM_MAJOR_VERSION == 3 && GDCM_MINOR_VERSION == 0 && GDCM_BUILD_VERSION > 23) || GDCM_MAJOR_VERSION > 3))
#endif

around SetSecondaryCaptureImagePlaneModule? Otherwise, it may cause build failures using external GDCM. There is currently no version of GDCM with this feature.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@issakomi good idea, added here: #4601

pieper added a commit to pieper/Slicer that referenced this pull request Oct 8, 2024
Fixes Slicer#7937

See original report here about a dataset being scrambled that loaded well
in the 5.6.2 release:

https://discourse.slicer.org/t/regression-in-the-dicom-data-base/37913

This corresponds to the change in behavior described here, where spacing
from ITK that used to be 1 is now, for example 5 or -1:

InsightSoftwareConsortium/ITK#4794

Which is believed to be due to the changes here, which was
added so that for Secondary Capture files the spacing would
be respected if present:

InsightSoftwareConsortium/ITK#4521

However adding this code in GDCM meant that if the SpacingBetweenSlices
tag is present, even in a CT, it is being used by ITK to calculate
spacing, and also ITKToRAS transforms when trying to orthogonalize
the transform.

Since Slicer doesn't rely on orthogonal IJKToRAS transforms, this change
tells ITK to skip that step and instead it relies on the positions
and orientations of the slices to calculate the IJKToRAS transform, which
is compatible with what Slicer expects.

This code was tested on both the CT scan with the negative spacing that
was reported in the original issue, and on other CT cans without that
tag and the geometry matches what was obtained in 5.6.2.

This change was discussed in the Slicer developer meeting 2024-10-08 and
determined to be the best course of action.  Further fixes in GDCM or
ITK were not pursued because it was unclear whta the correct behavior
should be at the library level considering that a negative spacing
between slices is technically invalid for CT scans according to
the DICOM standard.
pieper added a commit to Slicer/Slicer that referenced this pull request Oct 8, 2024
Fixes #7937

See original report here about a dataset being scrambled that loaded well
in the 5.6.2 release:

https://discourse.slicer.org/t/regression-in-the-dicom-data-base/37913

This corresponds to the change in behavior described here, where spacing
from ITK that used to be 1 is now, for example 5 or -1:

InsightSoftwareConsortium/ITK#4794

Which is believed to be due to the changes here, which was
added so that for Secondary Capture files the spacing would
be respected if present:

InsightSoftwareConsortium/ITK#4521

However adding this code in GDCM meant that if the SpacingBetweenSlices
tag is present, even in a CT, it is being used by ITK to calculate
spacing, and also ITKToRAS transforms when trying to orthogonalize
the transform.

Since Slicer doesn't rely on orthogonal IJKToRAS transforms, this change
tells ITK to skip that step and instead it relies on the positions
and orientations of the slices to calculate the IJKToRAS transform, which
is compatible with what Slicer expects.

This code was tested on both the CT scan with the negative spacing that
was reported in the original issue, and on other CT cans without that
tag and the geometry matches what was obtained in 5.6.2.

This change was discussed in the Slicer developer meeting 2024-10-08 and
determined to be the best course of action.  Further fixes in GDCM or
ITK were not pursued because it was unclear whta the correct behavior
should be at the library level considering that a negative spacing
between slices is technically invalid for CT scans according to
the DICOM standard.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:IO Issues affecting the IO module area:ThirdParty Issues affecting the ThirdParty module type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants