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