/*******************************************************************************
* 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.Component;
import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
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.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.imageio.IIOException;
import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.RasterFactory;
import javax.media.jai.RenderedOp;
import javax.media.jai.operator.TranslateDescriptor;
import javax.media.jai.operator.TransposeDescriptor;
import javax.media.jai.operator.TransposeType;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.UID;
import org.dcm4che3.data.VR;
import org.dcm4che3.image.PhotometricInterpretation;
import org.dcm4che3.util.UIDUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weasis.core.api.explorer.ObservableEvent;
import org.weasis.core.api.explorer.model.DataExplorerModel;
import org.weasis.core.api.explorer.model.TreeModel;
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.MathUtil;
import org.weasis.core.api.image.util.ImageToolkit;
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.media.data.TagW.TagType;
import org.weasis.core.api.util.FileUtil;
import org.weasis.dicom.codec.DcmMediaReader;
import org.weasis.dicom.codec.DicomImageElement;
import org.weasis.dicom.codec.DicomSeries;
import org.weasis.dicom.codec.SortSeriesStack;
import org.weasis.dicom.codec.TagD;
import org.weasis.dicom.codec.geometry.GeometryOfSlice;
import org.weasis.dicom.codec.utils.DicomMediaUtils;
import org.weasis.dicom.explorer.DicomModel;
import org.weasis.dicom.viewer2d.Messages;
import org.weasis.dicom.viewer2d.RawImage;
import org.weasis.dicom.viewer2d.mpr.MprView.SliceOrientation;
public class SeriesBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(SeriesBuilder.class);
static TagW SeriesReferences = new TagW("series.builder.refs", TagType.STRING, 2, 2); //$NON-NLS-1$
public static final File MPR_CACHE_DIR =
AppProperties.buildAccessibleTempDirectory(AppProperties.FILE_CACHE_DIR.getName(), "mpr"); //$NON-NLS-1$
private SeriesBuilder() {
}
public static void createMissingSeries(Thread thread, MPRContainer mprContainer, final MprView view)
throws Exception {
// TODO test images have all the same size and pixel spacing
MediaSeries<DicomImageElement> series = view.getSeries();
if (series != null) {
SliceOrientation type1 = view.getSliceOrientation();
if (type1 != null) {
String seriesID = (String) series.getTagValue(TagW.SubseriesInstanceUID);
Filter filter = (Filter) view.getActionValue(ActionW.FILTERED_SERIES.cmd());
// Get image stack sort from Reference Coordinates System
DicomImageElement img = SliceOrientation.CORONAL.equals(type1)
? series.getMedia(MediaSeries.MEDIA_POSITION.FIRST, filter, SortSeriesStack.slicePosition)
: series.getMedia(MediaSeries.MEDIA_POSITION.LAST, filter, SortSeriesStack.slicePosition);
if (img != null && img.getMediaReader() instanceof DcmMediaReader) {
GeometryOfSlice geometry = img.getDispSliceGeometry();
if (geometry != null) {
int width = TagD.getTagValue(img, Tag.Columns, Integer.class);
int height = TagD.getTagValue(img, Tag.Rows, Integer.class);
// abort needs to be final array to be changed on "invoqueAndWhait()" block.
final boolean[] abort = new boolean[] { false, false };
if (MathUtil.isDifferent(img.getRescaleX(), img.getRescaleY())) {
// confirmMessage(view, Messages.getString("SeriesBuilder.non_square"), abort);
// //$NON-NLS-1$
width = img.getRescaleWidth(width);
height = img.getRescaleHeight(height);
}
Double tilt = TagD.getTagValue(img, Tag.GantryDetectorTilt, Double.class);
if (tilt != null && MathUtil.isDifferentFromZero(tilt)) {
confirmMessage(view, Messages.getString("SeriesBuilder.gantry"), abort); //$NON-NLS-1$
}
Map<TagW, Object> tags = img.getMediaReader().getMediaFragmentTags(0);
if (tags != null) {
double[] row = geometry.getRowArray();
double[] col = geometry.getColumnArray();
Vector3d vr = new Vector3d(row);
Vector3d vc = new Vector3d(col);
Vector3d resr = new Vector3d();
Vector3d resc = new Vector3d();
final ViewParameter[] recParams = new ViewParameter[2];
String frUID = TagD.getTagValue(series, Tag.FrameOfReferenceUID, String.class);
if (frUID == null) {
frUID = UIDUtils.createUID();
series.setTag(TagD.get(Tag.FrameOfReferenceUID), frUID);
}
final String uid1;
final String uid2;
String[] uidsRef = TagD.getTagValue(series, SeriesReferences, String[].class);
if (uidsRef != null && uidsRef.length == 2) {
uid1 = uidsRef[0];
uid2 = uidsRef[1];
} else {
uid1 = UIDUtils.createUID();
uid2 = UIDUtils.createUID();
series.setTag(SeriesReferences, new String[] { uid1, uid2 });
}
if (SliceOrientation.SAGITTAL.equals(type1)) {
// The reference image is the first of the sagittal stack (Left)
rotate(vc, vr, Math.toRadians(270), resr);
recParams[0] = new ViewParameter(uid1, SliceOrientation.AXIAL, false, null,
new double[] { resr.x, resr.y, resr.z, row[0], row[1], row[2] }, true, true,
new Object[] { 0.0, false }, frUID);
recParams[1] = new ViewParameter(uid2, SliceOrientation.CORONAL, false,
TransposeDescriptor.ROTATE_270,
new double[] { resr.x, resr.y, resr.z, col[0], col[1], col[2] }, true, true,
new Object[] { true, 0.0 }, frUID);
} else if (SliceOrientation.CORONAL.equals(type1)) {
// The reference image is the first of the coronal stack (Anterior)
rotate(vc, vr, Math.toRadians(90), resc);
recParams[0] = new ViewParameter(uid1, SliceOrientation.AXIAL, false, null,
new double[] { row[0], row[1], row[2], resc.x, resc.y, resc.z }, false, true,
new Object[] { 0.0, false }, frUID);
rotate(vc, vr, Math.toRadians(90), resr);
recParams[1] = new ViewParameter(uid2, SliceOrientation.SAGITTAL, true,
TransposeDescriptor.ROTATE_270,
new double[] { resr.x, resr.y, resr.z, col[0], col[1], col[2] }, true, false,
new Object[] { true, 0.0 }, frUID);
} else {
// The reference image is the last of the axial stack (Head)
rotate(vc, vr, Math.toRadians(270), resc);
recParams[0] = new ViewParameter(uid1, SliceOrientation.CORONAL, true, null,
new double[] { row[0], row[1], row[2], resc.x, resc.y, resc.z }, false, false,
new Object[] { 0.0, false }, frUID);
rotate(vr, vc, Math.toRadians(90), resr);
recParams[1] = new ViewParameter(uid2, SliceOrientation.SAGITTAL, true,
TransposeDescriptor.ROTATE_270,
new double[] { col[0], col[1], col[2], resr.x, resr.y, resr.z }, false, false,
new Object[] { true, 0.0 }, frUID);
}
final MprView[] recView = new MprView[2];
recView[0] = mprContainer.getMprView(recParams[0].sliceOrientation);
recView[1] = mprContainer.getMprView(recParams[1].sliceOrientation);
if (recView[0] == null || recView[1] == null) {
return;
}
final MprView mainView = mprContainer.getMprView(type1);
mainView.zoom(0.0);
mainView.center();
final boolean[] needBuild = new boolean[2];
MediaSeriesGroup study = null;
DataExplorerModel model = (DataExplorerModel) series.getTagValue(TagW.ExplorerModel);
TreeModel treeModel = null;
if (model instanceof TreeModel) {
treeModel = (TreeModel) model;
study = treeModel.getParent(series, DicomModel.study);
if (study != null) {
for (int i = 0; i < 2; i++) {
final MediaSeriesGroup group =
treeModel.getHierarchyNode(study, recParams[i].seriesUID);
needBuild[i] = group == null;
if (!needBuild[i]) {
final MprView mprView = recView[i];
GuiExecutor.instance().execute(() -> {
mprView.setSeries((MediaSeries<DicomImageElement>) group);
// Copy the synch values from the main view
for (String action : MPRContainer.DEFAULT_MPR.getSynchData()
.getActions().keySet()) {
mprView.setActionsInView(action, view.getActionValue(action));
}
mprView.zoom(mainView.getViewModel().getViewScale());
mprView.center();
mprView.repaint();
});
}
}
}
}
final int size = series.size(filter);
final JProgressBar[] bar = new JProgressBar[2];
GuiExecutor.instance().invokeAndWait(() -> {
for (int i = 0; i < 2; i++) {
if (needBuild[i]) {
bar[i] = new JProgressBar(0, size);
Dimension dim = new Dimension(recView[i].getWidth() / 2, 30);
bar[i].setSize(dim);
bar[i].setPreferredSize(dim);
bar[i].setMaximumSize(dim);
bar[i].setValue(0);
bar[i].setStringPainted(true);
recView[i].setProgressBar(bar[i]);
recView[i].repaint();
}
}
});
// Get the image in the middle of the series for having better default W/L values
img = series.getMedia(MediaSeries.MEDIA_POSITION.MIDDLE, filter,
SortSeriesStack.slicePosition);
final Attributes attributes = ((DcmMediaReader) img.getMediaReader()).getDicomObject();
for (int i = 0; i < 2; i++) {
if (needBuild[i]) {
final MprView mprView = recView[i];
final ViewParameter viewParams = recParams[i];
Iterable<DicomImageElement> medias = series.copyOfMedias(filter,
viewParams.reverseSeriesOrder
? SortSeriesStack.slicePosition.getReversOrderComparator()
: SortSeriesStack.slicePosition);
double origPixSize = img.getPixelSize();
RawImage[] secSeries = new RawImage[i == 0 ? height : width];
/*
* Write the new image by tacking the lines (from first to last) of all the images
* of the original series stack
*/
double sPixSize = writeBlock(secSeries, series, medias, viewParams, mprView, thread,
abort, seriesID);
if (thread.isInterrupted()) {
return;
}
/*
* Reconstruct dicom files, adapt position, orientation, pixel spacing, instance
* number and UIDs.
*/
final DicomSeries dicomSeries =
buildDicomSeriesFromRaw(secSeries, new Dimension(i == 0 ? width : height, size),
img, viewParams, origPixSize, sPixSize, geometry, mprView, attributes);
if (dicomSeries != null && dicomSeries.size(null) > 0) {
((DcmMediaReader) dicomSeries.getMedia(0, null, null).getMediaReader())
.writeMetaData(dicomSeries);
if (study != null && treeModel != null) {
dicomSeries.setTag(TagW.ExplorerModel, model);
treeModel.addHierarchyNode(study, dicomSeries);
if (treeModel instanceof DicomModel) {
DicomModel dicomModel = (DicomModel) treeModel;
dicomModel.firePropertyChange(new ObservableEvent(
ObservableEvent.BasicAction.ADD, dicomModel, null, dicomSeries));
}
}
GuiExecutor.instance().execute(() -> {
mprView.setProgressBar(null);
mprView.setSeries(dicomSeries);
// Copy the synch values from the main view
for (String action : MPRContainer.DEFAULT_MPR.getSynchData().getActions()
.keySet()) {
mprView.setActionsInView(action, view.getActionValue(action));
}
mprView.zoom(mainView.getViewModel().getViewScale());
mprView.center();
mprView.repaint();
});
}
}
}
}
}
}
}
}
}
private static DicomSeries buildDicomSeriesFromRaw(final RawImage[] newSeries, Dimension dim, DicomImageElement img,
ViewParameter params, double origPixSize, double sPixSize, GeometryOfSlice geometry, final MprView view,
final Attributes attributes) throws Exception {
int bitsAllocated = img.getBitsAllocated();
int bitsStored = img.getBitsStored();
double[] pixSpacing = new double[] { sPixSize, origPixSize };
int dataType = 0;
ColorModel cm = null;
SampleModel sampleModel = null;
final JProgressBar bar = view.getProgressBar();
if (params.rotateOutputImg) {
if (bar != null) {
GuiExecutor.instance().execute(() -> {
bar.setMaximum(newSeries.length);
bar.setValue(0);
// Force to reset the progress bar (substance)
bar.updateUI();
view.repaint();
});
}
pixSpacing = new double[] { origPixSize, sPixSize };
int samplesPerPixel = TagD.getTagValue(img, Tag.SamplesPerPixel, Integer.class);
boolean banded = samplesPerPixel > 1
&& DicomMediaUtils.getIntegerFromDicomElement(attributes, Tag.PlanarConfiguration, 0) != 0;
int pixelRepresentation =
DicomMediaUtils.getIntegerFromDicomElement(attributes, Tag.PixelRepresentation, 0);
dataType = bitsAllocated <= 8 ? DataBuffer.TYPE_BYTE
: pixelRepresentation != 0 ? DataBuffer.TYPE_SHORT : DataBuffer.TYPE_USHORT;
if (bitsAllocated > 16 && samplesPerPixel == 1) {
dataType = DataBuffer.TYPE_INT;
}
String photometricInterpretation = TagD.getTagValue(img, Tag.PhotometricInterpretation, String.class);
PhotometricInterpretation pmi = PhotometricInterpretation.fromString(photometricInterpretation);
cm = pmi.createColorModel(bitsStored, dataType, attributes);
sampleModel = pmi.createSampleModel(dataType, dim.width, dim.height, samplesPerPixel, banded);
int tmp = dim.width;
dim.width = dim.height;
dim.height = tmp;
}
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.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, "") + " [MPR]"); //$NON-NLS-1$ //$NON-NLS-2$
cpTags.setString(Tag.ImageType, VR.CS, new String[] { "DERIVED", "SECONDARY", "MPR" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
cpTags.setString(Tag.FrameOfReferenceUID, VR.UI, params.frameOfReferenceUID);
int last = newSeries.length;
List<DicomImageElement> dcms = new ArrayList<>();
for (int i = 0; i < newSeries.length; i++) {
File inFile = newSeries[i].getFile();
if (params.rotateOutputImg) {
ByteBuffer byteBuffer = getBytesFromFile(inFile);
DataBuffer dataBuffer = null;
if (dataType == DataBuffer.TYPE_BYTE) {
dataBuffer = new DataBufferByte(byteBuffer.array(), byteBuffer.limit());
} else if (dataType <= DataBuffer.TYPE_SHORT) {
ShortBuffer sBuffer = byteBuffer.asShortBuffer();
short[] data;
if (sBuffer.hasArray()) {
data = sBuffer.array();
} else {
data = new short[byteBuffer.limit() / 2];
for (int k = 0; k < data.length; k++) {
if (byteBuffer.hasRemaining()) {
data[k] = byteBuffer.getShort();
}
}
}
dataBuffer = dataType == DataBuffer.TYPE_SHORT ? new DataBufferShort(data, data.length)
: new DataBufferUShort(data, data.length);
} else if (dataType == DataBuffer.TYPE_INT) {
IntBuffer sBuffer = byteBuffer.asIntBuffer();
int[] data;
if (sBuffer.hasArray()) {
data = sBuffer.array();
} else {
data = new int[byteBuffer.limit() / 4];
for (int k = 0; k < data.length; k++) {
if (byteBuffer.hasRemaining()) {
data[k] = byteBuffer.getInt();
}
}
}
dataBuffer = new DataBufferInt(data, data.length);
}
WritableRaster raster = RasterFactory.createWritableRaster(sampleModel, dataBuffer, null);
BufferedImage bufImg = new BufferedImage(cm, raster, false, null);
bufImg = getImage(bufImg, TransposeDescriptor.ROTATE_90);
dataBuffer = bufImg.getRaster().getDataBuffer();
if (dataBuffer instanceof DataBufferByte) {
byteBuffer = ByteBuffer.wrap(((DataBufferByte) dataBuffer).getData());
writToFile(inFile, byteBuffer);
} else if (dataBuffer instanceof DataBufferShort || dataBuffer instanceof DataBufferUShort) {
short[] data = dataBuffer instanceof DataBufferShort ? ((DataBufferShort) dataBuffer).getData()
: ((DataBufferUShort) dataBuffer).getData();
byteBuffer = ByteBuffer.allocate(data.length * 2);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
shortBuffer.put(data);
writToFile(inFile, byteBuffer);
} else if (dataBuffer instanceof DataBufferInt) {
int[] data = ((DataBufferInt) dataBuffer).getData();
byteBuffer = ByteBuffer.allocate(data.length * 4);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
IntBuffer intBuffer = byteBuffer.asIntBuffer();
intBuffer.put(data);
writToFile(inFile, byteBuffer);
}
if (bar != null) {
GuiExecutor.instance().execute(() -> {
bar.setValue(bar.getValue() + 1);
view.repaint();
});
}
}
RawImageIO rawIO = new RawImageIO(inFile.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), dim.width);
rawIO.setTag(TagD.get(Tag.Rows), dim.height);
rawIO.setTag(TagD.get(Tag.SliceThickness), origPixSize);
rawIO.setTag(TagD.get(Tag.PixelSpacing), pixSpacing);
rawIO.setTag(TagD.get(Tag.SeriesInstanceUID), params.seriesUID);
rawIO.setTag(TagD.get(Tag.ImageOrientationPatient), params.imgOrientation);
rawIO.setTag(TagD.get(Tag.BitsAllocated), bitsAllocated);
rawIO.setTag(TagD.get(Tag.BitsStored), bitsStored);
// 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.RescaleSlope, Tag.RescaleIntercept, Tag.RescaleType,
Tag.PixelPaddingValue, Tag.PixelPaddingRangeLimit, Tag.WindowWidth, Tag.WindowCenter,
Tag.WindowCenterWidthExplanation, Tag.VOILUTFunction, Tag.PixelSpacingCalibrationDescription);
rawIO.copyTags(tagList2, img, false);
// Clone array, because values are adapted according to the min and max pixel values.
TagW[] tagList3 = TagD.getTagFromIDs(Tag.WindowWidth, Tag.WindowCenter);
for (int j = 0; j < tagList3.length; j++) {
double[] val = (double[]) img.getTagValue(tagList3[j]);
if (val != null) {
img.setTag(tagList3[j], Arrays.copyOf(val, val.length));
}
}
// Image specific tags
int index = i;
rawIO.setTag(TagD.get(Tag.SOPInstanceUID), UIDUtils.createUID());
rawIO.setTag(TagD.get(Tag.InstanceNumber), params.reverseIndexOrder ? last - index : index + 1);
double x = (params.imgPosition[0] instanceof Double) ? (Double) params.imgPosition[0]
: (Boolean) params.imgPosition[0] ? last - index - 1 : index;
double y = (params.imgPosition[1] instanceof Double) ? (Double) params.imgPosition[1]
: (Boolean) params.imgPosition[1] ? last - index - 1 : index;
Point3d p = geometry.getPosition(new Point2D.Double(x, y));
rawIO.setTag(TagD.get(Tag.ImagePositionPatient), new double[] { p.x, p.y, p.z });
DicomMediaUtils.computeSlicePositionVector(rawIO);
double[] loc = (double[]) rawIO.getTagValue(TagW.SlicePosition);
if (loc != null) {
rawIO.setTag(TagD.get(Tag.SliceLocation), loc[0] + loc[1] + loc[2]);
}
DicomImageElement dcm = new DicomImageElement(rawIO, 0) {
@Override
public boolean saveToFile(File output) {
RawImageIO reader = (RawImageIO) getMediaReader();
return FileUtil.nioCopyFile(reader.getDicomFile(), output);
}
};
dcms.add(dcm);
}
return new DicomSeries(params.seriesUID, dcms, DicomModel.series.getTagView());
}
private static double writeBlock(RawImage[] newSeries, MediaSeries<DicomImageElement> series,
Iterable<DicomImageElement> medias, ViewParameter params, final MprView view, Thread thread,
final boolean[] abort, String seriesID) throws IOException {
// TODO should return the more frequent space!
final JProgressBar bar = view.getProgressBar();
try {
File dir = new File(MPR_CACHE_DIR, params.seriesUID);
dir.mkdirs();
for (int i = 0; i < newSeries.length; i++) {
newSeries[i] = new RawImage(new File(dir, "mpr_" + (i + 1)));//$NON-NLS-1$
}
double epsilon = 1e-3;
double lastPos = 0.0;
double lastSpace = 0.0;
int index = 0;
Iterator<DicomImageElement> iter = medias.iterator();
while (iter.hasNext()) {
if (thread.isInterrupted()) {
return lastSpace;
}
DicomImageElement dcm = iter.next();
double[] sp = (double[]) dcm.getTagValue(TagW.SlicePosition);
if (sp == null && !abort[1]) {
confirmMessage(view, Messages.getString("SeriesBuilder.space_missing"), abort); //$NON-NLS-1$
} else {
double pos = sp[0] + sp[1] + sp[2];
if (index > 0) {
double space = Math.abs(pos - lastPos);
if (!abort[1]
&& (MathUtil.isEqualToZero(space) || (index > 1 && lastSpace - space > epsilon))) {
confirmMessage(view, Messages.getString("SeriesBuilder.space"), abort); //$NON-NLS-1$
}
lastSpace = space;
}
lastPos = pos;
index++;
if (bar != null) {
GuiExecutor.instance().execute(() -> {
bar.setValue(bar.getValue() + 1);
view.repaint();
});
}
}
// TODO do not open more than 512 files (Limitation to open 1024 in the same time on Ubuntu)
PlanarImage image = dcm.getImage();
if (image == null) {
abort[0] = true;
throw new IIOException("Cannot read an image!"); //$NON-NLS-1$
}
if (MathUtil.isDifferent(dcm.getRescaleX(), dcm.getRescaleY())) {
ParameterBlock pb = new ParameterBlock();
pb.addSource(image);
pb.add((float) dcm.getRescaleX()).add((float) dcm.getRescaleY()).add(0.0f).add(0.0f);
pb.add(Interpolation.getInstance(Interpolation.INTERP_BILINEAR));
image = JAI.create("scale", pb, ImageToolkit.NOCACHE_HINT); //$NON-NLS-1$
}
writeRasterInRaw(getImage(image, params.transposeImage), newSeries);
}
return lastSpace;
} finally {
for (int i = 0; i < newSeries.length; i++) {
if (newSeries[i] != null) {
newSeries[i].disposeOutputStream();
if (abort[0]) {
newSeries[i].getFile().delete();
}
}
}
}
}
private static void writeRasterInRaw(BufferedImage image, RawImage[] newSeries) throws IOException {
if (newSeries != null && image != null && image.getHeight() == newSeries.length) {
DataBuffer dataBuffer = image.getRaster().getDataBuffer();
int width = image.getWidth();
int height = newSeries.length;
byte[] bytesOut;
if (dataBuffer instanceof DataBufferByte) {
bytesOut = ((DataBufferByte) dataBuffer).getData();
for (int j = 0; j < height; j++) {
newSeries[j].getOutputStream().write(bytesOut, j * width, width);
}
} 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);
}
width *= 2;
for (int j = 0; j < height; j++) {
newSeries[j].getOutputStream().write(bytesOut, j * width, width);
}
} 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);
}
width *= 4;
for (int j = 0; j < height; j++) {
newSeries[j].getOutputStream().write(bytesOut, j * width, width);
}
}
}
}
private static BufferedImage getImage(PlanarImage source, TransposeType rotate) {
if (rotate == null) {
return source == null ? null : source.getAsBufferedImage();
}
return getRotatedImage(source, rotate);
}
private static BufferedImage getImage(BufferedImage source, TransposeType rotate) {
if (rotate == null) {
return source == null ? null : source;
}
return getRotatedImage(source, rotate);
}
private static BufferedImage getRotatedImage(RenderedImage source, TransposeType rotate) {
RenderedOp result;
if (source instanceof BufferedImage) {
source = PlanarImage.wrapRenderedImage(source);
}
// use Transpose operation
result = TransposeDescriptor.create(source, rotate, ImageToolkit.NOCACHE_HINT);
// Handle non square images. Translation is necessary because the transpose operator keeps the same
// origin (top left not the center of the image)
float diffw = source.getWidth() / 2.0f - result.getWidth() / 2.0f;
float diffh = source.getHeight() / 2.0f - result.getHeight() / 2.0f;
if (MathUtil.isDifferentFromZero(diffw) || MathUtil.isDifferentFromZero(diffh)) {
result = TranslateDescriptor.create(result, diffw, diffh, null, ImageToolkit.NOCACHE_HINT);
}
return result.getAsBufferedImage();
}
private static void rotate(Vector3d vSrc, Vector3d axis, double angle, Vector3d vDst) {
axis.normalize();
vDst.x = axis.x * (axis.x * vSrc.x + axis.y * vSrc.y + axis.z * vSrc.z) * (1 - Math.cos(angle))
+ vSrc.x * Math.cos(angle) + (-axis.z * vSrc.y + axis.y * vSrc.z) * Math.sin(angle);
vDst.y = axis.y * (axis.x * vSrc.x + axis.y * vSrc.y + axis.z * vSrc.z) * (1 - Math.cos(angle))
+ vSrc.y * Math.cos(angle) + (axis.z * vSrc.x - axis.x * vSrc.z) * Math.sin(angle);
vDst.z = axis.z * (axis.x * vSrc.x + axis.y * vSrc.y + axis.z * vSrc.z) * (1 - Math.cos(angle))
+ vSrc.z * Math.cos(angle) + (-axis.y * vSrc.x + axis.x * vSrc.y) * Math.sin(angle);
}
public static ByteBuffer getBytesFromFile(File file) {
FileInputStream is = null;
try {
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
is = new FileInputStream(file);
FileChannel in = is.getChannel();
in.read(byteBuffer);
byteBuffer.flip();
return byteBuffer;
} catch (Exception e) {
LOGGER.error("Get bytes", e); //$NON-NLS-1$
} finally {
FileUtil.safeClose(is);
}
return null;
}
public static void writToFile(File file, ByteBuffer byteBuffer) {
FileOutputStream os = null;
try {
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
os = new FileOutputStream(file);
FileChannel out = os.getChannel();
out.write(byteBuffer);
} catch (Exception e) {
LOGGER.error("Write bytes", e); //$NON-NLS-1$
} finally {
FileUtil.safeClose(os);
}
}
public static void confirmMessage(final Component view, final String message, final boolean[] abort) {
GuiExecutor.instance().invokeAndWait(() -> {
int usrChoice = JOptionPane.showConfirmDialog(view, message + Messages.getString("SeriesBuilder.add_warn"), //$NON-NLS-1$
MPRFactory.NAME, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (usrChoice == JOptionPane.NO_OPTION) {
abort[0] = true;
} else {
// bypass for other similar messages
abort[1] = true;
}
});
if (abort[0]) {
throw new IllegalStateException(message);
}
}
static class ViewParameter {
final String seriesUID;
final SliceOrientation sliceOrientation;
final boolean reverseSeriesOrder;
final TransposeType transposeImage;
final double[] imgOrientation;
final boolean rotateOutputImg;
final boolean reverseIndexOrder;
final Object[] imgPosition;
final String frameOfReferenceUID;
public ViewParameter(String seriesUID, SliceOrientation sliceOrientation, boolean reverseSeriesOrder,
TransposeType transposeImage, double[] imgOrientation, boolean rotateOutputImg, boolean reverseIndexOrder,
Object[] imgPosition, String frameOfReferenceUID) {
this.seriesUID = seriesUID;
this.sliceOrientation = sliceOrientation;
this.reverseSeriesOrder = reverseSeriesOrder;
this.transposeImage = transposeImage;
this.imgOrientation = imgOrientation;
this.rotateOutputImg = rotateOutputImg;
this.reverseIndexOrder = reverseIndexOrder;
this.imgPosition = imgPosition;
this.frameOfReferenceUID = frameOfReferenceUID;
}
}
}