/// <summary> /// Constructor /// </summary> public SegmentationDocument(SegmentationDocumentIod segmentationDocumentIod) { // Because the constructor isn't called during deserialization Initialize(new StreamingContext()); SegmentationDocumentIod = segmentationDocumentIod; }
public DicomFile Serialize(SegmentationSerializerCallback callback) { Platform.CheckForNullReference(callback, "callback"); Debug.Assert(!_segDocument.Saved, "Cannot serialize previously saved document"); // TODO: validate that all Segs are coming from the same study! IPresentationImage firstIPresentationImage = null; if (_segDocument.Segs != null) { var oneSeg = _segDocument.Segs.FirstOrDefault(item => item != null && item.SegGraphics != null && item.SegGraphics.OfType<PolygonalRoiSegGraphic>().Any()); if (oneSeg != null) { var polyGraphic = oneSeg.SegGraphics.OfType<PolygonalRoiSegGraphic>().First(); firstIPresentationImage = callback(polyGraphic.ImageSopInstanceUid, polyGraphic.ImageFrameNumber); } } var firstImageSopProvider = firstIPresentationImage as IImageSopProvider; if (firstImageSopProvider == null) return null; var sourceSop = firstImageSopProvider.ImageSop; // source of the common DICOM attributes var dicomFile = _sopInstanceFactory.CreateFile(sourceSop); // NOTE. These modules are initialized by the factory: // patient IE // - PatientModule // - ClinicalTrialSubjectModule // study IE // - GeneralStudyModule // - PatientStudyModule // - ClinicalTrialStudyModule // equipment IE // - GeneralEquipmentModule // Data values const int instanceNumber = 1; var contentDateTime = DateTime.Now; var segDocumentIod = new SegmentationDocumentIod(dicomFile.DataSet); // PatientModule var patientModule = segDocumentIod.PatientModuleIod; // patientModule.PatientBreedDescription = null; // bug in CC code patientModule.DicomAttributeProvider[DicomTags.PatientBreedDescription].SetEmptyValue(); // GeneralEquipmentModule var generalEquipmentModuleIod = segDocumentIod.GeneralEquipmentModuleIod; generalEquipmentModuleIod.DeviceSerialNumber = EnvironmentUtilities.MachineIdentifier; // GeneralSeriesModule var srcGeneralSeriesModuleIod = new GeneralSeriesModuleIod(sourceSop.DataSource); var generalSeriesModuleIod = segDocumentIod.GeneralSeriesModuleIod; generalSeriesModuleIod.SeriesDescription = _segDocument.SeriesDescription; generalSeriesModuleIod.SeriesNumber = _segDocument.SeriesNumber; generalSeriesModuleIod.Modality = Modality.Seg; generalSeriesModuleIod.SeriesInstanceUid = DicomUid.GenerateUid().UID; generalSeriesModuleIod.Laterality = srcGeneralSeriesModuleIod.Laterality; generalSeriesModuleIod.SeriesDateTime = _segDocument.CreationDateTime; generalSeriesModuleIod.PerformingPhysiciansName = srcGeneralSeriesModuleIod.PerformingPhysiciansName; generalSeriesModuleIod.PerformingPhysicianIdentificationSequence = srcGeneralSeriesModuleIod.PerformingPhysicianIdentificationSequence; generalSeriesModuleIod.ProtocolName = srcGeneralSeriesModuleIod.ProtocolName; { // General Description Code Sequence is missing from the GeneralSeriesModuleIod implementation var seriesDescriptionCodeSequence = new CodeSequenceMacro { CodeValue = "113076", CodeMeaning = "Segmentation", CodingSchemeDesignator = "DCM" }; var result = new[] { seriesDescriptionCodeSequence.DicomSequenceItem }; generalSeriesModuleIod.DicomAttributeProvider[DicomTags.SeriesDescriptionCodeSequence].Values = result; } string userDicomName = null; if (_segDocument.UserInfo != null && !string.IsNullOrEmpty(_segDocument.UserInfo.Name)) { userDicomName = FormatDicomName(_segDocument.UserInfo.Name); if (userDicomName != null) generalSeriesModuleIod.OperatorsName = userDicomName; // NOTE: Login name is being ignored for now } generalSeriesModuleIod.BodyPartExamined = srcGeneralSeriesModuleIod.BodyPartExamined; generalSeriesModuleIod.PatientPosition = srcGeneralSeriesModuleIod.PatientPosition; generalSeriesModuleIod.RequestAttributesSequence = srcGeneralSeriesModuleIod.RequestAttributesSequence; //generalSeriesModuleIod.AnatomicalOrientationType = srcGeneralSeriesModuleIod.AnatomicalOrientationType; // Not needed // FrameOfReferenceModule var srcFrameOfReferenceModuleIod = new FrameOfReferenceModuleIod(sourceSop.DataSource); segDocumentIod.FrameOfReferenceModuleIod.FrameOfReferenceUid = srcFrameOfReferenceModuleIod.FrameOfReferenceUid; segDocumentIod.FrameOfReferenceModuleIod.PositionReferenceIndicator = srcFrameOfReferenceModuleIod.PositionReferenceIndicator; // Initialize Segmentation Image Module first var segmentationImageModuleIod = segDocumentIod.SegmentationImageModuleIod; segmentationImageModuleIod.InitializeAttributes(); // General Image Module and Segmentation Image Module var srcGeneralImageModuleIod = new GeneralImageModuleIod(sourceSop.DataSource); var generalImageModuleIod = segDocumentIod.GeneralImageModuleIod; generalImageModuleIod.InstanceNumber = instanceNumber; generalImageModuleIod.PatientOrientation = srcGeneralImageModuleIod.PatientOrientation; generalImageModuleIod.ContentDateTime = contentDateTime; generalImageModuleIod.ImageType = "DERIVED\\PRIMARY"; generalImageModuleIod.AcquisitionNumber = srcGeneralImageModuleIod.AcquisitionNumber; generalImageModuleIod.AcquisitionDateTime = srcGeneralImageModuleIod.AcquisitionDateTime; generalImageModuleIod.QualityControlImage = srcGeneralImageModuleIod.QualityControlImage; generalImageModuleIod.BurnedInAnnotation = srcGeneralImageModuleIod.BurnedInAnnotation; generalImageModuleIod.RecognizableVisualFeatures = srcGeneralImageModuleIod.RecognizableVisualFeatures; generalImageModuleIod.LossyImageCompression = srcGeneralImageModuleIod.LossyImageCompression.HasValue && srcGeneralImageModuleIod.LossyImageCompression.Value; generalImageModuleIod.LossyImageCompressionMethod = srcGeneralImageModuleIod.LossyImageCompressionMethod; generalImageModuleIod.LossyImageCompressionRatio = srcGeneralImageModuleIod.LossyImageCompressionRatio; generalImageModuleIod.IrradiationEventUid = srcGeneralImageModuleIod.IrradiationEventUid; // Image Pixel Module and Segmentation Image Module var srcImagePixelModule = new ImagePixelMacroIod(sourceSop.DataSource); var imagePixelModule = segDocumentIod.ImagePixelModuleIod; imagePixelModule.Rows = srcImagePixelModule.Rows; // same height as the the image imagePixelModule.Columns = srcImagePixelModule.Columns; // same width as the image //imagePixelModule.PixelAspectRatio = srcImagePixelModule.PixelAspectRatio; // same as the image // Continue initialization of non-default values for the Segmentation Image Module segmentationImageModuleIod.ContentLabel = SanitizeDicomCsValue(_segDocument.ContentLabel); if (!string.IsNullOrEmpty(userDicomName)) segmentationImageModuleIod.ContentCreatorsName = userDicomName; segmentationImageModuleIod.SegmentationType = SegmentationType.BINARY; // Per segmentation and per frame item initialization var docHasOneFrame = _segDocument.Segs.Where(item => item != null && item.SegGraphics != null).Sum(seg => seg.SegGraphics.OfType<PolygonalRoiSegGraphic>().Count()) == 1; var docHasOneSeg = _segDocument.Segs.Count(item => item != null && item.SegGraphics != null && item.SegGraphics.OfType<PolygonalRoiSegGraphic>().Any()) == 1; var multiFrameDimensionsModuleIod = segDocumentIod.MultiFrameDimensionModuleIod; multiFrameDimensionsModuleIod.InitializeAttributes(); var segmentSequenceItems = new List<SegmentSequence>(); var dimensionIndexSequenceItems = new List<DimensionIndexSequenceItem>(); var dimensionOrganizationSequenceItems = new List<DimensionOrganizationSequenceItem>(); var multiFrameFunctionalGroupsModuleIod = segDocumentIod.MultiFrameFunctionalGroupsModuleIod; multiFrameFunctionalGroupsModuleIod.InitializeAttributes(); var perFrameFunctionalGroupSequenceItems = new List<FunctionalGroupsSequenceItem>(); var frameBytesList = new List<byte[]>(); // list of pixel data for each frame var seriesUidToSopClassUidToSopInstanceUid = new Dictionary<string, Dictionary<string, HashSet<string>>>(); var segmentNumber = 0; foreach (var seg in _segDocument.Segs) { segmentNumber++; Debug.Assert(segmentNumber == 1, "We're only supposed to create one Segment per document for now"); // Segment Sequence initialization var segmentSequenceItemIod = segmentationImageModuleIod.CreateSegmentSequence(); segmentSequenceItemIod.SegmentNumber = segmentNumber; segmentSequenceItemIod.SegmentLabel = seg.Label; segmentSequenceItemIod.SegmentDescription = seg.Description; segmentSequenceItemIod.SegmentAlgorithmType = "MANUAL"; #region Category, Type, Anatomic Region, Anatomic Region Modifier var selectedCategory = seg.SelectedCategory; if (selectedCategory != null) { // Category var segmentedPropertyCategoryCodeSequenceItem = segmentSequenceItemIod.CreateSegmentedPropertyCategoryCodeSequence(); segmentedPropertyCategoryCodeSequenceItem.CodeValue = selectedCategory.CodeValue; segmentedPropertyCategoryCodeSequenceItem.CodeMeaning = selectedCategory.CodeMeaning; segmentedPropertyCategoryCodeSequenceItem.CodingSchemeDesignator = selectedCategory.CodingSchemeDesignator; //if (!string.IsNullOrEmpty(selectedCategory.CodingSchemeVersion)) // segmentedPropertyCategoryCodeSequenceItem.CodingSchemeVersion = selectedCategory.CodingSchemeVersion; segmentSequenceItemIod.SegmentedPropertyCategoryCodeSequence = new[] { segmentedPropertyCategoryCodeSequenceItem }; // Type if (selectedCategory.SelectedType != null) { var segmentedPropertyTypeCodeSequenceItem = segmentSequenceItemIod.CreateSegmentedPropertyTypeCodeSequence(); segmentedPropertyTypeCodeSequenceItem.CodeValue = selectedCategory.SelectedType.CodeValue; segmentedPropertyTypeCodeSequenceItem.CodeMeaning = selectedCategory.SelectedType.CodeMeaning; segmentedPropertyTypeCodeSequenceItem.CodingSchemeDesignator = selectedCategory.SelectedType.CodingSchemeDesignator; //if (!string.IsNullOrEmpty(selectedCategory.SelectedType.CodingSchemeVersion)) // segmentedPropertyTypeCodeSequenceItem.CodingSchemeVersion = selectedCategory.SelectedType.CodingSchemeVersion; // Type modifier if (selectedCategory.SelectedType.SelectedTypeModifier != null) { var segmentedPropertyTypeModifierCodeSequenceItem = new CodeSequenceMacro(); segmentedPropertyTypeModifierCodeSequenceItem.CodeValue = selectedCategory.SelectedType.SelectedTypeModifier.CodeValue; segmentedPropertyTypeModifierCodeSequenceItem.CodeMeaning = selectedCategory.SelectedType.SelectedTypeModifier.CodeMeaning; segmentedPropertyTypeModifierCodeSequenceItem.CodingSchemeDesignator = selectedCategory.SelectedType.SelectedTypeModifier.CodingSchemeDesignator; //if (!string.IsNullOrEmpty(selectedCategory.SelectedType.SelectedTypeModifier.CodingSchemeVersion)) // segmentedPropertyTypeModifierCodeSequenceItem.CodingSchemeVersion = selectedCategory.SelectedType.SelectedTypeModifier.CodingSchemeVersion; segmentedPropertyTypeCodeSequenceItem.SegmentedPropertyTypeModifierCodeSequence = new[] {segmentedPropertyTypeModifierCodeSequenceItem}; } segmentSequenceItemIod.SegmentedPropertyTypeCodeSequence = new[] { segmentedPropertyTypeCodeSequenceItem }; } // Anatomic Region var selectedAnatomicRegion = selectedCategory.SelectedAnatomicRegion; if (selectedAnatomicRegion != null) { var anatomicRegionSequenceItem = segmentSequenceItemIod.CreateAnatomicRegionSequence(); anatomicRegionSequenceItem.CodeValue = selectedAnatomicRegion.CodeValue; anatomicRegionSequenceItem.CodeMeaning = selectedAnatomicRegion.CodeMeaning; anatomicRegionSequenceItem.CodingSchemeDesignator = selectedAnatomicRegion.CodingSchemeDesignator; //if (!string.IsNullOrEmpty(selectedAnatomicRegion.CodingSchemeVersion)) // anatomicRegionSequenceItem.CodingSchemeVersion = selectedAnatomicRegion.CodingSchemeVersion; // Anatomic region Modifier if (selectedAnatomicRegion.SelectedAnatomicRegionModifier != null) { var anatomicRegionModifierSequenceItem = new CodeSequenceMacro(); anatomicRegionModifierSequenceItem.CodeValue = selectedAnatomicRegion.SelectedAnatomicRegionModifier.CodeValue; anatomicRegionModifierSequenceItem.CodeMeaning = selectedAnatomicRegion.SelectedAnatomicRegionModifier.CodeMeaning; anatomicRegionModifierSequenceItem.CodingSchemeDesignator = selectedAnatomicRegion.SelectedAnatomicRegionModifier.CodingSchemeDesignator; //if (!string.IsNullOrEmpty(selectedAnatomicRegion.SelectedAnatomicRegionModifier.CodingSchemeVersion)) // anatomicRegionModifierSequenceItem.CodingSchemeVersion = selectedAnatomicRegion.SelectedAnatomicRegionModifier.CodingSchemeVersion; anatomicRegionSequenceItem.AnatomicRegionModifierSequence = new[] { anatomicRegionModifierSequenceItem }; } segmentSequenceItemIod.AnatomicRegionSequence = new[] { anatomicRegionSequenceItem }; } } #endregion segmentSequenceItemIod.RecomendedDisplayCIELabValue = LabColorHelpers.RgbColorToCIELabColor(seg.Color); segmentSequenceItems.Add(segmentSequenceItemIod); // Dimension Organization Sequence item var dimensionOrganizationUid = DicomUid.GenerateUid().UID; var dimensionOrganizationSequenceItem = multiFrameDimensionsModuleIod.CreateDimensionOrganizationSequenceItem(); dimensionOrganizationSequenceItem.DimensionOrganizationUid = dimensionOrganizationUid; dimensionOrganizationSequenceItems.Add(dimensionOrganizationSequenceItem); // Dimension Index Sequence items var dimensionIndexSequenceItem1 = multiFrameDimensionsModuleIod.CreateDimensionIndexSequenceItem(); dimensionIndexSequenceItem1.DimensionIndexPointer = DicomTags.StackId; dimensionIndexSequenceItem1.FunctionalGroupPointer = DicomTags.FrameContentSequence; dimensionIndexSequenceItem1.DimensionOrganizationUid = dimensionOrganizationUid; dimensionIndexSequenceItem1.DimensionDescriptionLabel = "Stack ID"; dimensionIndexSequenceItems.Add(dimensionIndexSequenceItem1); var dimensionIndexSequenceItem2 = multiFrameDimensionsModuleIod.CreateDimensionIndexSequenceItem(); dimensionIndexSequenceItem2.DimensionIndexPointer = DicomTags.InStackPositionNumber; dimensionIndexSequenceItem2.FunctionalGroupPointer = DicomTags.FrameContentSequence; dimensionIndexSequenceItem2.DimensionOrganizationUid = dimensionOrganizationUid; dimensionIndexSequenceItem2.DimensionDescriptionLabel = "In Stack Position Number"; dimensionIndexSequenceItems.Add(dimensionIndexSequenceItem2); var inStackPositionIndex = 0; var presentationImagePolygons = new Dictionary<IPresentationImage, List<PolygonalRoiSegGraphic>>(); foreach (var polygonalSegGraphic in seg.SegGraphics.OfType<PolygonalRoiSegGraphic>()) { var poly = polygonalSegGraphic.PolygonalRoiGraphic.Roi as PolygonalRoi; if (poly != null) { var currentPresentationImage = callback(polygonalSegGraphic.ImageSopInstanceUid, polygonalSegGraphic.ImageFrameNumber); if (presentationImagePolygons.ContainsKey(currentPresentationImage)) presentationImagePolygons[currentPresentationImage].Add(polygonalSegGraphic); else presentationImagePolygons.Add(poly.PresentationImage, new List<PolygonalRoiSegGraphic> { polygonalSegGraphic }); } else { Debug.Assert(false, "Encountered non-polygonal graphic during segmentation serialization"); } } foreach (var presentationImage in presentationImagePolygons.Keys) { var currentImageSopProvider = presentationImage as IImageSopProvider; if (presentationImage == null) { Debug.Assert(false, "Failed to get IImageSopProvider for the current Segmentation graphic"); continue; } Debug.Assert(presentationImagePolygons[presentationImage].FirstOrDefault().ImageFrameNumber == currentImageSopProvider.Frame.FrameNumber, "Stored frame number must match with the current SOP Instance's value"); #region PerFrameFunctionalGroupSequenceItem // Initialize Per Frame Functional Groups here and groups var perFrameFunctionalGroupSequenceItem = multiFrameFunctionalGroupsModuleIod.CreatePerFrameFunctionalGroupsSequence(); if (!docHasOneSeg) { // Pixel Measures Functional Group (per frame) InitializePixelMeasureFunctionalGroup(perFrameFunctionalGroupSequenceItem, currentImageSopProvider.Frame); // Initialize Segmentation Functional Group (per frame) InitializeSegmentationFunctionalGroup(perFrameFunctionalGroupSequenceItem, segmentNumber); // Plane Orientation (Patient) Functional Group InitializePlaneOrientationPatientFunctionalGroup(perFrameFunctionalGroupSequenceItem, currentImageSopProvider.Frame.ImageOrientationPatient); } if (!docHasOneFrame) { // Plain Position Patient Functional Group (per frame) InitializePlanePositionPatientFunctionalGroup(perFrameFunctionalGroupSequenceItem, currentImageSopProvider.Frame.ImagePositionPatient); // Derivation Image Functional Group (per frame) InitializeDerivationImageFunctionalGroup(perFrameFunctionalGroupSequenceItem, currentImageSopProvider.ImageSop, currentImageSopProvider.Frame.FrameNumber); } else { Debug.Assert(firstImageSopProvider.ImageSop.SeriesInstanceUid == currentImageSopProvider.Frame.SeriesInstanceUid && firstImageSopProvider.ImageSop.SopInstanceUid == currentImageSopProvider.ImageSop.SopInstanceUid, "initial image reference and the single image reference must be the same"); } // Initialize Frame Content Functional Group InitializeFrameContentFunctionalGroup(perFrameFunctionalGroupSequenceItem, segmentNumber, ++inStackPositionIndex); perFrameFunctionalGroupSequenceItems.Add(perFrameFunctionalGroupSequenceItem); #endregion PerFrameFunctionalGroupSequenceItem // Store referenced image info in a dictionary for later use { var currentSeriesInstanceUid = currentImageSopProvider.ImageSop.SeriesInstanceUid; var currentSopClassUid = currentImageSopProvider.ImageSop.SopClassUid; var currentSopInstanceUid = currentImageSopProvider.ImageSop.SopInstanceUid; if (!seriesUidToSopClassUidToSopInstanceUid.ContainsKey(currentSeriesInstanceUid)) seriesUidToSopClassUidToSopInstanceUid.Add(currentSeriesInstanceUid, new Dictionary<string, HashSet<string>>()); var sopClassToSopInstanceDic = seriesUidToSopClassUidToSopInstanceUid[currentSeriesInstanceUid]; if (!sopClassToSopInstanceDic.ContainsKey(currentSopClassUid)) sopClassToSopInstanceDic.Add(currentSopClassUid, new HashSet<string>()); sopClassToSopInstanceDic[currentSopClassUid].Add(currentSopInstanceUid); } var polygons = new List<IList<PointF>>(); // Get frame's pixel data here foreach (var polygonalSegGraphic in presentationImagePolygons[presentationImage]) { var poly = polygonalSegGraphic.PolygonalRoiGraphic.Roi as PolygonalRoi; if (poly != null) { polygons.Add(poly.Polygon.Vertices); } else { Debug.Assert(false, "Encountered non-polygonal graphic during segmentation serialization"); } } var grayscalePixelData = CreateFramePixelData(presentationImage, polygons); frameBytesList.Add(grayscalePixelData.Raw); } } segmentationImageModuleIod.SegmentSequence = segmentSequenceItems.ToArray(); // Per Frame Functional Groups module multiFrameFunctionalGroupsModuleIod.PerFrameFunctionalGroupsSequence = perFrameFunctionalGroupSequenceItems.ToArray(); #region SharedFunctionalGroupSequence // Shared Functional Group Sequence Item var sharedFunctionalGroupSequenceItem = multiFrameFunctionalGroupsModuleIod.CreateSharedFunctionalGroupsSequence(); if (docHasOneSeg) { Debug.Assert(segmentNumber == 1, "This is for a single segment only"); // Pixel Measures Functional Group (shared) InitializePixelMeasureFunctionalGroup(sharedFunctionalGroupSequenceItem, firstImageSopProvider.Frame); // Initialize Segmentation Functional Group (shared) InitializeSegmentationFunctionalGroup(sharedFunctionalGroupSequenceItem, segmentNumber); // Plane Orientation (Patient) Functional Group InitializePlaneOrientationPatientFunctionalGroup(sharedFunctionalGroupSequenceItem, firstImageSopProvider.Frame.ImageOrientationPatient); } if (docHasOneFrame) { // Plain Position Patient Functional Group InitializePlanePositionPatientFunctionalGroup(sharedFunctionalGroupSequenceItem, firstImageSopProvider.Frame.ImagePositionPatient); // Derivation Image Functional Group InitializeDerivationImageFunctionalGroup(sharedFunctionalGroupSequenceItem, firstImageSopProvider.ImageSop, firstImageSopProvider.Frame.FrameNumber); } multiFrameFunctionalGroupsModuleIod.SharedFunctionalGroupsSequence = sharedFunctionalGroupSequenceItem; #endregion SharedFunctionalGroupSequence // Multi-frame Dimensions module multiFrameDimensionsModuleIod.DimensionIndexSequence = dimensionIndexSequenceItems.ToArray(); multiFrameDimensionsModuleIod.DimensionOrganizationSequence = dimensionOrganizationSequenceItems.ToArray(); multiFrameDimensionsModuleIod.DimensionOrganizationType = "3D"; // Multi-frame Functional Groups module multiFrameFunctionalGroupsModuleIod.SharedFunctionalGroupsSequence = sharedFunctionalGroupSequenceItem; multiFrameFunctionalGroupsModuleIod.PerFrameFunctionalGroupsSequence = perFrameFunctionalGroupSequenceItems.ToArray(); multiFrameFunctionalGroupsModuleIod.NumberOfFrames = perFrameFunctionalGroupSequenceItems.Count; // Specimen Module var srcSpecimenModuleIod = new SpecimenModuleIod(sourceSop.DataSource); var specimenModuleIod = segDocumentIod.SpecimenModuleIod; //specimenModuleIod.ContainerIdentifier = srcSpecimenModuleIod.ContainerIdentifier; specimenModuleIod.IssuerOfTheContainterIdentifier = srcSpecimenModuleIod.IssuerOfTheContainterIdentifier; specimenModuleIod.AlternateContainerIdentifierSequence = srcSpecimenModuleIod.AlternateContainerIdentifierSequence; specimenModuleIod.ContainerTypeCodeSequence = srcSpecimenModuleIod.ContainerTypeCodeSequence; //specimenModuleIod.ContainerDescription = srcSpecimenModuleIod.ContainerDescription; specimenModuleIod.ContainerComponentSequence = srcSpecimenModuleIod.ContainerComponentSequence; specimenModuleIod.SpecimenDescriptionSequence = srcSpecimenModuleIod.SpecimenDescriptionSequence; // Common Instance Reference Module var referencedSeriesSequenceItems = new List<ReferencedSeriesSequenceIod>(); foreach ( var seriesToSopClassToSopInstanceDic in seriesUidToSopClassUidToSopInstanceUid.Where(seriesToSopClassToSopInstanceDic => seriesToSopClassToSopInstanceDic.Value != null)) { var referencedSopInstances = new List<ReferencedInstanceSequenceIod>(); foreach (var sopClassToSopInstanceDic in seriesToSopClassToSopInstanceDic.Value.Where(sopClassToSopInstanceDic => sopClassToSopInstanceDic.Value != null)) { referencedSopInstances.AddRange(sopClassToSopInstanceDic.Value.Select(sopInstanceUid => new ReferencedInstanceSequenceIod { ReferencedSopClassUid = sopClassToSopInstanceDic.Key, ReferencedSopInstanceUid = sopInstanceUid })); } if (referencedSopInstances.Count > 0) { referencedSeriesSequenceItems.Add(new ReferencedSeriesSequenceIod { SeriesInstanceUid = seriesToSopClassToSopInstanceDic.Key, ReferencedInstanceSequence = referencedSopInstances.ToArray() }); } } if (referencedSeriesSequenceItems.Count > 0) { var commonInstanceReferenceModuleIod = segDocumentIod.CommonInstanceReferenceModuleIod; commonInstanceReferenceModuleIod.InitializeAttributes(); commonInstanceReferenceModuleIod.ReferencedSeriesSequence = referencedSeriesSequenceItems.ToArray(); } // SOP Common Module var srcSopCommonModuleIod = new SopCommonModuleIod(sourceSop.DataSource); var sopCommonModuleIod = segDocumentIod.SopCommonModuleIod; sopCommonModuleIod.SopClass = SopClass.SegmentationStorage; sopCommonModuleIod.SopInstanceUid = DicomUid.GenerateUid().UID; //sopCommonModuleIod.SpecificCharacterSet = "UTF-8"; // TBD -it's ISO_IR 192 by default sopCommonModuleIod.InstanceCreationDateTime = contentDateTime; sopCommonModuleIod.InstanceCreatorUid = InstanceCreatorUid; sopCommonModuleIod.TimezoneOffsetFromUtc = contentDateTime.ToString("zzz", DateTimeFormatInfo.InvariantInfo); //sopCommonModuleIod.LongitudinalTemporalInformationModified = srcSopCommonModuleIod.LongitudinalTemporalInformationModified; // has a bug in CC // Pixel data { Debug.Assert(frameBytesList.TrueForAll(bytes => bytes.Length == frameBytesList[0].Length), "Allocated buffers for all frames must be of the same size"); var byteBuffer = new byte[frameBytesList[0].Length * frameBytesList.Count]; using (var stream = new MemoryStream(byteBuffer)) { foreach (var frameBytes in frameBytesList) stream.Write(frameBytes, 0, frameBytes.Length); } // Byte Packing // TODO FIXME: we can do in-place byte packing without allocating the second array! var packetBuffer = new byte[(int) Math.Ceiling(byteBuffer.Length/8.0)]; var numberOfFullBytes = byteBuffer.Length/8; for (var i = 0; i < numberOfFullBytes; i++) { var newByte = packetBuffer[i]; for (var y = 0; y < 8; y++) { var bitMask = (byte) (1 << y); newByte = (byte) ((byteBuffer[8*i + y] & 0xFF) > 0 ? newByte | bitMask : newByte & ~bitMask); } packetBuffer[i] = newByte; } // last byte(s) TODO VK: handle padding for non-even number of bytes. make sure padded bits are initialized to 0 if (numberOfFullBytes < packetBuffer.Length) { // Pack leftover bytes ( < 8) Debug.Assert(packetBuffer.Length - numberOfFullBytes == 1, "Wrong destination bytes count during packing"); Debug.Assert(byteBuffer.Length - numberOfFullBytes*8 < 8, "Wrong leftover bytes count during packing"); var newByte = packetBuffer[packetBuffer.Length - 1]; for (var y = numberOfFullBytes * 8; y < byteBuffer.Length; y++) { var bitMask = (byte) (1 << (y%8)); newByte = (byte) ((byteBuffer[y] & 0xFF) > 0 ? newByte | bitMask : newByte & ~bitMask); } packetBuffer[packetBuffer.Length - 1] = newByte; } var pdAttribute = new DicomAttributeOW(DicomTags.PixelData); using (var stream = pdAttribute.AsStream()) { stream.Write(packetBuffer, 0, packetBuffer.Length); } multiFrameFunctionalGroupsModuleIod.DicomAttributeProvider[DicomTags.PixelData] = pdAttribute; } dicomFile.MediaStorageSopClassUid = SopClass.SegmentationStorageUid; dicomFile.MediaStorageSopInstanceUid = segDocumentIod.SopInstanceUid; // Update the original document with new values _segDocument.SeriesInstanceUid = segDocumentIod.SeriesInstanceUid; _segDocument.SopInstanceUid = segDocumentIod.SopInstanceUid; return dicomFile; }