public static DicomFile GetRtStructFile(RadiotherapyStruct rtStruct) { var file = new DicomFile(); var ds = file.Dataset; // We must use the same UID for SOPInstanceUID & MediaStorageSOPInstanceUID DicomUID sopInstanceUID = DicomUID.Generate(); file.FileMetaInfo.MediaStorageSOPClassUID = DicomUID.RTStructureSetStorage; file.FileMetaInfo.MediaStorageSOPInstanceUID = sopInstanceUID; // Fo-Dicom has a sub-optimal policy for this - using the machine name - we remove this file.FileMetaInfo.Remove(DicomTag.SourceApplicationEntityTitle); // It is very important that we only use ImplicitVRLittleEndian here, otherwise large contours // can exceed the maximum length of a an explicit VR. file.FileMetaInfo.TransferSyntax = DicomTransferSyntax.ImplicitVRLittleEndian; //WRITE INSTANCE UID AND SOP CLASS UID ds.Add(DicomTag.SOPClassUID, DicomUID.RTStructureSetStorage); ds.Add(DicomTag.SOPInstanceUID, sopInstanceUID); RadiotherapyStruct.Write(ds, rtStruct); return(file); }
/// <summary> /// Creates a radiotherapy structure from a set of contours, including the given rendering /// information (name of the contour, color to render in). /// </summary> /// <param name="contours">The contours and their rendering information.</param> /// <param name="dicomIdentifiers">The Dicom identifiers for the scan to which the contours belong.</param> /// <param name="volumeTransform">The Dicom-to-data transformation of the scan to which the contours belong.</param> /// <returns></returns> public static RadiotherapyStruct ContoursToRadiotherapyStruct(IEnumerable <ContourRenderingInformation> contours, IReadOnlyList <DicomIdentifiers> dicomIdentifiers, VolumeTransform volumeTransform) { var radiotherapyStruct = RadiotherapyStruct.CreateDefault(dicomIdentifiers); int roiNumber = 0; var nameConverter = new DicomPersonNameConverter("InnerEye", "CreateDataset", string.Empty, string.Empty, string.Empty); foreach (var contour in contours) { // ROIs need to start at 1 by DICOM spec roiNumber++; // Create contours - mapping each contour into the volume. var radiotherapyContour = RTStructCreator.CreateRadiotherapyContour( contour.Contour, dicomIdentifiers, volumeTransform, contour.Name, (contour.Color.R, contour.Color.G, contour.Color.B), roiNumber.ToString(), nameConverter, ROIInterpretedType.None ); radiotherapyStruct.Contours.Add(radiotherapyContour); } return(radiotherapyStruct); }
/// <summary> /// Returns a unique sequence of unsupported types in the given structure by name /// </summary> /// <param name="contoursData"></param> /// <returns></returns> private static IEnumerable <string> UnsupportedTypes(RadiotherapyStruct rtStruct) { return(rtStruct.Contours.SelectMany( c => c.DicomRtContour.DicomRtContourItems). Where(c => c.GeometricType != DicomExtensions.ClosedPlanarString). Select(c1 => c1.GeometricType). Distinct()); }
/// <summary> /// Loads a medical volume from a Nifti file. The <see cref="MedicalVolume.Volume"/> property /// will be set to the volume in the Nifti file, the RT structures will be empty, empty /// Dicom identifiers. /// </summary> /// <param name="path"></param> /// <returns></returns> public static MedicalVolume LoadMedicalVolumeFromNifti(string path) { var volume = LoadNiftiAsShort(path); return(new MedicalVolume( volume, new DicomIdentifiers[0], new[] { path }, RadiotherapyStruct.CreateDefault(new[] { DicomIdentifiers.CreateEmpty() }))); }
/// <summary> /// Load a RadiotherapyStruct from the given dicom dataset and map into the coordinate of the given Volume3D /// </summary> /// <param name="ds">Dataset to read the structure set from</param> /// <param name="dicomToData">The transform from going between dicom and voxel points.</param> /// <param name="seriesUID">SeriesUID that must match the referenced seriesUID inside the structure set</param> /// <param name="studyUID">The structure set must belong to the same study</param> /// <param name="warningsAsErrors">true if warnings should be treated as errors and thrown from this method.</param> /// <returns>A new RadiotherapyStruct with any warnings collated into a string</returns> public static Tuple <RadiotherapyStruct, string> LoadContours( DicomDataset ds, Transform3 dicomToData, string seriesUID = null, string studyUID = null, bool warningsAsErrors = true) { RadiotherapyStruct rtStruct = RadiotherapyStruct.Read(ds); if (studyUID != null && studyUID != rtStruct.Study.StudyInstanceUid) { var warningText = $"The RT STRUCTURE Set does not belong to this Study"; if (warningsAsErrors) { throw new ArgumentException(warningText); } return(Tuple.Create <RadiotherapyStruct, string>(null, warningText)); } // ReferencedFramesOfRef must contain at least 1 study/series reference if (CheckUnreferencedSeries(rtStruct.StructureSet, seriesUID)) { var warningText = $"The RT STRUCTURE does not reference this Series"; if (warningsAsErrors) { throw new ArgumentException(warningText); } return(Tuple.Create <RadiotherapyStruct, string>(null, warningText)); } var contoursData = rtStruct.Contours; // Do not warn if all structures are empty if (contoursData.Count != 0 && !contoursData.Any(c => CanUseStructData(c.DicomRtContour))) { var warningText = $"The RT STRUCTURE does not contain CLOSED PLANAR contours"; if (warningsAsErrors) { throw new ArgumentException(warningText); } return(Tuple.Create <RadiotherapyStruct, string>(null, warningText)); } var warningTypeText = string.Empty; if (CheckUnsupportedData(rtStruct)) { var badTypes = UnsupportedTypes(rtStruct); warningTypeText = $"The RT STRUCTURE contains unsupported Contour Types: {string.Join(",",badTypes)}"; // remove the parent structures RemoveUnsupportedTypes(rtStruct); } Parallel.ForEach(contoursData, c => CreateStructureContoursBySlice(dicomToData, c)); return(Tuple.Create(rtStruct, warningTypeText)); }
/// <summary> /// Removes all unsupported types in the given struct /// </summary> /// <param name="rtStruct"></param> private static void RemoveUnsupportedTypes(RadiotherapyStruct rtStruct) { // remove the parent structures var invalidContours = rtStruct.Contours.Where( c => c.DicomRtContour.DicomRtContourItems.Any( r => r.GeometricType != DicomExtensions.ClosedPlanarString)).ToList(); foreach (var i in invalidContours) { rtStruct.Contours.Remove(i); } }
/// <summary> /// Construct MedicalVolume from Dicom images /// </summary> /// <param name="volume"></param> /// <param name="identifiers"></param> /// <param name="filePaths"></param> /// <param name="rtStruct"></param> public MedicalVolume( Volume3D <short> volume, IReadOnlyList <DicomIdentifiers> identifiers, IReadOnlyList <string> filePaths, RadiotherapyStruct rtStruct) { Debug.Assert(volume != null); Debug.Assert(identifiers != null); Debug.Assert(filePaths != null); Debug.Assert(rtStruct != null); Identifiers = identifiers; Volume = volume; FilePaths = filePaths; Struct = rtStruct; }
public void LoadAndSaveMedicalVolumeTest() { var directory = TestData.GetFullImagesPath("sample_dicom"); var acceptanceTests = new StrictGeometricAcceptanceTest(string.Empty, string.Empty); var medicalVolume = MedIO.LoadAllDicomSeriesInFolderAsync(directory, acceptanceTests).Result.First().Volume; Directory.CreateDirectory(_tempFolder); Console.WriteLine($"Directory created {_tempFolder}"); Volume3D <byte>[] contourVolumes = new Volume3D <byte> [medicalVolume.Struct.Contours.Count]; for (int i = 0; i < medicalVolume.Struct.Contours.Count; i++) { contourVolumes[i] = new Volume3D <byte>( medicalVolume.Volume.DimX, medicalVolume.Volume.DimY, medicalVolume.Volume.DimZ, medicalVolume.Volume.SpacingX, medicalVolume.Volume.SpacingY, medicalVolume.Volume.SpacingZ, medicalVolume.Volume.Origin, medicalVolume.Volume.Direction); contourVolumes[i].Fill(medicalVolume.Struct.Contours[i].Contours, (byte)1); } // Calculate contours based on masks var rtContours = new List <RadiotherapyContour>(); for (int i = 0; i < medicalVolume.Struct.Contours.Count; i++) { var contour = medicalVolume.Struct.Contours[i]; var contourForAllSlices = GetContoursForAllSlices(contourVolumes[i]); var rtcontour = new DicomRTContour( contour.DicomRtContour.ReferencedRoiNumber, contour.DicomRtContour.RGBColor, contourForAllSlices); DicomRTStructureSetROI rtROIstructure = new DicomRTStructureSetROI( contour.StructureSetRoi.RoiNumber, contour.StructureSetRoi.RoiName, string.Empty, ERoiGenerationAlgorithm.Semiautomatic); DicomRTObservation observation = new DicomRTObservation( contour.DicomRtObservation.ReferencedRoiNumber, new DicomPersonNameConverter("Left^Richard^^Dr"), ROIInterpretedType.EXTERNAL); rtContours.Add(new RadiotherapyContour(rtcontour, rtROIstructure, observation)); } var rtStructureSet = new RadiotherapyStruct( medicalVolume.Struct.StructureSet, medicalVolume.Struct.Patient, medicalVolume.Struct.Equipment, medicalVolume.Struct.Study, medicalVolume.Struct.RTSeries, rtContours); MedIO.SaveMedicalVolumeAsync(_tempFolder, new MedicalVolume( medicalVolume.Volume, medicalVolume.Identifiers, medicalVolume.FilePaths, rtStructureSet)).Wait(); var medicalVolume2 = MedIO.LoadAllDicomSeriesInFolderAsync(_tempFolder, acceptanceTests).Result.First().Volume; foreach (var radiotherapyContour in medicalVolume2.Struct.Contours.Where(x => x.DicomRtContour.DicomRtContourItems.First().GeometricType == "CLOSED_PLANAR")) { var savedContour = medicalVolume2.Struct.Contours.First(x => x.StructureSetRoi.RoiName == radiotherapyContour.StructureSetRoi.RoiName); foreach (var contour in radiotherapyContour.Contours) { Assert.AreEqual(radiotherapyContour.DicomRtObservation.ROIInterpretedType, ROIInterpretedType.EXTERNAL); for (int i = 0; i < contour.Value.Count; i++) { if (!contour.Value[i].Equals(savedContour.Contours.ContoursForSlice(contour.Key)[i])) { Console.WriteLine(radiotherapyContour.StructureSetRoi.RoiName); Assert.Fail(); } } } } }
/// <summary> /// Checks if any of the contours are unsupported /// </summary> /// <param name="contoursData"></param> /// <returns></returns> private static bool CheckUnsupportedData(RadiotherapyStruct rtStruct) { return(rtStruct.Contours.Any(c => c.DicomRtContour.DicomRtContourItems.Any(r => r.GeometricType != DicomExtensions.ClosedPlanarString))); }
/// <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>())); } }
/// <summary> /// Returns a task to save the given rtStruct to disk. /// </summary> /// <param name="filename"></param> /// <param name="rtStruct"></param> /// <returns></returns> public static async Task SaveRtStructAsync(string filename, RadiotherapyStruct rtStruct) { await Task.Run( () => RtStructWriter.SaveRtStruct(filename, rtStruct)); }
public static void SaveRtStruct(string filePath, RadiotherapyStruct rtStruct) { var file = GetRtStructFile(rtStruct); file.Save(filePath); }