public void LoadImageAndContours()
        {
            var acceptanceTests = new ModerateGeometricAcceptanceTest(string.Empty, string.Empty);
            var medicalVolume   = MedIO.LoadAllDicomSeriesInFolderAsync(TestData.GetFullImagesPath(@"sample_dicom"), acceptanceTests).Result.First().Volume;

            var volumes = new Dictionary <string, ContourStatistics>()
            {
                { "Bladder", new ContourStatistics(95.87, -1000.0, 0) },
                { "Femur_L", new ContourStatistics(205.04, -1000.0, 0) },
            };

            var readOnlyVolume = new ReadOnlyVolume3D <short>(medicalVolume.Volume);

            for (int i = 0; i < medicalVolume.Struct.Contours.Count; i++)
            {
                var contour       = medicalVolume.Struct.Contours[i];
                var name          = contour.StructureSetRoi.RoiName;
                var contourVolume = contour.Contours.ToVolume3D(medicalVolume.Volume);
                Console.WriteLine($"Starting checks for contour {name}");
                var contourStatistics = ContourStatistics.FromVolumeAndMask(readOnlyVolume, contourVolume);
                Assert.AreEqual(volumes[contour.StructureSetRoi.RoiName].SizeInCubicCentimeters, contourStatistics.SizeInCubicCentimeters, 1e-1, $"Size for contour {name}");
                Assert.AreEqual(volumes[contour.StructureSetRoi.RoiName].VoxelValueMean, contourStatistics.VoxelValueMean, 1e-1, $"Mean for contour {name}");
                Assert.AreEqual(volumes[contour.StructureSetRoi.RoiName].VoxelValueStandardDeviation, contourStatistics.VoxelValueStandardDeviation, 1e-1, $"Std for contour {name}");
            }
        }
        public void LoadMedicalVolumeTest()
        {
            var dir             = TestData.GetFullImagesPath(@"sample_dicom");
            var acceptanceTests = new ModerateGeometricAcceptanceTest(string.Empty, string.Empty);
            var medicalVolumes  = MedIO.LoadAllDicomSeriesInFolderAsync(dir, acceptanceTests).Result;

            Assert.AreEqual(1, medicalVolumes.Count);

            var medicalVolume = medicalVolumes.First().Volume;

            Assert.IsTrue(medicalVolume.Struct.Contours.Any(x => x.StructureSetRoi.RoiName == "Bladder"));
            Assert.IsTrue(medicalVolume.Struct.Contours.Any(x => x.StructureSetRoi.RoiName == "Femur_L"));

            RtStructWriter.SaveRtStruct(_tempfile, medicalVolume.Struct);

            DicomFile file = DicomFile.Open(_tempfile);

            var identifiers = medicalVolume.Identifiers.First();

            var reloaded = RtStructReader.LoadContours(
                file.Dataset,
                medicalVolume.Volume.Transform.DicomToData,
                identifiers.Series.SeriesInstanceUid,
                identifiers.Study.StudyInstanceUid).Item1;

            Assert.IsTrue(reloaded.Contours.Any(x => x.StructureSetRoi.RoiName == "Bladder"));
            Assert.IsTrue(reloaded.Contours.Any(x => x.StructureSetRoi.RoiName == "Femur_L"));
        }
        public void TestVolumeInformationValidation()
        {
            var dicomDatasets  = CreateValidDicomDatasetVolume(5, 5, 5, 1, 1, 3, new Point3D(), DicomUID.CTImageStorage, 16);
            var acceptanceTest = new ModerateGeometricAcceptanceTest("Blah1", "Blah2");

            // Valid DICOM slice.
            DicomSeriesInformationValidator.ValidateVolumeInformation(
                VolumeInformation.Create(dicomDatasets),
                acceptanceTest,
                new[] { dicomDatasets[0].InternalTransferSyntax });

            // Inconsistent slice information.
            var dicomDatasets2 = CreateValidDicomDatasetVolume(5, 5, 5, 1, 1, 3, new Point3D(), DicomUID.CTImageStorage, 16);

            dicomDatasets2[dicomDatasets2.Length - 2].AddOrUpdate(new DicomUnsignedShort(DicomTag.Rows, 234));
            var exception = Assert.Throws <ArgumentException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation(
                                                                  VolumeInformation.Create(dicomDatasets2),
                                                                  acceptanceTest));

            Assert.IsTrue(exception.Message.Contains("Slice at position '9' has an inconsistent height. Expected: '5', Actual: '234'."));

            // Invalid supported transfer syntax
            Assert.Throws <ArgumentException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation(
                                                  VolumeInformation.Create(dicomDatasets),
                                                  acceptanceTest,
                                                  new[] { DicomTransferSyntax.DeflatedExplicitVRLittleEndian }));

            // Failing acceptance test
            Assert.Throws <ArgumentException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation(
                                                  VolumeInformation.Create(dicomDatasets),
                                                  new FailingAcceptanceTest()));

            // Exception testing
            Assert.Throws <ArgumentNullException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation(
                                                      VolumeInformation.Create(dicomDatasets),
                                                      null));

            Assert.Throws <ArgumentNullException>(() => DicomSeriesInformationValidator.ValidateVolumeInformation(
                                                      null,
                                                      acceptanceTest));
        }
        /// <summary>
        /// Loads a medical volume from a set of Dicom files, including the RT structures if present.
        /// The Dicom files are expected to contain a single Dicom Series.
        /// </summary>
        /// <param name="dicomFileAndPath">The Dicom files to load from</param>
        /// <param name="maxPixelSizeRatioMR">The maximum allowed aspect ratio for pixels, if the volume is an MR scan.</param>
        /// <returns></returns>
        /// <exception cref="InvalidDataException">If the volume cannot be loaded from the Dicom files</exception>
        public static MedicalVolume MedicalVolumeFromDicom(IReadOnlyList <DicomFileAndPath> dicomFileAndPath,
                                                           double maxPixelSizeRatioMR = ModerateGeometricAcceptanceTest.DefaultMaxPixelSizeRatioMR)
        {
            var acceptanceTest     = new ModerateGeometricAcceptanceTest("Non Square pixels", "Unsupported Orientation", maxPixelSizeRatioMR);
            var volumeLoaderResult = MedIO.LoadAllDicomSeries(
                DicomFolderContents.Build(dicomFileAndPath),
                acceptanceTest,
                loadStructuresIfExists: true,
                supportLossyCodecs: false);

            if (volumeLoaderResult.Count != 1)
            {
                throw new InvalidDataException($"Unable to load the scan from the Dicom files: There should be exactly 1 series, but got {volumeLoaderResult.Count}.");
            }

            var result = volumeLoaderResult[0];

            if (result.Volume == null)
            {
                throw new InvalidDataException("An exception was thrown trying to load the scan from the Dicom files.", result.Error);
            }

            return(result.Volume);
        }
