public static MedicalVolume LoadVolume(DicomFolderContents dicomFolder)
        {
            dicomFolder = dicomFolder ?? throw new ArgumentNullException(nameof(dicomFolder));

            var volumeLoaderResult = MedIO.LoadAllDicomSeries(
                dicomFolder,
                new ModerateGeometricAcceptanceTest("Non Square pixels", "Unsupported Orientation"),
                loadStructuresIfExists: false,
                supportLossyCodecs: false);

            if (volumeLoaderResult.Count > 1)
            {
                throw new Exception($"More than 1 volume loaded for path {dicomFolder.FolderPath}");
            }

            var volumeLoaderFirstResult = volumeLoaderResult.First();

            if (volumeLoaderFirstResult.Error != null)
            {
                throw volumeLoaderFirstResult.Error;
            }

            if (volumeLoaderFirstResult.Warnings != null && volumeLoaderFirstResult.Warnings.Count > 0)
            {
                throw new Exception(string.Join(",", volumeLoaderFirstResult.Warnings));
            }

            return(volumeLoaderFirstResult.Volume);
        }
Example #2
0
        public static IEnumerable <(string ChannelId, DicomFolderContents Content)> DecompressSegmentationData(Stream inputStream)
        {
            var files = DicomCompressionHelpers.DecompressPayload(inputStream);
            var dictionaryChannelToFiles = new Dictionary <string, List <byte[]> >();

            foreach ((string filename, byte[] data) in files)
            {
                var channelId = filename.Split(DicomCompressionHelpers.ChannelIdAndDicomSeriesSeparator).First();

                if (!dictionaryChannelToFiles.ContainsKey(channelId))
                {
                    dictionaryChannelToFiles.Add(channelId, new List <byte[]>());
                }

                dictionaryChannelToFiles[channelId].Add(data);
            }

            var result = new List <(string ChannelId, DicomFolderContents content)>();

            foreach (var item in dictionaryChannelToFiles)
            {
                var fileAndPaths = item.Value
                                   .Select(x => DicomFileAndPath.SafeCreate(new MemoryStream(x), string.Empty))
                                   .ToList();
                result.Add((item.Key, DicomFolderContents.Build(fileAndPaths)));
            }

            return(result);
        }
Example #3
0
        /// <summary>
        /// Attempt to load all volume for all CT and MR image series within the given DicomFolderContents
        /// </summary>
        /// <param name="dfc">A pre-built description of DICOM contents within a particular folder</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 a volume should be loaded</param>
        /// <param name="supportLossyCodecs">If you wish to accept lossy encodings of image pixel data</param>
        /// <returns></returns>
        public static IList <VolumeLoaderResult> LoadAllDicomSeries(
            DicomFolderContents dfc, IVolumeGeometricAcceptanceTest acceptanceTests, bool loadStructuresIfExists, bool supportLossyCodecs)
        {
            var stopwatch = Stopwatch.StartNew();

            var resultList = new List <VolumeLoaderResult>();

            foreach (var s in dfc.Series)
            {
                if (s.SeriesUID != null)
                {
                    resultList.Add(LoadDicomSeries(dfc, s.SeriesUID, acceptanceTests, loadStructuresIfExists, supportLossyCodecs));
                }
            }

            stopwatch.Stop();
            Trace.TraceInformation($"Reading all DICOM series took: {stopwatch.ElapsedMilliseconds} ms");
            return(resultList);
        }
        /// <summary>
        /// Convert from Nifti format to DICOM-RT format.
        /// </summary>
        /// <param name="niftiFilename">Nifti input filename.</param>
        /// <param name="referenceSeries">Path to folder of reference DICOM files.</param>
        /// <param name="structureNames">Names for each structure.</param>
        /// <param name="structureColors">Colors for each structure, defaults to red if this array smaller than list of structures.</param>
        /// <param name="fillHoles">Flags to enable fill holes for each structure, defaults to false this array smaller than list of structures..</param>
        /// <param name="dcmFilename">Target output file.</param>
        public static void ConvertNiftiToDicom(
            string niftiFilename,
            string referenceSeries,
            string[] structureNames,
            RGBColorOption?[] structureColors,
            bool?[] fillHoles,
            ROIInterpretedType[] roiInterpretedTypes,
            string dcmFilename,
            string modelNameAndVersion,
            string manufacturer,
            string interpreter)
        {
            var sourceVolume = MedIO.LoadNiftiAsByte(niftiFilename);

            Trace.TraceInformation($"Loaded NIFTI from {niftiFilename}");

            var labels = VolumeMetadataMapper.MultiLabelMapping(sourceVolume, structureNames.Length);
            var volumesWithMetadata = VolumeMetadataMapper.MapVolumeMetadata(labels, structureNames, structureColors, fillHoles, roiInterpretedTypes);

            var referenceVolume = DicomSeriesHelpers.LoadVolume(
                DicomFolderContents.Build(
                    Directory.EnumerateFiles(referenceSeries).Select(x => DicomFileAndPath.SafeCreate(x)).ToList()
                    ));

            var outputEncoder = new DicomRTStructOutputEncoder();

            var outputStructureBytes = outputEncoder.EncodeStructures(
                volumesWithMetadata,
                new Dictionary <string, MedicalVolume>()
            {
                { "", referenceVolume }
            },
                modelNameAndVersion,
                manufacturer,
                interpreter);

            File.WriteAllBytes(dcmFilename, outputStructureBytes.Array);
        }
        /// <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);
        }
        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))));
            }
        }
Example #7
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>()));
            }
        }