public void MapVolumeMetadata_SuccessWithIncompleteMetadata()
        {
            int numLabels = 5;

            var labels = VolumeMetadataMapper.MultiLabelMapping(sourceVolume, numLabels);

            var structureNames  = new [] { "struct1", "struct2" };
            var structureColors = (new [] { "1,2,3", "4,65,  8", "12, 22,510" }).Select(ParseColorOption).ToArray();
            var fillHoles       = (new [] { "true", "True" }).Select(ParseBoolOption).ToArray();

            var volumesWithMetadata = VolumeMetadataMapper.MapVolumeMetadata(labels, structureNames, structureColors, fillHoles, ROIInterpretedTypes);

            Assert.AreEqual(labels.Length, volumesWithMetadata.Count());

            var expectedStructureNames = structureNames.Concat(
                new[] {
                VolumeMetadataMapper.DefaultStructureName.Invoke(1),
                VolumeMetadataMapper.DefaultStructureName.Invoke(2),
                VolumeMetadataMapper.DefaultStructureName.Invoke(3)
            }).ToArray();

            var expectedStructureColors = new[] { new RGBColor(1, 2, 3), new RGBColor(4, 65, 8), new RGBColor(12, 22, 0),
                                                  VolumeMetadataMapper.DefaultStructureColor, VolumeMetadataMapper.DefaultStructureColor }.ToArray();

            var expectedFillHoles = new[] { true, true,
                                            VolumeMetadataMapper.DefaultFillHoles, VolumeMetadataMapper.DefaultFillHoles, VolumeMetadataMapper.DefaultFillHoles }.ToArray();

            for (var i = 0; i < numLabels; i++)
            {
                Assert.AreEqual(expectedStructureNames[i], volumesWithMetadata.ElementAt(i).name);
                Assert.AreEqual(expectedStructureColors[i], volumesWithMetadata.ElementAt(i).color);
                Assert.AreEqual(expectedFillHoles[i], volumesWithMetadata.ElementAt(i).fillHoles);
            }
        }
        public void MultiLabelVolumeMapping_SuccessWithTooFewRequestedLabels()
        {
            const int LabelsRequested = 2;
            var       labels          = VolumeMetadataMapper.MultiLabelMapping(sourceVolume, LabelsRequested);

            Assert.AreEqual(labels.Length, LabelsRequested);

            Assert.AreNotEqual(CountAndValidateVoxelsInLabel(labels[0]), 0);
            Assert.AreNotEqual(CountAndValidateVoxelsInLabel(labels[1]), 0);
        }
        public void MapVolumeMetadata_SuccessWithExtraMetadata()
        {
            int numLabels = 5;

            var labels           = VolumeMetadataMapper.MultiLabelMapping(sourceVolume, numLabels);
            var labelVoxelCounts = labels.AsParallel().Select(l => CountAndValidateVoxelsInLabel(l)).ToArray();

            var volumesWithMetadata      = VolumeMetadataMapper.MapVolumeMetadata(labels, StructureNames, StructureColors, FillHoles, ROIInterpretedTypes);
            var mappedVolumesVoxelCounts = volumesWithMetadata.AsParallel().Select(l => CountAndValidateVoxelsInLabel(l.volume)).ToArray();

            Assert.AreEqual(numLabels, volumesWithMetadata.Count());

            for (int i = 0; i < labelVoxelCounts.Count(); i++)
            {
                Assert.AreEqual(labelVoxelCounts[i], mappedVolumesVoxelCounts[i]);
            }
        }
        public void MultiLabelVolumeMapping_SuccessWithValidInputs()
        {
            var labels = VolumeMetadataMapper.MultiLabelMapping(sourceVolume, NumValidLabels);

            Assert.AreEqual(labels.Length, NumValidLabels);
            Parallel.ForEach(labels, (img) =>
            {
                Assert.AreEqual(img.DimX, sourceVolume.DimX);
                Assert.AreEqual(img.DimY, sourceVolume.DimY);
                Assert.AreEqual(img.DimZ, sourceVolume.DimZ);

                Assert.AreEqual(img.SpacingX, sourceVolume.SpacingX);
                Assert.AreEqual(img.SpacingY, sourceVolume.SpacingY);
                Assert.AreEqual(img.SpacingZ, sourceVolume.SpacingZ);

                Assert.AreNotEqual(CountAndValidateVoxelsInLabel(img), 0);
            }
                             );
        }
        public void MapVolumeMetadata_SuccessWithValidInputs()
        {
            var labels = VolumeMetadataMapper.MultiLabelMapping(sourceVolume, NumValidLabels);
            // we will use voxel counts as a hash for our volumes
            var labelVoxelCounts = labels.AsParallel().Select(l => CountAndValidateVoxelsInLabel(l)).ToArray();

            var volumesWithMetadata = VolumeMetadataMapper.MapVolumeMetadata(labels, StructureNames, StructureColors, FillHoles, ROIInterpretedTypes);
            // pre-compute voxel counts in parallel
            var mappedVolumesVoxelCounts = volumesWithMetadata.AsParallel().Select(l => CountAndValidateVoxelsInLabel(l.volume)).ToArray();

            Assert.AreEqual(labels.Length, volumesWithMetadata.Count());

            for (int i = 0; i < NumValidLabels; i++)
            {
                Assert.AreEqual(labelVoxelCounts.ElementAt(i), mappedVolumesVoxelCounts.ElementAt(i));
                Assert.AreEqual(StructureNames[i], volumesWithMetadata.ElementAt(i).name);

                var expectedStructureColor = StructureColors[i].Value.ApplyDefault(null);
                Assert.AreEqual(expectedStructureColor, volumesWithMetadata.ElementAt(i).color);
                Assert.AreEqual(FillHoles[i], volumesWithMetadata.ElementAt(i).fillHoles);
            }
        }
        public void RtStructOutputEncoder_SuccessWithValidInputs()
        {
            var labels = VolumeMetadataMapper.MultiLabelMapping(sourceVolume, NumValidLabels);
            var volumesWithMetadata = VolumeMetadataMapper.MapVolumeMetadata(labels, StructureNames, StructureColors, FillHoles, ROIInterpretedTypes);

            var referenceVolume = DicomSeriesHelpers.LoadVolume(DicomFolderContents.Build(
                                                                    Directory.EnumerateFiles(TestDicomVolumeLocation).Select(x => DicomFileAndPath.SafeCreate(x)).ToList()
                                                                    ));

            var outputEncoder = new DicomRTStructOutputEncoder();

            var outputStructureBytes = outputEncoder.EncodeStructures(
                volumesWithMetadata,
                new Dictionary <string, MedicalVolume>()
            {
                { "", referenceVolume }
            },
                "modelX:1",
                "manufacturer",
                "interpreter");

            var dcm = DicomFile.Open(new MemoryStream(outputStructureBytes.Array));

            // Check the output format (should be RT struct)
            Assert.AreEqual(DicomUID.RTStructureSetStorage, dcm.FileMetaInfo.MediaStorageSOPClassUID);

            // Check stuff in StructureSet ROI sequence
            var structSetRois = dcm.Dataset.GetSequence(DicomTag.StructureSetROISequence).Items;
            var iter          = StructureNames.GetEnumerator();

            iter.MoveNext();

            var origReferencedFrameOfReference = referenceVolume.Identifiers.First().FrameOfReference.FrameOfReferenceUid;

            foreach (var roi in structSetRois)
            {
                // Verify that names in the generated DICOM Rt structure are the ones we've supplied
                Assert.AreEqual(iter.Current, roi.GetString(DicomTag.ROIName));
                iter.MoveNext();

                // Verify that this roi references the same frame of reference as the original image
                Assert.AreEqual(roi.GetString(DicomTag.ReferencedFrameOfReferenceUID), origReferencedFrameOfReference);
            }

            // Check stuff in ROI Contour sequence
            var roiContours = dcm.Dataset.GetSequence(DicomTag.ROIContourSequence).Items;
            var iterColors  = 0;

            var sopInstanceUIDs = referenceVolume.Identifiers.Select(x => x.Image.SopCommon.SopInstanceUid);

            foreach (var contourSequence in roiContours)
            {
                // Verify that colors in the generated contour sequence are the ones we've supplied
                var currentColor       = StructureColors[iterColors].Value;
                var currentColorString = string.Format("{0}\\{1}\\{2}", currentColor.R, currentColor.G, currentColor.B);

                Assert.AreEqual(contourSequence.GetString(DicomTag.ROIDisplayColor), currentColorString);
                iterColors++;

                // Verify that all contour types are closed planar
                Assert.IsTrue(contourSequence.GetSequence(DicomTag.ContourSequence).Items.All(
                                  x => x.GetString(DicomTag.ContourGeometricType) == "CLOSED_PLANAR"));

                // Verify that for all contours there exists a SOP Instance UID in the original series
                Assert.IsTrue(contourSequence.GetSequence(DicomTag.ContourSequence).Items.All(
                                  x => sopInstanceUIDs.Contains(x.GetSequence(DicomTag.ContourImageSequence).Items[0].GetString(DicomTag.ReferencedSOPInstanceUID))));
            }
        }
예제 #7
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}");
            }
        }