Beispiel #5
0
        /// <summary>
        /// Test Nifti file to DICOM-RT translation.
        /// </summary>
        /// <param name="niftiFilename">Source Nifti file.</param>
        /// <param name="referenceSeries">Reference DICOM series folder.</param>
        /// <param name="structureNames">List of structure names for DICOM-RT.</param>
        /// <param name="structureColors">List of structure colours for DICOM-RT.</param>
        /// <param name="fillHoles">List of fill hole flags for DICOM-RT.</param>
        /// <param name="roiInterpretedType">List of roiInterpretedType for DICOM-RT.</param>
        /// <param name="debugFolderName">If present, create a full set of debug images.</param>
        /// <param name="testVolumesMatch">If true, check the volumes match.</param>
        /// <param name="expectedNiftiFilename">Expect volume to match Nifti file.</param>
        public static void DoTestNiftiToDicom(
            string niftiFilename,
            string referenceSeries,
            string[] structureNames,
            RGBColorOption?[] structureColors,
            bool?[] fillHoles,
            ROIInterpretedType[] roiInterpretedTypes,
            string debugFolderName,
            bool testVolumesMatch,
            string expectedNiftiFilename)
        {
            var outputFileName = Path.GetRandomFileName() + ".dcm";

            RTConverters.ConvertNiftiToDicom(
                niftiFilename,
                referenceSeries,
                structureNames,
                structureColors,
                fillHoles,
                roiInterpretedTypes,
                outputFileName,
                "modelX:1",
                "manufacturer",
                "interpreter");

            // Open the newly created DICOM-RT file
            var dicomRTFile = DicomFile.Open(outputFileName);

            // Get the medical volume from the reference
            var acceptanceTests         = new ModerateGeometricAcceptanceTest(string.Empty, string.Empty);
            var referenceMedicalVolumes = MedIO.LoadAllDicomSeriesInFolderAsync(referenceSeries, acceptanceTests).Result;

            Assert.AreEqual(1, referenceMedicalVolumes.Count);

            var referenceMedicalVolume = referenceMedicalVolumes.First().Volume;

            var referenceIdentifiers = referenceMedicalVolume.Identifiers.First();

            // Extract the RTStruct from the DICOM-RT file
            var reloaded = RtStructReader.LoadContours(
                dicomRTFile.Dataset,
                referenceMedicalVolume.Volume.Transform.DicomToData,
                referenceIdentifiers.Series.SeriesInstanceUid,
                referenceIdentifiers.Study.StudyInstanceUid);

            Assert.IsNotNull(reloaded);

            var reloadedRTStruct = reloaded.Item1;

            Assert.AreEqual(referenceIdentifiers.Patient.Id, reloadedRTStruct.Patient.Id);
            Assert.AreEqual(referenceIdentifiers.Study.StudyInstanceUid, reloadedRTStruct.Study.StudyInstanceUid);
            Assert.AreEqual(DicomRTSeries.RtModality, reloadedRTStruct.RTSeries.Modality);

            // Load the nifti file
            var sourceVolume = MedIO.LoadNiftiAsByte(expectedNiftiFilename);

            // Split this volume from segment id to a set of mask volumes
            var labels = VolumeMetadataMapper.MultiLabelMapping(sourceVolume, structureNames.Length);

            // Make tuples of mask volume, color, names, fill holes.
            var volumesWithMetadata = VolumeMetadataMapper.MapVolumeMetadata(labels, structureNames, structureColors, fillHoles, roiInterpretedTypes).ToArray();

            Assert.AreEqual(structureNames.Length, reloadedRTStruct.Contours.Count);

            for (int i = 0; i < reloadedRTStruct.Contours.Count; i++)
            {
                var contourVolumes = new Volume3D <byte>(
                    referenceMedicalVolume.Volume.DimX,
                    referenceMedicalVolume.Volume.DimY,
                    referenceMedicalVolume.Volume.DimZ,
                    referenceMedicalVolume.Volume.SpacingX,
                    referenceMedicalVolume.Volume.SpacingY,
                    referenceMedicalVolume.Volume.SpacingZ,
                    referenceMedicalVolume.Volume.Origin,
                    referenceMedicalVolume.Volume.Direction);

                contourVolumes.Fill(reloadedRTStruct.Contours[i].Contours, (byte)1);

                var v = volumesWithMetadata[i];

                if (!string.IsNullOrWhiteSpace(debugFolderName))
                {
                    for (var z = 0; z < referenceMedicalVolume.Volume.DimZ; z++)
                    {
                        var actualFileName = Path.Combine(debugFolderName, "actual", $"slice_{v.name}_{z}.png");
                        var actualSlice    = contourVolumes.Slice(SliceType.Axial, z);
                        actualSlice.SaveBinaryMaskToPng(actualFileName);

                        var expectedFileName = Path.Combine(debugFolderName, "expected", $"slice_{v.name}_{z}.png");
                        var expectedSlice    = v.volume.Slice(SliceType.Axial, z);
                        expectedSlice.SaveBinaryMaskToPng(expectedFileName);
                    }
                }

                if (testVolumesMatch)
                {
                    VolumeAssert.AssertVolumesMatch(v.volume, contourVolumes, $"Loaded mask {i}");
                }

                Assert.AreEqual(v.name, reloadedRTStruct.Contours[i].StructureSetRoi.RoiName, $"Loaded mask name {i}");

                var expectedColor = Tuple.Create(v.color.R, v.color.G, v.color.B);
                Assert.AreEqual(expectedColor, reloadedRTStruct.Contours[i].DicomRtContour.RGBColor, $"Loaded color {i}");
            }
        }