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 static Tuple <RadiotherapyStruct, string> LoadStruct(string rtfile, Transform3 dicomToData, string studyUId, string seriesUId) { try { var file = DicomFile.Open(rtfile); return(RtStructReader.LoadContours(file.Dataset, dicomToData, seriesUId, studyUId, true)); } catch (Exception ex) { throw new ArgumentException($"RT file {rtfile} cannot be loaded - {ex.Message}"); } }
/// <summary> /// Attempt to load a volume from the given SeriesUID for the given DicomFolderContents /// </summary> /// <param name="dfc">A pre-built description of DICOM contents within a particular folder</param> /// <param name="seriesUID">The DICOM seriesUID you wish to construct a volume for</param> /// <param name="acceptanceTests">An implementation of IVolumeGeometricAcceptanceTest defining the geometric constraints of your application</param> /// <param name="loadStructuresIfExists">True if rt-structures identified in the folder and referencing seriesUID should be loaded</param> /// <param name="supportLossyCodecs">If you wish to accept lossy encodings of image pixel data</param> /// <returns></returns> private static VolumeLoaderResult LoadDicomSeries( DicomFolderContents dfc, DicomUID seriesUID, IVolumeGeometricAcceptanceTest acceptanceTests, bool loadStructuresIfExists, bool supportLossyCodecs) { try { var dicomSeriesContent = dfc.Series.FirstOrDefault((s) => s.SeriesUID == seriesUID); var warnings = new List <string>(); RadiotherapyStruct rtStruct = null; if (dicomSeriesContent != null) { var volumeData = DicomSeriesReader.BuildVolume(dicomSeriesContent.Content.Select(x => x.File.Dataset), acceptanceTests, supportLossyCodecs); if (volumeData != null && loadStructuresIfExists) { var rtStructData = dfc.RTStructs.FirstOrDefault(rt => rt.SeriesUID == seriesUID); if (rtStructData != null) { if (rtStructData.Content.Count == 1) { var rtStructAndWarnings = RtStructReader.LoadContours( rtStructData.Content.First().File.Dataset, volumeData.Transform.DicomToData, seriesUID.UID, null, false); rtStruct = rtStructAndWarnings.Item1; var warning = rtStructAndWarnings.Item2; if (!string.IsNullOrEmpty(warning)) { warnings.Add(warning); } } else if (rtStructData.Content.Count > 1) { warnings.Add("There is more than 1 RT STRUCT referencing this series - skipping structure set load"); } } } var dicomIdentifiers = dicomSeriesContent.Content.Select((v) => DicomIdentifiers.ReadDicomIdentifiers(v.File.Dataset)).ToArray(); if (rtStruct == null) { rtStruct = RadiotherapyStruct.CreateDefault(dicomIdentifiers); } var result = new MedicalVolume( volumeData, dicomIdentifiers, dicomSeriesContent.Content.Select((d) => d.Path).ToArray(), rtStruct); return(new VolumeLoaderResult(seriesUID.UID, result, null, warnings)); } throw new Exception("Could not find that series"); } catch (Exception oops) { return(new VolumeLoaderResult(seriesUID.UID, null, oops, new List <string>())); } }
public void VolumeToDicomAndBack() { var outputFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "DicomOutput"); if (Directory.Exists(outputFolder)) { Directory.Delete(outputFolder, recursive: true); Thread.Sleep(1000); } Directory.CreateDirectory(outputFolder); var scan = new Volume3D <short>(5, 5, 5); foreach (var index in scan.Array.Indices()) { scan.Array[index] = (short)index; } // Create 3 structures, each with a different color var masks = new List <ContourRenderingInformation>(); var colors = new[] { new RGBColor(255, 0, 0), new RGBColor(0, 255, 0), new RGBColor(0, 0, 255), }; foreach (var index in Enumerable.Range(0, 3)) { var mask = scan.CreateSameSize <byte>(); mask[index + 1, index + 1, index + 1] = 1; masks.Add(new ContourRenderingInformation($"structure_{index}", colors[index], mask)); } var seriesDescription = "description"; var patientId = DicomUID.Generate().UID; var studyId = DicomUID.Generate().UID; var dicomFiles = NiiToDicomHelpers.ScanAndContoursToDicom(scan, ImageModality.CT, masks, seriesDescription, patientId, studyId); // Write to disk, so that we can load it into the App as well var dicomFilesOnDisk = new List <string>(); foreach (var dicomFile in dicomFiles) { dicomFilesOnDisk.Add(dicomFile.SaveToFolder(outputFolder)); } // Test if the first returned Dicom file is really the RTStruct var rtStructFromFile = RtStructReader.LoadContours(dicomFilesOnDisk[0], scan.Transform.DicomToData); Assert.IsNotNull(rtStructFromFile); Assert.AreEqual(masks.Count, rtStructFromFile.Item1.Contours.Count); var fromDisk = NiiToDicomHelpers.MedicalVolumeFromDicomFolder(outputFolder); VolumeAssert.AssertVolumesMatch(scan, fromDisk.Volume, "Loaded scan does not match"); Assert.AreEqual(seriesDescription, fromDisk.Identifiers.First().Series.SeriesDescription); Assert.AreEqual(patientId, fromDisk.Identifiers.First().Patient.Id); Assert.AreEqual(studyId, fromDisk.Identifiers.First().Study.StudyInstanceUid); foreach (var index in Enumerable.Range(0, fromDisk.Struct.Contours.Count)) { var loadedMask = fromDisk.Struct.Contours[index].Contours.ToVolume3D(scan); VolumeAssert.AssertVolumesMatch(masks[index].Contour.ToVolume3D(scan), loadedMask, $"Loaded mask {index}"); Assert.AreEqual(masks[index].Name, fromDisk.Struct.Contours[index].StructureSetRoi.RoiName, $"Loaded mask name {index}"); } // Now test if we can ZIP up all the Dicom files, and read them back in. var zippedDicom = ZippedDicom.DicomFilesToZipArchive(dicomFiles); var dicomFromZip = ZippedDicom.DicomFilesFromZipArchive(zippedDicom); var volumeFromZip = NiiToDicomHelpers.MedicalVolumeFromDicom(dicomFromZip.ToList()); VolumeAssert.AssertVolumesMatch(scan, volumeFromZip.Volume, "Scan from Zip archive does not match"); Assert.AreEqual(masks.Count, volumeFromZip.Struct.Contours.Count, "RtStructs from Zip do not match"); }
/// <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}"); } }