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());
 }
Example #4
0
        /// <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);
            }
        }
Example #7
0
        /// <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)));
 }
Example #10
0
        /// <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>()));
            }
        }
Example #11
0
 /// <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);
        }