/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), hosted at https://github.com/gunterze/dcm4che. * * The Initial Developer of the Original Code is * Agfa Healthcare. * Portions created by the Initial Developer are Copyright (C) 2013 * the Initial Developer. All Rights Reserved. * * Contributor(s): * See @authors listed below * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4che3.emf; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.BulkData; import org.dcm4che3.data.Fragments; import org.dcm4che3.data.Sequence; import org.dcm4che3.data.VR; /** * @author Gunter Zeilinger <gunterze@gmail.com> * */ public class MultiframeExtractor { private enum Impl { EnhancedCTImageExtractor(UID.CTImageStorage), EnhancedMRImageExtractor(UID.MRImageStorage) { Attributes extract(MultiframeExtractor mfe, Attributes emf, int frame) { Attributes sf = super.extract(mfe, emf, frame); setEchoTime(sf); setScanningSequence(sf); setSequenceVariant(sf); setScanOptions(sf); return sf; } void setEchoTime(Attributes sf) { double echoTime = sf.getDouble(Tag.EffectiveEchoTime, 0); if (echoTime == 0) sf.setNull(Tag.EchoTime, VR.DS); else sf.setDouble(Tag.EchoTime, VR.DS, echoTime); } void setScanningSequence(Attributes sf) { ArrayList<String> list = new ArrayList<String>(3); String eps = sf.getString(Tag.EchoPulseSequence); if (!"GRADIENT".equals(eps)) list.add("SE"); if (!"SPIN".equals(eps)) list.add("GR"); if ("YES".equals(sf.getString(Tag.InversionRecovery))) list.add("IR"); if ("YES".equals(sf.getString(Tag.EchoPlanarPulseSequence))) list.add("EP"); sf.setString(Tag.ScanningSequence, VR.CS, list.toArray(new String[list.size()])); } void setSequenceVariant(Attributes sf) { ArrayList<String> list = new ArrayList<String>(5); if (!"SINGLE".equals(sf.getString(Tag.SegmentedKSpaceTraversal))) list.add("SK"); String mf = sf.getString(Tag.MagnetizationTransfer); if (mf != null && !"NONE".equals(mf)) list.add("MTC"); String ssps = sf.getString(Tag.SteadyStatePulseSequence); if (ssps != null && !"NONE".equals(ssps)) list.add("TIME_REVERSED".equals(ssps) ? "TRSS" :"SS"); String sp = sf.getString(Tag.Spoiling); if (sp != null && !"NONE".equals(sp)) list.add("SP"); String op = sf.getString(Tag.OversamplingPhase); if (op != null && !"NONE".equals(op)) list.add("OSP"); if (list.isEmpty()) list.add("NONE"); sf.setString(Tag.SequenceVariant, VR.CS, list.toArray(new String[list.size()])); } void setScanOptions(Attributes sf) { ArrayList<String> list = new ArrayList<String>(3); String per = sf.getString(Tag.RectilinearPhaseEncodeReordering); if (per != null && !"LINEAR".equals(per)) list.add("PER"); String frameType3 = sf.getString(Tag.ImageType, 2); if ("ANGIO".equals(frameType3)) sf.setString(Tag.AngioFlag, VR.CS, "Y"); if (frameType3.startsWith("CARD")) list.add("CG"); if (frameType3.endsWith("RESP_GATED")) list.add("RG"); String pfd = sf.getString(Tag.PartialFourierDirection); if ("PHASE".equals(pfd)) list.add("PFP"); if ("FREQUENCY".equals(pfd)) list.add("PFF"); String sp = sf.getString(Tag.SpatialPresaturation); if (sp != null && !"NONE".equals(sp)) list.add("SP"); String sss = sf.getString(Tag.SpectrallySelectedSuppression); if (sss != null && sss.startsWith("FAT")) list.add("FS"); String fc = sf.getString(Tag.FlowCompensation); if (fc != null && !"NONE".equals(fc)) list.add("FC"); sf.setString(Tag.ScanOptions, VR.CS, list.toArray(new String[list.size()])); } }, EnhancedPETImageExtractor(UID.PositronEmissionTomographyImageStorage); private final String sfcuid; Impl(String sfcuid) { this.sfcuid = sfcuid; } Attributes extract(MultiframeExtractor mfe, Attributes emf, int frame) { return mfe.extract(emf, frame, sfcuid); } } private static final HashMap<String,Impl> impls = new HashMap<String,Impl>(8); static { impls.put(UID.EnhancedCTImageStorage, Impl.EnhancedCTImageExtractor); impls.put(UID.EnhancedMRImageStorage, Impl.EnhancedMRImageExtractor); impls.put(UID.EnhancedPETImageStorage, Impl.EnhancedPETImageExtractor); } private static final int[] EXCLUDE_TAGS = { Tag.ReferencedImageEvidenceSequence, Tag.SourceImageEvidenceSequence, Tag.DimensionIndexSequence, Tag.NumberOfFrames, Tag.SharedFunctionalGroupsSequence, Tag.PerFrameFunctionalGroupsSequence, Tag.PixelData }; private boolean preserveSeriesInstanceUID; private String instanceNumberFormat = "%s%04d"; private UIDMapper uidMapper = new HashUIDMapper(); private NumberOfFramesAccessor nofAccessor = new NumberOfFramesAccessor(); public static boolean isSupportedSOPClass(String cuid) { return impls.containsKey(cuid); } public static String legacySOPClassUID(String mfcuid) { Impl impl = impls.get(mfcuid); return impl != null ? impl.sfcuid : null; } public final boolean isPreserveSeriesInstanceUID() { return preserveSeriesInstanceUID; } public final void setPreserveSeriesInstanceUID( boolean preserveSeriesInstanceUID) { this.preserveSeriesInstanceUID = preserveSeriesInstanceUID; } public final String getInstanceNumberFormat() { return instanceNumberFormat; } public final void setInstanceNumberFormat(String instanceNumberFormat) { String.format(instanceNumberFormat, "1", 1); this.instanceNumberFormat = instanceNumberFormat; } public final UIDMapper getUIDMapper() { return uidMapper; } public final void setUIDMapper(UIDMapper uidMapper) { if (uidMapper == null) throw new NullPointerException(); this.uidMapper = uidMapper; } public final NumberOfFramesAccessor getNumberOfFramesAccessorr() { return nofAccessor; } public final void setNumberOfFramesAccessor(NumberOfFramesAccessor accessor) { if (accessor == null) throw new NullPointerException(); this.nofAccessor = accessor; } /** Extract specified frame from Enhanced Multi-frame image and return it * as correponding legacy Single-frame image. * * @param emf Enhanced Multi-frame image * @param frame 0 based frame index * @return legacy Single-frame image */ public Attributes extract(Attributes emf, int frame) { return implFor(emf.getString(Tag.SOPClassUID)) .extract(this, emf, frame); } private static Impl implFor(String mfcuid) { Impl impl = impls.get(mfcuid); if (impl == null) throw new IllegalArgumentException( "Unsupported SOP Class: " + mfcuid); return impl; } private Attributes extract(Attributes emf, int frame, String cuid) { Attributes sfgs = emf.getNestedDataset(Tag.SharedFunctionalGroupsSequence); if (sfgs == null) throw new IllegalArgumentException( "Missing (5200,9229) Shared Functional Groups Sequence"); Attributes fgs = emf.getNestedDataset(Tag.PerFrameFunctionalGroupsSequence, frame); if (fgs == null) throw new IllegalArgumentException( "Missing (5200,9230) Per-frame Functional Groups Sequence Item for frame #" + (frame + 1)); Attributes dest = new Attributes(emf.size() * 2); dest.addNotSelected(emf, EXCLUDE_TAGS); addFunctionGroups(dest, sfgs); addFunctionGroups(dest, fgs); addPixelData(dest, emf, frame); dest.setString(Tag.SOPClassUID, VR.UI, cuid); dest.setString(Tag.SOPInstanceUID, VR.UI, uidMapper.get( dest.getString(Tag.SOPInstanceUID)) + '.' + (frame + 1)); dest.setString(Tag.InstanceNumber, VR.IS, createInstanceNumber(dest.getString(Tag.InstanceNumber, ""), frame)); dest.setString(Tag.ImageType, VR.CS, dest.getStrings(Tag.FrameType)); dest.remove(Tag.FrameType); if (!preserveSeriesInstanceUID) dest.setString(Tag.SeriesInstanceUID, VR.UI, uidMapper.get( dest.getString(Tag.SeriesInstanceUID))); adjustReferencedImages(dest, Tag.ReferencedImageSequence); adjustReferencedImages(dest, Tag.SourceImageSequence); return dest; } private void adjustReferencedImages(Attributes attrs, int sqtag) { Sequence sq = attrs.getSequence(sqtag); if (sq == null) return; ArrayList<Attributes> newRefs = new ArrayList<Attributes>(); for (Iterator<Attributes> itr = sq.iterator(); itr.hasNext();) { Attributes ref = (Attributes) itr.next(); String cuid = legacySOPClassUID(ref.getString(Tag.ReferencedSOPClassUID)); if (cuid == null) continue; itr.remove(); String iuid = uidMapper.get(ref.getString(Tag.ReferencedSOPInstanceUID)); int[] frames = ref.getInts(Tag.ReferencedFrameNumber); int n = frames == null ? nofAccessor.getNumberOfFrames(iuid) : frames.length; ref.remove(Tag.ReferencedFrameNumber); ref.setString(Tag.ReferencedSOPClassUID, VR.UI, cuid); for (int i = 0; i < n; i++) { Attributes newRef = new Attributes(ref); newRef.setString(Tag.ReferencedSOPInstanceUID, VR.UI, iuid + '.' + (frames != null ? frames[i] : (i+1))); newRefs.add(newRef); } } for (Attributes ref : newRefs) sq.add(ref); } private void addFunctionGroups(Attributes dest, Attributes fgs) { dest.addSelected(fgs, Tag.ReferencedImageSequence); Attributes fg; for (int sqTag : fgs.tags()) if (sqTag != Tag.ReferencedImageSequence && (fg = fgs.getNestedDataset(sqTag)) != null) dest.addAll(fg); } private void addPixelData(Attributes dest, Attributes src, int frame) { VR.Holder vr = new VR.Holder(); Object pixelData = src.getValue(Tag.PixelData, vr); if (pixelData instanceof byte[]) { dest.setBytes(Tag.PixelData, vr.vr, extractPixelData( (byte[]) pixelData, frame, calcFrameLength(src))); } else if (pixelData instanceof BulkData) { dest.setValue(Tag.PixelData, vr.vr, extractPixelData( (BulkData) pixelData, frame, calcFrameLength(src))); } else { Fragments destFrags = dest.newFragments(Tag.PixelData, vr.vr, 2); destFrags.add(null); destFrags.add(((Fragments) pixelData).get(frame + 1)); } } private BulkData extractPixelData(BulkData src, int frame, int length) { return new BulkData(src.uriWithoutQuery(), src.offset() + frame * length, length, src.bigEndian); } private byte[] extractPixelData(byte[] src, int frame, int length) { byte[] dest = new byte[length]; System.arraycopy(src, frame * length, dest, 0, length); return dest; } private int calcFrameLength(Attributes src) { return src.getInt(Tag.Rows, 0) * src.getInt(Tag.Columns, 0) * (src.getInt(Tag.BitsAllocated, 8) >> 3) * src.getInt(Tag.NumberOfSamples, 1); } private String createInstanceNumber(String mfinstno, int frame) { String s = String.format(instanceNumberFormat, mfinstno, frame + 1); return s.length() > 16 ? s.substring(s.length() - 16) : s; } }