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)))); } }
/// <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}"); } }