001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2013 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.emf; 040 041import java.util.ArrayList; 042import java.util.HashMap; 043import java.util.Iterator; 044 045import org.dcm4che3.data.Tag; 046import org.dcm4che3.data.UID; 047import org.dcm4che3.data.Attributes; 048import org.dcm4che3.data.BulkData; 049import org.dcm4che3.data.Fragments; 050import org.dcm4che3.data.Sequence; 051import org.dcm4che3.data.VR; 052 053/** 054 * @author Gunter Zeilinger <gunterze@gmail.com> 055 * 056 */ 057public class MultiframeExtractor { 058 059 private enum Impl { 060 EnhancedCTImageExtractor(UID.CTImageStorage), 061 EnhancedMRImageExtractor(UID.MRImageStorage) { 062 Attributes extract(MultiframeExtractor mfe, Attributes emf, int frame) { 063 Attributes sf = super.extract(mfe, emf, frame); 064 setEchoTime(sf); 065 setScanningSequence(sf); 066 setSequenceVariant(sf); 067 setScanOptions(sf); 068 return sf; 069 } 070 071 void setEchoTime(Attributes sf) { 072 double echoTime = sf.getDouble(Tag.EffectiveEchoTime, 0); 073 if (echoTime == 0) 074 sf.setNull(Tag.EchoTime, VR.DS); 075 else 076 sf.setDouble(Tag.EchoTime, VR.DS, echoTime); 077 } 078 079 void setScanningSequence(Attributes sf) { 080 ArrayList<String> list = new ArrayList<String>(3); 081 082 String eps = sf.getString(Tag.EchoPulseSequence); 083 if (!"GRADIENT".equals(eps)) 084 list.add("SE"); 085 if (!"SPIN".equals(eps)) 086 list.add("GR"); 087 if ("YES".equals(sf.getString(Tag.InversionRecovery))) 088 list.add("IR"); 089 if ("YES".equals(sf.getString(Tag.EchoPlanarPulseSequence))) 090 list.add("EP"); 091 sf.setString(Tag.ScanningSequence, VR.CS, 092 list.toArray(new String[list.size()])); 093 } 094 095 void setSequenceVariant(Attributes sf) { 096 ArrayList<String> list = new ArrayList<String>(5); 097 if (!"SINGLE".equals(sf.getString(Tag.SegmentedKSpaceTraversal))) 098 list.add("SK"); 099 String mf = sf.getString(Tag.MagnetizationTransfer); 100 if (mf != null && !"NONE".equals(mf)) 101 list.add("MTC"); 102 String ssps = sf.getString(Tag.SteadyStatePulseSequence); 103 if (ssps != null && !"NONE".equals(ssps)) 104 list.add("TIME_REVERSED".equals(ssps) ? "TRSS" :"SS"); 105 String sp = sf.getString(Tag.Spoiling); 106 if (sp != null && !"NONE".equals(sp)) 107 list.add("SP"); 108 String op = sf.getString(Tag.OversamplingPhase); 109 if (op != null && !"NONE".equals(op)) 110 list.add("OSP"); 111 if (list.isEmpty()) 112 list.add("NONE"); 113 sf.setString(Tag.SequenceVariant, VR.CS, 114 list.toArray(new String[list.size()])); 115 } 116 117 void setScanOptions(Attributes sf) { 118 ArrayList<String> list = new ArrayList<String>(3); 119 String per = sf.getString(Tag.RectilinearPhaseEncodeReordering); 120 if (per != null && !"LINEAR".equals(per)) 121 list.add("PER"); 122 String frameType3 = sf.getString(Tag.ImageType, 2); 123 if ("ANGIO".equals(frameType3)) 124 sf.setString(Tag.AngioFlag, VR.CS, "Y"); 125 if (frameType3.startsWith("CARD")) 126 list.add("CG"); 127 if (frameType3.endsWith("RESP_GATED")) 128 list.add("RG"); 129 String pfd = sf.getString(Tag.PartialFourierDirection); 130 if ("PHASE".equals(pfd)) 131 list.add("PFP"); 132 if ("FREQUENCY".equals(pfd)) 133 list.add("PFF"); 134 String sp = sf.getString(Tag.SpatialPresaturation); 135 if (sp != null && !"NONE".equals(sp)) 136 list.add("SP"); 137 String sss = sf.getString(Tag.SpectrallySelectedSuppression); 138 if (sss != null && sss.startsWith("FAT")) 139 list.add("FS"); 140 String fc = sf.getString(Tag.FlowCompensation); 141 if (fc != null && !"NONE".equals(fc)) 142 list.add("FC"); 143 sf.setString(Tag.ScanOptions, VR.CS, 144 list.toArray(new String[list.size()])); 145 } 146 147 }, 148 EnhancedPETImageExtractor(UID.PositronEmissionTomographyImageStorage); 149 150 private final String sfcuid; 151 152 Impl(String sfcuid) { 153 this.sfcuid = sfcuid; 154 } 155 156 Attributes extract(MultiframeExtractor mfe, Attributes emf, int frame) { 157 return mfe.extract(emf, frame, sfcuid); 158 } 159 } 160 161 private static final HashMap<String,Impl> impls = new HashMap<String,Impl>(8); 162 static { 163 impls.put(UID.EnhancedCTImageStorage, Impl.EnhancedCTImageExtractor); 164 impls.put(UID.EnhancedMRImageStorage, Impl.EnhancedMRImageExtractor); 165 impls.put(UID.EnhancedPETImageStorage, Impl.EnhancedPETImageExtractor); 166 } 167 168 private static final int[] EXCLUDE_TAGS = { 169 Tag.ReferencedImageEvidenceSequence, 170 Tag.SourceImageEvidenceSequence, 171 Tag.DimensionIndexSequence, 172 Tag.NumberOfFrames, 173 Tag.SharedFunctionalGroupsSequence, 174 Tag.PerFrameFunctionalGroupsSequence, 175 Tag.PixelData }; 176 177 private boolean preserveSeriesInstanceUID; 178 private String instanceNumberFormat = "%s%04d"; 179 private UIDMapper uidMapper = new HashUIDMapper(); 180 private NumberOfFramesAccessor nofAccessor = new NumberOfFramesAccessor(); 181 182 public static boolean isSupportedSOPClass(String cuid) { 183 return impls.containsKey(cuid); 184 } 185 186 public static String legacySOPClassUID(String mfcuid) { 187 Impl impl = impls.get(mfcuid); 188 return impl != null ? impl.sfcuid : null; 189 } 190 191 public final boolean isPreserveSeriesInstanceUID() { 192 return preserveSeriesInstanceUID; 193 } 194 195 public final void setPreserveSeriesInstanceUID( 196 boolean preserveSeriesInstanceUID) { 197 this.preserveSeriesInstanceUID = preserveSeriesInstanceUID; 198 } 199 200 public final String getInstanceNumberFormat() { 201 return instanceNumberFormat; 202 } 203 204 public final void setInstanceNumberFormat(String instanceNumberFormat) { 205 String.format(instanceNumberFormat, "1", 1); 206 this.instanceNumberFormat = instanceNumberFormat; 207 } 208 209 public final UIDMapper getUIDMapper() { 210 return uidMapper; 211 } 212 213 public final void setUIDMapper(UIDMapper uidMapper) { 214 if (uidMapper == null) 215 throw new NullPointerException(); 216 this.uidMapper = uidMapper; 217 } 218 219 public final NumberOfFramesAccessor getNumberOfFramesAccessorr() { 220 return nofAccessor; 221 } 222 223 public final void setNumberOfFramesAccessor(NumberOfFramesAccessor accessor) { 224 if (accessor == null) 225 throw new NullPointerException(); 226 this.nofAccessor = accessor; 227 } 228 229 /** Extract specified frame from Enhanced Multi-frame image and return it 230 * as correponding legacy Single-frame image. 231 * 232 * @param emf Enhanced Multi-frame image 233 * @param frame 0 based frame index 234 * @return legacy Single-frame image 235 */ 236 public Attributes extract(Attributes emf, int frame) { 237 return implFor(emf.getString(Tag.SOPClassUID)) 238 .extract(this, emf, frame); 239 } 240 241 private static Impl implFor(String mfcuid) { 242 Impl impl = impls.get(mfcuid); 243 if (impl == null) 244 throw new IllegalArgumentException( 245 "Unsupported SOP Class: " + mfcuid); 246 return impl; 247 } 248 249 private Attributes extract(Attributes emf, int frame, String cuid) { 250 Attributes sfgs = emf.getNestedDataset(Tag.SharedFunctionalGroupsSequence); 251 if (sfgs == null) 252 throw new IllegalArgumentException( 253 "Missing (5200,9229) Shared Functional Groups Sequence"); 254 Attributes fgs = emf.getNestedDataset(Tag.PerFrameFunctionalGroupsSequence, frame); 255 if (fgs == null) 256 throw new IllegalArgumentException( 257 "Missing (5200,9230) Per-frame Functional Groups Sequence Item for frame #" + (frame + 1)); 258 Attributes dest = new Attributes(emf.size() * 2); 259 dest.addNotSelected(emf, EXCLUDE_TAGS); 260 addFunctionGroups(dest, sfgs); 261 addFunctionGroups(dest, fgs); 262 addPixelData(dest, emf, frame); 263 dest.setString(Tag.SOPClassUID, VR.UI, cuid); 264 dest.setString(Tag.SOPInstanceUID, VR.UI, uidMapper.get( 265 dest.getString(Tag.SOPInstanceUID)) + '.' + (frame + 1)); 266 dest.setString(Tag.InstanceNumber, VR.IS, 267 createInstanceNumber(dest.getString(Tag.InstanceNumber, ""), frame)); 268 dest.setString(Tag.ImageType, VR.CS, dest.getStrings(Tag.FrameType)); 269 dest.remove(Tag.FrameType); 270 if (!preserveSeriesInstanceUID) 271 dest.setString(Tag.SeriesInstanceUID, VR.UI, uidMapper.get( 272 dest.getString(Tag.SeriesInstanceUID))); 273 adjustReferencedImages(dest, Tag.ReferencedImageSequence); 274 adjustReferencedImages(dest, Tag.SourceImageSequence); 275 return dest; 276 } 277 278 private void adjustReferencedImages(Attributes attrs, int sqtag) { 279 Sequence sq = attrs.getSequence(sqtag); 280 if (sq == null) 281 return; 282 283 ArrayList<Attributes> newRefs = new ArrayList<Attributes>(); 284 for (Iterator<Attributes> itr = sq.iterator(); itr.hasNext();) { 285 Attributes ref = (Attributes) itr.next(); 286 String cuid = legacySOPClassUID(ref.getString(Tag.ReferencedSOPClassUID)); 287 if (cuid == null) 288 continue; 289 290 itr.remove(); 291 String iuid = uidMapper.get(ref.getString(Tag.ReferencedSOPInstanceUID)); 292 int[] frames = ref.getInts(Tag.ReferencedFrameNumber); 293 int n = frames == null ? nofAccessor.getNumberOfFrames(iuid) 294 : frames.length; 295 ref.remove(Tag.ReferencedFrameNumber); 296 ref.setString(Tag.ReferencedSOPClassUID, VR.UI, cuid); 297 for (int i = 0; i < n; i++) { 298 Attributes newRef = new Attributes(ref); 299 newRef.setString(Tag.ReferencedSOPInstanceUID, VR.UI, 300 iuid + '.' + (frames != null ? frames[i] : (i+1))); 301 newRefs.add(newRef); 302 } 303 } 304 for (Attributes ref : newRefs) 305 sq.add(ref); 306 } 307 308 private void addFunctionGroups(Attributes dest, Attributes fgs) { 309 dest.addSelected(fgs, Tag.ReferencedImageSequence); 310 Attributes fg; 311 for (int sqTag : fgs.tags()) 312 if (sqTag != Tag.ReferencedImageSequence 313 && (fg = fgs.getNestedDataset(sqTag)) != null) 314 dest.addAll(fg); 315 } 316 317 private void addPixelData(Attributes dest, Attributes src, int frame) { 318 VR.Holder vr = new VR.Holder(); 319 Object pixelData = src.getValue(Tag.PixelData, vr); 320 if (pixelData instanceof byte[]) { 321 dest.setBytes(Tag.PixelData, vr.vr, extractPixelData( 322 (byte[]) pixelData, frame, calcFrameLength(src))); 323 } else if (pixelData instanceof BulkData) { 324 dest.setValue(Tag.PixelData, vr.vr, extractPixelData( 325 (BulkData) pixelData, frame, calcFrameLength(src))); 326 } else { 327 Fragments destFrags = dest.newFragments(Tag.PixelData, vr.vr, 2); 328 destFrags.add(null); 329 destFrags.add(((Fragments) pixelData).get(frame + 1)); 330 } 331 } 332 333 private BulkData extractPixelData(BulkData src, int frame, 334 int length) { 335 return new BulkData(src.uriWithoutQuery(), 336 src.offset() + frame * length, length, 337 src.bigEndian); 338 } 339 340 private byte[] extractPixelData(byte[] src, int frame, int length) { 341 byte[] dest = new byte[length]; 342 System.arraycopy(src, frame * length, dest, 0, length); 343 return dest; 344 } 345 346 private int calcFrameLength(Attributes src) { 347 return src.getInt(Tag.Rows, 0) 348 * src.getInt(Tag.Columns, 0) 349 * (src.getInt(Tag.BitsAllocated, 8) >> 3) 350 * src.getInt(Tag.NumberOfSamples, 1); 351 } 352 353 private String createInstanceNumber(String mfinstno, int frame) { 354 String s = String.format(instanceNumberFormat, mfinstno, frame + 1); 355 return s.length() > 16 ? s.substring(s.length() - 16) : s; 356 } 357 358}