/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.dicom.viewer2d.mip; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; import java.awt.image.DataBufferShort; import java.awt.image.DataBufferUShort; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import javax.media.jai.JAI; import javax.media.jai.ParameterBlockJAI; import javax.media.jai.PlanarImage; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; import org.dcm4che3.data.VR; import org.dcm4che3.util.UIDUtils; import org.weasis.core.api.gui.task.TaskInterruptionException; import org.weasis.core.api.gui.task.TaskMonitor; import org.weasis.core.api.gui.util.ActionState; import org.weasis.core.api.gui.util.ActionW; import org.weasis.core.api.gui.util.AppProperties; import org.weasis.core.api.gui.util.Filter; import org.weasis.core.api.gui.util.GuiExecutor; import org.weasis.core.api.gui.util.SliderCineListener; import org.weasis.core.api.image.op.MaxCollectionZprojection; import org.weasis.core.api.image.op.MeanCollectionZprojection; import org.weasis.core.api.image.op.MinCollectionZprojection; import org.weasis.core.api.image.util.ImageToolkit; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.api.media.data.MediaSeries; import org.weasis.core.api.media.data.SeriesComparator; import org.weasis.core.api.media.data.TagW; import org.weasis.core.api.util.FileUtil; import org.weasis.core.api.util.StringUtil; import org.weasis.dicom.codec.DcmMediaReader; import org.weasis.dicom.codec.DicomImageElement; import org.weasis.dicom.codec.TagD; import org.weasis.dicom.viewer2d.Messages; import org.weasis.dicom.viewer2d.RawImage; import org.weasis.dicom.viewer2d.View2d; import org.weasis.dicom.viewer2d.mip.MipView.Type; import org.weasis.dicom.viewer2d.mpr.RawImageIO; public class SeriesBuilder { public static final File MPR_CACHE_DIR = AppProperties.buildAccessibleTempDirectory(AppProperties.FILE_CACHE_DIR.getName(), "mip"); //$NON-NLS-1$ private SeriesBuilder() { } public static void applyMipParameters(final TaskMonitor taskMonitor, final View2d view, final MediaSeries<DicomImageElement> series, List<DicomImageElement> dicoms, Type mipType, Integer extend, boolean fullSeries) { PlanarImage curImage = null; if (series != null) { SeriesComparator sort = (SeriesComparator) view.getActionValue(ActionW.SORTSTACK.cmd()); Boolean reverse = (Boolean) view.getActionValue(ActionW.INVERSESTACK.cmd()); Comparator sortFilter = (reverse != null && reverse) ? sort.getReversOrderComparator() : sort; Filter filter = (Filter) view.getActionValue(ActionW.FILTERED_SERIES.cmd()); Iterable<DicomImageElement> medias = series.copyOfMedias(filter, sortFilter); int curImg = extend - 1; ActionState sequence = view.getEventManager().getAction(ActionW.SCROLL_SERIES); if (sequence instanceof SliderCineListener) { SliderCineListener cineAction = (SliderCineListener) sequence; curImg = cineAction.getSliderValue() - 1; } int minImg = fullSeries ? extend : curImg; int maxImg = fullSeries ? series.size(filter) - extend : curImg; if (fullSeries) { taskMonitor.setMaximum(maxImg - minImg); } DicomImageElement img = series.getMedia(MediaSeries.MEDIA_POSITION.MIDDLE, filter, sortFilter); final Attributes attributes = ((DcmMediaReader) img.getMediaReader()).getDicomObject(); final int[] COPIED_ATTRS = { Tag.SpecificCharacterSet, Tag.PatientID, Tag.PatientName, Tag.PatientBirthDate, Tag.PatientBirthTime, Tag.PatientSex, Tag.IssuerOfPatientID, Tag.IssuerOfAccessionNumberSequence, Tag.PatientWeight, Tag.PatientAge, Tag.PatientSize, Tag.PatientState, Tag.PatientComments, Tag.StudyID, Tag.StudyDate, Tag.StudyTime, Tag.StudyDescription, Tag.StudyComments, Tag.AccessionNumber, Tag.ModalitiesInStudy, Tag.Modality, Tag.SeriesDate, Tag.SeriesTime, Tag.RetrieveAETitle, Tag.ReferringPhysicianName, Tag.InstitutionName, Tag.InstitutionalDepartmentName, Tag.StationName, Tag.Manufacturer, Tag.ManufacturerModelName, Tag.SeriesNumber, Tag.KVP, Tag.Laterality, Tag.BodyPartExamined, Tag.FrameOfReferenceUID, Tag.ModalityLUTSequence, Tag.VOILUTSequence }; Arrays.sort(COPIED_ATTRS); final Attributes cpTags = new Attributes(attributes, COPIED_ATTRS); cpTags.setString(Tag.SeriesDescription, VR.LO, attributes.getString(Tag.SeriesDescription, "") + " [MIP]"); //$NON-NLS-1$ //$NON-NLS-2$ cpTags.setString(Tag.ImageType, VR.CS, new String[] { "DERIVED", "SECONDARY", "PROJECTION IMAGE" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ String seriesUID = UIDUtils.createUID(); for (int index = minImg; index <= maxImg; index++) { Iterator<DicomImageElement> iter = medias.iterator(); final List<ImageElement> sources = new ArrayList<>(); int startIndex = index - extend; if (startIndex < 0) { startIndex = 0; } int stopIndex = index + extend; int k = 0; while (iter.hasNext()) { DicomImageElement dcm = iter.next(); if (k >= startIndex) { sources.add(dcm); } if (k >= stopIndex) { break; } k++; } if (sources.size() > 1) { if (fullSeries) { taskMonitor.setShowProgression(false); } curImage = addCollectionOperation(mipType, sources, taskMonitor); } else { curImage = null; } if (fullSeries) { taskMonitor.setShowProgression(true); } final DicomImageElement dicom; if (curImage != null) { DicomImageElement imgRef = (DicomImageElement) sources.get(sources.size() / 2); RawImage raw = null; try { File mipDir = AppProperties.buildAccessibleTempDirectory(AppProperties.FILE_CACHE_DIR.getName(), "mip"); //$NON-NLS-1$ raw = new RawImage(File.createTempFile("mip_", ".raw", mipDir));//$NON-NLS-1$ //$NON-NLS-2$ writeRasterInRaw(curImage.getAsBufferedImage(), raw.getOutputStream()); } catch (Exception e) { e.printStackTrace(); } finally { if (raw != null) { raw.disposeOutputStream(); } } if (raw == null) { return; } RawImageIO rawIO = new RawImageIO(raw.getFile().toURI(), null); rawIO.setBaseAttributes(cpTags); // Tags with same values for all the Series rawIO.setTag(TagD.get(Tag.TransferSyntaxUID), UID.ImplicitVRLittleEndian); rawIO.setTag(TagD.get(Tag.Columns), curImage.getWidth()); rawIO.setTag(TagD.get(Tag.Rows), curImage.getHeight()); rawIO.setTag(TagD.get(Tag.BitsAllocated), imgRef.getBitsAllocated()); rawIO.setTag(TagD.get(Tag.BitsStored), imgRef.getBitsStored()); rawIO.setTag(TagD.get(Tag.SliceThickness), getThickness(sources.get(0), sources.get(sources.size() - 1))); double[] loc = (double[]) imgRef.getTagValue(TagW.SlicePosition); if (loc != null) { rawIO.setTag(TagW.SlicePosition, loc); rawIO.setTag(TagD.get(Tag.SliceLocation), loc[0] + loc[1] + loc[2]); } rawIO.setTag(TagD.get(Tag.SeriesInstanceUID), seriesUID); // Mandatory tags TagW[] mtagList = TagD.getTagFromIDs(Tag.PatientID, Tag.PatientName, Tag.PatientBirthDate, Tag.StudyInstanceUID, Tag.StudyID, Tag.SOPClassUID, Tag.StudyDate, Tag.StudyTime, Tag.AccessionNumber); rawIO.copyTags(mtagList, img, true); rawIO.setTag(TagW.PatientPseudoUID, img.getTagValue(TagW.PatientPseudoUID)); TagW[] tagList = TagD.getTagFromIDs(Tag.PhotometricInterpretation, Tag.PixelRepresentation, Tag.Units, Tag.SamplesPerPixel, Tag.Modality); rawIO.copyTags(tagList, img, true); rawIO.setTag(TagW.MonoChrome, img.getTagValue(TagW.MonoChrome)); TagW[] tagList2 = { TagW.ModalityLUTData, TagW.ModalityLUTType, TagW.ModalityLUTExplanation, TagW.VOILUTsData, TagW.VOILUTsExplanation }; rawIO.copyTags(tagList2, img, false); tagList2 = TagD.getTagFromIDs(Tag.ImageOrientationPatient, Tag.ImagePositionPatient, Tag.RescaleSlope, Tag.RescaleIntercept, Tag.RescaleType, Tag.PixelPaddingValue, Tag.PixelPaddingRangeLimit, Tag.WindowWidth, Tag.WindowCenter, Tag.WindowCenterWidthExplanation, Tag.VOILUTFunction, Tag.PixelSpacing, Tag.ImagerPixelSpacing, Tag.NominalScannedPixelSpacing, Tag.PixelSpacingCalibrationDescription, Tag.PixelAspectRatio); rawIO.copyTags(tagList2, imgRef, false); // Image specific tags rawIO.setTag(TagD.get(Tag.SOPInstanceUID), UIDUtils.createUID()); rawIO.setTag(TagD.get(Tag.InstanceNumber), index + 1); dicom = new DicomImageElement(rawIO, 0) { @Override public boolean saveToFile(File output) { RawImageIO reader = (RawImageIO) getMediaReader(); return FileUtil.nioCopyFile(reader.getDicomFile(), output); } }; dicoms.add(dicom); if (taskMonitor != null && taskMonitor.isCanceled()) { throw new TaskInterruptionException("Rebuilding MIP series has been canceled!"); //$NON-NLS-1$ } final int progress = index - minImg; GuiExecutor.instance().execute(new Runnable() { @Override public void run() { if (taskMonitor != null) { taskMonitor.setProgress(progress); StringBuilder buf = new StringBuilder(Messages.getString("SeriesBuilder.image")); //$NON-NLS-1$ buf.append(StringUtil.COLON_AND_SPACE); buf.append(progress); buf.append("/"); //$NON-NLS-1$ buf.append(taskMonitor.getMaximum()); taskMonitor.setNote(buf.toString()); } } }); } } } } static double getThickness(ImageElement firstDcm, ImageElement lastDcm) { double[] p1 = (double[]) firstDcm.getTagValue(TagW.SlicePosition); double[] p2 = (double[]) lastDcm.getTagValue(TagW.SlicePosition); if (p1 != null && p2 != null) { double diff = Math.abs((p2[0] + p2[1] + p2[2]) - (p1[0] + p1[1] + p1[2])); Double t1 = TagD.getTagValue(firstDcm, Tag.SliceThickness, Double.class); if (t1 != null) { diff += t1 / 2; } t1 = TagD.getTagValue(lastDcm, Tag.SliceThickness, Double.class); if (t1 != null) { diff += t1 / 2; } return diff; } return 1.0; } public static PlanarImage arithmeticOperation(String operation, PlanarImage img1, PlanarImage img2) { ParameterBlockJAI pb2 = new ParameterBlockJAI(operation); pb2.addSource(img1); pb2.addSource(img2); return JAI.create(operation, pb2, ImageToolkit.NOCACHE_HINT); } public static PlanarImage addCollectionOperation(Type mipType, List<ImageElement> sources, final TaskMonitor taskMonitor) { if (Type.MIN.equals(mipType)) { MinCollectionZprojection op = new MinCollectionZprojection(sources, taskMonitor); return op.computeMinCollectionOpImage(); } if (Type.MEAN.equals(mipType)) { MeanCollectionZprojection op = new MeanCollectionZprojection(sources, taskMonitor); return op.computeMeanCollectionOpImage(); } MaxCollectionZprojection op = new MaxCollectionZprojection(sources, taskMonitor); return op.computeMaxCollectionOpImage(); } static void writeRasterInRaw(BufferedImage image, OutputStream out) throws IOException { if (out != null && image != null) { DataBuffer dataBuffer = image.getRaster().getDataBuffer(); byte[] bytesOut = null; if (dataBuffer instanceof DataBufferByte) { bytesOut = ((DataBufferByte) dataBuffer).getData(); } else if (dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) { short[] data = dataBuffer instanceof DataBufferShort ? ((DataBufferShort) dataBuffer).getData() : ((DataBufferUShort) dataBuffer).getData(); bytesOut = new byte[data.length * 2]; for (int i = 0; i < data.length; i++) { bytesOut[i * 2] = (byte) (data[i] & 0xFF); bytesOut[i * 2 + 1] = (byte) ((data[i] >>> 8) & 0xFF); } } else if (dataBuffer instanceof DataBufferInt) { int[] data = ((DataBufferInt) dataBuffer).getData(); bytesOut = new byte[data.length * 4]; for (int i = 0; i < data.length; i++) { bytesOut[i * 4] = (byte) (data[i] & 0xFF); bytesOut[i * 4 + 1] = (byte) ((data[i] >>> 8) & 0xFF); bytesOut[i * 4 + 2] = (byte) ((data[i] >>> 16) & 0xFF); bytesOut[i * 4 + 3] = (byte) ((data[i] >>> 24) & 0xFF); } } out.write(bytesOut); } } }