/******************************************************************************* * 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.mpr; import java.awt.Dimension; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.BandedSampleModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.PixelInterleavedSampleModel; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.renderable.ParameterBlock; import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.ByteOrder; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.ImageTypeSpecifier; import javax.imageio.stream.ImageInputStream; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.media.jai.operator.NullDescriptor; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.BulkData; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; import org.dcm4che3.data.VR; import org.dcm4che3.io.DicomOutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.explorer.model.DataExplorerModel; import org.weasis.core.api.image.util.ImageFiler; import org.weasis.core.api.image.util.LayoutUtil; import org.weasis.core.api.media.data.Codec; import org.weasis.core.api.media.data.FileCache; import org.weasis.core.api.media.data.MediaElement; import org.weasis.core.api.media.data.MediaSeries; import org.weasis.core.api.media.data.MediaSeriesGroup; import org.weasis.core.api.media.data.TagW; import org.weasis.core.api.util.FileUtil; import org.weasis.dicom.codec.DcmMediaReader; import org.weasis.dicom.codec.DicomMediaIO; import org.weasis.dicom.codec.TagD; import org.weasis.dicom.codec.utils.DicomMediaUtils; import com.sun.media.imageio.stream.RawImageInputStream; import com.sun.media.jai.util.ImageUtil; public class RawImageIO implements DcmMediaReader { private static final Logger LOGGER = LoggerFactory.getLogger(RawImageIO.class); private static final String MIME_TYPE = "image/raw"; //$NON-NLS-1$ private static final int[] OFFSETS_0 = { 0 }; private static final int[] OFFSETS_0_0_0 = { 0, 0, 0 }; private static final int[] OFFSETS_0_1_2 = { 0, 1, 2 }; protected URI uri; private final FileCache fileCache; private final HashMap<TagW, Object> tags; private final Codec codec; private ImageInputStream imageStream; private Attributes attributes; public RawImageIO(URI media, Codec codec) { this.uri = Objects.requireNonNull(media); this.fileCache = new FileCache(this); this.tags = new HashMap<>(); this.codec = codec; } public void setBaseAttributes(Attributes attributes) { this.attributes = attributes; } public File getDicomFile() { Attributes dcm = getDicomObject(); DicomOutputStream out = null; try { File file = new File(uri); BulkData bdl = new BulkData(uri.toString(), 0, (int) file.length(), false); dcm.setValue(Tag.PixelData, VR.OW, bdl); File tmpFile = new File(DicomMediaIO.DICOM_EXPORT_DIR, dcm.getString(Tag.SOPInstanceUID)); out = new DicomOutputStream(tmpFile); out.writeDataset(dcm.createFileMetaInformation(UID.ImplicitVRLittleEndian), dcm); return tmpFile; } catch (IOException e) { LOGGER.error("Cannot write dicom file", e); //$NON-NLS-1$ } finally { FileUtil.safeClose(out); } return null; } @Override public void writeMetaData(MediaSeriesGroup group) { if (group == null) { return; } // Get the dicom header Attributes header = getDicomObject(); DicomMediaUtils.writeMetaData(group, header); // Series Group if (TagW.SubseriesInstanceUID.equals(group.getTagID())) { // Information for series ToolTips group.setTagNoNull(TagD.get(Tag.PatientName), getTagValue(TagD.get(Tag.PatientName))); group.setTagNoNull(TagD.get(Tag.StudyDescription), header.getString(Tag.StudyDescription)); } } @Override public PlanarImage getImageFragment(MediaElement media) throws Exception { if (media != null && media.getFile() != null) { Integer allocated = TagD.getTagValue(media, Tag.BitsAllocated, Integer.class); Integer sample = TagD.getTagValue(media, Tag.SamplesPerPixel, Integer.class); Integer rows = TagD.getTagValue(media, Tag.Rows, Integer.class); Integer columns = TagD.getTagValue(media, Tag.Columns, Integer.class); ImageParameters h = new ImageParameters(rows, columns, allocated, sample, false); // RawImageReader doesn't need to be disposed ImageReader reader = initRawImageReader(imageStream = ImageIO.createImageInputStream(media.getFile()), h, 1, 0, false, TagD.getTagValue(media, Tag.PixelRepresentation, Integer.class)); RenderedImage buffer = reader.readAsRenderedImage(0, null); PlanarImage img = null; if (buffer != null) { if (ImageUtil.isBinary(buffer.getSampleModel())) { ParameterBlock pb = new ParameterBlock(); pb.addSource(buffer); // Tile size are set in this operation img = JAI.create("formatbinary", pb, null); //$NON-NLS-1$ } else if (buffer.getTileWidth() != ImageFiler.TILESIZE || buffer.getTileHeight() != ImageFiler.TILESIZE) { img = ImageFiler.tileImage(buffer); } else { img = NullDescriptor.create(buffer, LayoutUtil.createTiledLayoutHints(buffer)); } } return img; } return null; } @Override public URI getUri() { return uri; } @Override public void reset() { // unlock file to be deleted on exit FileUtil.safeClose(imageStream); imageStream = null; } @Override public MediaElement getPreview() { return null; } @Override public boolean delegate(DataExplorerModel explorerModel) { return false; } @Override public MediaElement[] getMediaElement() { return null; } @Override public MediaSeries<MediaElement> getMediaSeries() { return null; } @Override public int getMediaElementNumber() { return 1; } @Override public String getMediaFragmentMimeType() { return MIME_TYPE; } @Override public Map<TagW, Object> getMediaFragmentTags(Object key) { return tags; } @Override public void close() { reset(); } @Override public Codec getCodec() { return codec; } @Override public String[] getReaderDescription() { return new String[] { "Raw Image Decoder" }; //$NON-NLS-1$ } @Override public Object getTagValue(TagW tag) { return tag == null ? null : tags.get(tag); } @Override public void setTag(TagW tag, Object value) { DicomMediaUtils.setTag(tags, tag, value); } @Override public void setTagNoNull(TagW tag, Object value) { if (value != null) { setTag(tag, value); } } @Override public boolean containTagKey(TagW tag) { return tags.containsKey(tag); } @Override public Iterator<Entry<TagW, Object>> getTagEntrySetIterator() { return tags.entrySet().iterator(); } public void copyTags(TagW[] tagList, MediaElement media, boolean allowNullValue) { if (tagList != null && media != null) { for (TagW tag : tagList) { Object value = media.getTagValue(tag); if (allowNullValue || value != null) { tags.put(tag, value); } } } } @Override public void replaceURI(URI uri) { } public static ImageReader initRawImageReader(ImageInputStream imageStream, ImageParameters h, int frames, int pixelDataPos, boolean bigEndian, int pixelRepresentation) throws IOException { if (imageStream != null) { long[] frameOffsets = new long[frames]; int frameLen = h.getWidth() * h.getHeight() * h.getSamplesPerPixel() * (h.getBitsPerSample() >> 3); ; frameOffsets[0] = pixelDataPos; for (int i = 1; i < frameOffsets.length; i++) { frameOffsets[i] = frameOffsets[i - 1] + frameLen; } Dimension[] imageDimensions = new Dimension[frames]; Arrays.fill(imageDimensions, new Dimension(h.getWidth(), h.getHeight())); RawImageInputStream riis = new RawImageInputStream(imageStream, createImageTypeSpecifier(h, false, pixelRepresentation), frameOffsets, imageDimensions); riis.setByteOrder(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); ImageReader reader = ImageIO.getImageReadersByFormatName("RAW").next(); //$NON-NLS-1$ reader.setInput(riis); return reader; } return null; } public static ImageTypeSpecifier createImageTypeSpecifier(ImageParameters h, boolean banded, int pixelRepresentation) { int width = h.getWidth(); int height = h.getHeight(); int bps = h.getBitsPerSample(); int spp = h.getSamplesPerPixel(); int dataType = bps <= 8 ? DataBuffer.TYPE_BYTE : pixelRepresentation != 0 ? DataBuffer.TYPE_SHORT : DataBuffer.TYPE_USHORT; if (bps > 16 && spp == 1) { dataType = DataBuffer.TYPE_INT; } ColorSpace cs; if (spp == 1) { cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); } else if (spp == 3) { cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); } else { throw new IllegalArgumentException("Unsupported Samples per Pixel: " + spp); //$NON-NLS-1$ } if (cs == null) { throw new IllegalArgumentException("Unsupported Photometric Interpretation: " //$NON-NLS-1$ + " with Samples per Pixel: " + spp); //$NON-NLS-1$ } int[] bits = new int[spp]; Arrays.fill(bits, bps); ComponentColorModel cm = new ComponentColorModel(cs, bits, false, false, Transparency.OPAQUE, dataType); SampleModel sm; if (spp == 1) { sm = new PixelInterleavedSampleModel(dataType, width, height, 1, width, OFFSETS_0); } // samples == 3 else if (banded) { sm = new BandedSampleModel(dataType, width, height, width, OFFSETS_0_1_2, OFFSETS_0_0_0); } else { sm = new PixelInterleavedSampleModel(dataType, width, height, 3, width * 3, OFFSETS_0_1_2); } return new ImageTypeSpecifier(cm, sm); } @Override public Attributes getDicomObject() { Attributes dcm = new Attributes(); DicomMediaUtils.fillAttributes(tags, dcm); dcm.addAll(attributes); return dcm; } @Override public FileCache getFileCache() { return fileCache; } @Override public boolean buildFile(File ouptut) { return false; } }