Enhanced Multiframe to Single Frame Conversion
Splitting enhanced multiframe imaging objects to multiple single frame objects may involve many attributes. Given our flexiblility with Multiframe Images, we do not split the images by default. Because of this many ask us: “why don’t you also include this attribute from the per-frame functional groups to the single frame image”, or “can you not include that attribute in the single frame image”.
So the short answer is no.
However, there is a way with DicomObjects (both .NET version and .COM version) to load enhanced imaging objects, and split them into multiple single-frame objects.
You can include/exclude any attributes from the output single-frame images and make a decision on the behavior of your conversion based on the specific enhanced imaging objects (Enhanced CT, MR. etc, etc.). Below is one example of how to achieve the splitting process using DicomObjects. This example also demonstrates the complexity in dealing with Enhanced Imaging objects, and will give you more of an understanding as to why we negated to add this method into DicomObjects.
class MultiToSingle
{
public Keyword Group;
public Keyword GroupItem;
public Keyword TopLevelItem;
public MultiToSingle(Keyword group, Keyword groupItem, Keyword topLevelItem)
{
Group = group;
GroupItem = groupItem;
TopLevelItem = topLevelItem;
}
public MultiToSingle(Keyword group, Keyword groupItem)
{
Group = group;
GroupItem = groupItem;
TopLevelItem = groupItem;
}
}
static MultiToSingle[] Lookup = new MultiToSingle[]
{
//basic
new MultiToSingle(Keyword.PlaneOrientationSequence, Keyword.ImageOrientationPatient),
new MultiToSingle(Keyword.PixelMeasuresSequence, Keyword.SliceThickness),
new MultiToSingle(Keyword.PixelMeasuresSequence, Keyword.PixelSpacing),
new MultiToSingle(Keyword.FrameVOILUTSequence, Keyword.WindowCenter),
new MultiToSingle(Keyword.FrameVOILUTSequence, Keyword.WindowWidth),
new MultiToSingle(Keyword.PixelValueTransformationSequence, Keyword.RescaleIntercept),
new MultiToSingle(Keyword.PixelValueTransformationSequence, Keyword.RescaleSlope),
new MultiToSingle(Keyword.PixelValueTransformationSequence, Keyword.RescaleType),
new MultiToSingle(Keyword.FrameContentSequence, Keyword.FrameAcquisitionNumber, Keyword.InstanceNumber),
new MultiToSingle(Keyword.PlanePositionSequence, Keyword.ImagePositionPatient),
new MultiToSingle(Keyword.PixelValueTransformationSequence, Keyword.RescaleIntercept),
new MultiToSingle(Keyword.PixelValueTransformationSequence, Keyword.RescaleSlope),
new MultiToSingle(Keyword.PixelValueTransformationSequence, Keyword.RescaleType),
new MultiToSingle(Keyword.FrameAnatomySequence, Keyword.AnatomicRegionSequence),
//MR from sample
new MultiToSingle(Keyword.MRImagingModifierSequence, Keyword.PixelBandwidth),
new MultiToSingle(Keyword.MRImagingModifierSequence, Keyword.TransmitterFrequency, Keyword.ImagingFrequency),
new MultiToSingle(Keyword.MRReceiveCoilSequence, Keyword.ReceiveCoilName),
new MultiToSingle(Keyword.MRTransmitCoilSequence, Keyword.TransmitCoilName),
new MultiToSingle(Keyword.MRTimingAndRelatedParametersSequence, Keyword.RepetitionTime),
new MultiToSingle(Keyword.MRTimingAndRelatedParametersSequence, Keyword.EchoTrainLength),
new MultiToSingle(Keyword.MRTimingAndRelatedParametersSequence, Keyword.FlipAngle),
new MultiToSingle(Keyword.MRTimingAndRelatedParametersSequence, Keyword.RepetitionTime),
new MultiToSingle(Keyword.MREchoSequence, Keyword.EffectiveEchoTime, Keyword.EchoTime), // 3rd param!
// CT from sample
new MultiToSingle(Keyword.CTAcquisitionDetailsSequence, Keyword.DataCollectionDiameter),
new MultiToSingle(Keyword.CTAcquisitionDetailsSequence, Keyword.GantryDetectorTilt),
new MultiToSingle(Keyword.CTAcquisitionDetailsSequence, Keyword.TableHeight),
new MultiToSingle(Keyword.CTAcquisitionDetailsSequence, Keyword.RotationDirection),
new MultiToSingle(Keyword.CTGeometrySequence, Keyword.DistanceSourceToDetector),
new MultiToSingle(Keyword.CTReconstructionSequence, Keyword.ReconstructionDiameter),
new MultiToSingle(Keyword.CTReconstructionSequence, Keyword.ConvolutionKernel),
new MultiToSingle(Keyword.CTXRayDetailsSequence, Keyword.KVP),
new MultiToSingle(Keyword.CTXRayDetailsSequence, Keyword.FilterType),
new MultiToSingle(Keyword.CTXRayDetailsSequence, Keyword.FocalSpots),
new MultiToSingle(Keyword.CTXRayDetailsSequence, Keyword.FilterMaterial),
};
public static IEnumerable<DicomImage> MakeSingleImages(this DicomImage multi, string newSOPclass, string UIDBase)
{
for (int frame = 1; frame <= multi.FrameCount; frame++)
{
// Pixel Data for 1 frame
DicomImage Single = multi.SubImage(Point.Empty, multi.Size, 1, frame);
// simple moves from functional group to top level
foreach (var l in Lookup)
{
var g = multi.DataSet.FunctionalGroupDataSet(l.Group, frame);
if (g != null)
{
var a = g[l.GroupItem];
if (a.Exists)
Single.DataSet.Add(l.TopLevelItem, a.Value);
}
}
if (Single.SOPClass == SOPClasses.EnhancedMR)
{
// special handling for MR
switch (Single.DataSet[Keyword.EchoPulseSequence].Value as string)
{
case "SPIN":
Single.DataSet.Add(Keyword.ScanningSequence, "SE");
break;
case "GRADIENT":
Single.DataSet.Add(Keyword.ScanningSequence, "GR");
break;
case "BOTH":
Single.DataSet.Add(Keyword.ScanningSequence, "SE\\GR");
break;
default:
Single.DataSet.Add(Keyword.ScanningSequence, ""); // type 2
break;
}
// scanning variant
var Variant = new List<string>();
if (Single.DataSet[Keyword.SegmentedKSpaceTraversal].Value as string == "PARTIAL" || Single.DataSet[Keyword.SegmentedKSpaceTraversal].Value as string == "FULL")
Variant.Add("SK");
var MTC = Single.DataSet.FunctionalGroupOrRootLevelAttribute(Keyword.MRImagingModifierSequence, frame, Keyword.MagnetizationTransfer).Value as string;
if (Single.DataSet[Keyword.SteadyStatePulseSequence].Value is string && Single.DataSet[Keyword.SegmentedKSpaceTraversal].Value as string != "NONE")
{
if (Single.DataSet[Keyword.SteadyStatePulseSequence].Value as string == "TIME_REVERSED")
Variant.Add("TRSS");
else
Variant.Add("SS");
}
if (MTC == "ON_RESONANCE" || MTC == "OFF_RESONANCE")
Variant.Add("MTC");
if (Variant.Count == 0)
Variant.Add("NONE");
Single.DataSet.Add(Keyword.SequenceVariant, Variant);
// Scan Options
var Options = new List<string>();
// to fill in one day if we can find them !
Single.DataSet.Add(Keyword.ScanOptions, Options);
}
Single.DataSet.Remove(Keyword.SharedFunctionalGroupsSequence);
Single.DataSet.Remove(Keyword.PerFrameFunctionalGroupsSequence);
Single.DataSet.Add(Keyword.ImageType, "ORIGINAL\\PRIMARY");
// other options to consider in MR handling
//SP
//spoiled
//MP
//MAG prepared
//OSP
//oversampling phase
//NONE
//no sequence variant
Single.SOPClass = newSOPclass;
Single.InstanceUID = UIDBase + frame.ToString();
yield return Single;
}
}