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