/*******************************************************************************
* 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.acquire.explorer;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.util.UIDUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weasis.acquire.explorer.core.bean.SeriesGroup;
import org.weasis.core.api.image.AutoLevelsOp;
import org.weasis.core.api.image.BrightnessOp;
import org.weasis.core.api.image.CropOp;
import org.weasis.core.api.image.FlipOp;
import org.weasis.core.api.image.ImageOpNode;
import org.weasis.core.api.image.RotationOp;
import org.weasis.core.api.image.SimpleOpManager;
import org.weasis.core.api.image.ZoomOp;
import org.weasis.core.api.media.data.ImageElement;
import org.weasis.core.api.media.data.TagUtil;
import org.weasis.core.api.media.data.TagW;
import org.weasis.core.api.util.StringUtil;
import org.weasis.core.ui.editor.image.ViewCanvas;
import org.weasis.core.ui.model.GraphicModel;
import org.weasis.core.ui.model.layer.Layer;
import org.weasis.core.ui.model.layer.LayerType;
import org.weasis.core.ui.model.utils.imp.DefaultViewModel;
import org.weasis.dicom.codec.TagD;
import org.weasis.dicom.codec.TagD.Level;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifDirectoryBase;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.exif.ExifSubIFDDirectory;
/**
*
* @author Yannick LARVOR
* @version 2.5.0
* @since 2.5.0 - 2016-04-11 - ylar - Creation
*/
public class AcquireImageInfo {
private static final Logger LOGGER = LoggerFactory.getLogger(AcquireImageInfo.class);
private final ImageElement image;
private SeriesGroup seriesGroup;
private final Attributes attributes;
private Layer layer;
private AcquireImageStatus status;
private final SimpleOpManager preProcessOpManager;
private final SimpleOpManager postProcessOpManager;
private AcquireImageValues defaultValues;
private AcquireImageValues currentValues;
private AcquireImageValues nextValues;
private final List<AcquireImageValues> steps;
private String comment;
public AcquireImageInfo(ImageElement image) {
this.image = Objects.requireNonNull(image);
readTags(image);
this.setStatus(AcquireImageStatus.TO_PUBLISH);
this.attributes = new Attributes();
this.preProcessOpManager = new SimpleOpManager();
this.postProcessOpManager = new SimpleOpManager();
this.postProcessOpManager.addImageOperationAction(new BrightnessOp());
this.postProcessOpManager.addImageOperationAction(new AutoLevelsOp());
this.postProcessOpManager.addImageOperationAction(new RotationOp());
this.postProcessOpManager.addImageOperationAction(new FlipOp());
this.postProcessOpManager.addImageOperationAction(new CropOp());
this.postProcessOpManager.addImageOperationAction(new ZoomOp());
defaultValues = new AcquireImageValues();
currentValues = defaultValues.copy();
nextValues = defaultValues.copy();
steps = new ArrayList<>();
steps.add(currentValues);
}
public List<AcquireImageValues> getSteps() {
return steps;
}
public String getUID() {
return TagD.getTagValue(image, Tag.SOPInstanceUID, String.class);
}
public ImageElement getImage() {
return image;
}
public Layer getLayer() {
return layer;
}
public Attributes getAttributes() {
return attributes;
}
public SimpleOpManager getPreProcessOpManager() {
return this.preProcessOpManager;
}
public SimpleOpManager getPostProcessOpManager() {
return this.postProcessOpManager;
}
private static void addImageOperationAction(SimpleOpManager manager, ImageOpNode action) {
manager.addImageOperationAction(action);
}
private static void removeImageOpertationAction(SimpleOpManager manager, Class<? extends ImageOpNode> cls) {
for (ImageOpNode op : manager.getOperations()) {
if (cls.isInstance(op)) {
manager.removeImageOperationAction(op);
break;
}
}
}
public void addPreProcessImageOperationAction(ImageOpNode action) {
addImageOperationAction(preProcessOpManager, action);
}
public void removePreProcessImageOperationAction(Class<? extends ImageOpNode> cls) {
removeImageOpertationAction(preProcessOpManager, cls);
}
public void addPostProcessImageOperationAction(ImageOpNode action) {
addImageOperationAction(postProcessOpManager, action);
}
public void applyPostProcess(ViewCanvas<ImageElement> view) {
boolean dirty = isDirty();
if (dirty) {
postProcessOpManager.setParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE, nextValues.getFullRotation());
if (!Objects.equals(nextValues.getCropZone(), currentValues.getCropZone())) {
Rectangle area = nextValues.getCropZone();
postProcessOpManager.setParamValue(CropOp.OP_NAME, CropOp.P_AREA, area);
postProcessOpManager.setParamValue(CropOp.OP_NAME, CropOp.P_SHIFT_TO_ORIGIN, true);
if (view != null && area != null && !area.equals(view.getViewModel().getModelArea())) {
((DefaultViewModel) view.getViewModel()).adjustMinViewScaleFromImage(area.width, area.height);
view.getViewModel().setModelArea(new Rectangle(0, 0, area.width, area.height));
view.getImageLayer().setOffset(new Point(area.x, area.y));
view.resetZoom();
}
}
if (nextValues.getBrightness() != currentValues.getBrightness()
|| nextValues.getContrast() != currentValues.getContrast()) {
postProcessOpManager.setParamValue(BrightnessOp.OP_NAME, BrightnessOp.P_BRIGTNESS_VALUE,
(double) nextValues.getBrightness());
postProcessOpManager.setParamValue(BrightnessOp.OP_NAME, BrightnessOp.P_CONTRAST_VALUE,
(double) nextValues.getContrast());
}
postProcessOpManager.setParamValue(AutoLevelsOp.OP_NAME, AutoLevelsOp.P_AUTO_LEVEL,
nextValues.isAutoLevel());
postProcessOpManager.setParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP, nextValues.isFlip());
if (nextValues.getRatio() != currentValues.getRatio()) {
postProcessOpManager.setParamValue(ZoomOp.OP_NAME, ZoomOp.P_RATIO_X, nextValues.getRatio());
postProcessOpManager.setParamValue(ZoomOp.OP_NAME, ZoomOp.P_RATIO_Y, nextValues.getRatio());
postProcessOpManager.setParamValue(ZoomOp.OP_NAME, ZoomOp.P_INTERPOLATION, ZoomOp.INTERPOLATIONS[1]);
}
if (view != null) {
// Reset preprocess cache
postProcessOpManager.resetLastNodeOutputImage();
view.getImageLayer().setImage(image, postProcessOpManager);
updateTags(view.getImage());
}
preProcessOpManager.removeAllImageOperationAction();
// Next value become the current value. Register the step.
currentValues = nextValues;
nextValues = currentValues.copy();
steps.add(currentValues.copy());
}
}
public void applyPreProcess(ViewCanvas<ImageElement> view) {
for (ImageOpNode action : postProcessOpManager.getOperations()) {
if (preProcessOpManager.getNode(action.getName()) == null) {
preProcessOpManager.addImageOperationAction(action.copy());
}
}
if (view != null) {
view.getImageLayer().setImage(view.getImage(), preProcessOpManager);
}
}
public void removeLayer(ViewCanvas<ImageElement> view) {
if (view != null) {
GraphicModel gm = view.getGraphicManager();
gm.deleteByLayerType(LayerType.ACQUIRE);
view.getJComponent().repaint();
}
}
private void updateTags(ImageElement image) {
this.image.setTag(TagW.ImageWidth, image.getTagValue(TagW.ImageWidth));
this.image.setTag(TagW.ImageHeight, image.getTagValue(TagW.ImageHeight));
}
public void clearPreProcess() {
preProcessOpManager.removeAllImageOperationAction();
}
public AcquireImageValues getNextValues() {
return nextValues;
}
public AcquireImageValues getCurrentValues() {
return currentValues;
}
public AcquireImageValues getDefaultValues() {
return defaultValues;
}
public boolean isDirtyFromDefault() {
return !defaultValues.equals(nextValues);
}
public boolean isDirty() {
return !currentValues.equals(nextValues);
}
public AcquireImageValues restore(ViewCanvas<ImageElement> view) {
image.setPixelSpacingUnit(defaultValues.getCalibrationUnit());
image.setPixelSize(defaultValues.getCalibrationRatio());
postProcessOpManager.setParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE, defaultValues.getOrientation());
postProcessOpManager.setParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP, defaultValues.isFlip());
postProcessOpManager.setParamValue(CropOp.OP_NAME, CropOp.P_AREA, null);
postProcessOpManager.setParamValue(CropOp.OP_NAME, CropOp.P_SHIFT_TO_ORIGIN, null);
postProcessOpManager.setParamValue(BrightnessOp.OP_NAME, BrightnessOp.P_BRIGTNESS_VALUE,
(double) defaultValues.getBrightness());
postProcessOpManager.setParamValue(BrightnessOp.OP_NAME, BrightnessOp.P_CONTRAST_VALUE,
(double) defaultValues.getContrast());
postProcessOpManager.setParamValue(AutoLevelsOp.OP_NAME, AutoLevelsOp.P_AUTO_LEVEL,
defaultValues.isAutoLevel());
if (view != null) {
view.getImageLayer().setImage(image, postProcessOpManager);
}
steps.clear();
steps.add(defaultValues);
currentValues = defaultValues.copy();
nextValues = defaultValues.copy();
return defaultValues;
}
public SeriesGroup getSeries() {
return seriesGroup;
}
public void setSeries(SeriesGroup seriesGroup) {
this.seriesGroup = seriesGroup;
if (seriesGroup != null) {
image.setTag(TagD.get(Tag.SeriesInstanceUID), seriesGroup.getUID());
String seriesDescription = TagD.getTagValue(seriesGroup, Tag.SeriesDescription, String.class);
if (!StringUtil.hasText(seriesDescription) && seriesGroup.getType() != SeriesGroup.Type.NONE) {
seriesGroup.setTag(TagD.get(Tag.SeriesDescription), seriesGroup.getDisplayName());
}
}
}
@Override
public String toString() {
return Optional.ofNullable(image).map(ImageElement::getName).orElseGet(() -> ""); //$NON-NLS-1$
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public AcquireImageStatus getStatus() {
return status;
}
public void setStatus(AcquireImageStatus status) {
this.status = Objects.requireNonNull(status);
}
public static final Consumer<AcquireImageInfo> changeStatus(AcquireImageStatus status) {
return imgInfo -> imgInfo.setStatus(status);
}
/**
* Check if ImageElement has a SOPInstanceUID TAG value and if not create a new UUID. Read Exif metaData from from
* original file and populate relevant ImageElement TAGS. <br>
*
* @param imageElement
*/
private static void readTags(ImageElement imageElement) {
// Create a SOPInstanceUID if not present
TagW tagUid = TagD.getUID(Level.INSTANCE);
String uuid = (String) imageElement.getTagValue(tagUid);
if (uuid == null) {
uuid = UIDUtils.createUID();
imageElement.setTag(tagUid, uuid);
}
// Extract information from Exif TAG
Optional<File> file = imageElement.getFileCache().getOriginalFile();
if (file.isPresent()) {
Date date = null;
try {
Metadata metadata = ImageMetadataReader.readMetadata(file.get());
if (metadata != null) {
ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
if (directory != null) {
date = directory.getDate(ExifDirectoryBase.TAG_DATETIME_ORIGINAL);
if (date == null) {
date = directory.getDate(ExifDirectoryBase.TAG_DATETIME);
}
imageElement.setTagNoNull(TagD.get(Tag.DateOfSecondaryCapture),
directory.getDate(ExifDirectoryBase.TAG_DATETIME_DIGITIZED));
}
ExifIFD0Directory ifd0 = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
if (ifd0 != null) {
imageElement.setTagNoNull(TagD.get(Tag.Manufacturer),
ifd0.getString(ExifDirectoryBase.TAG_MAKE));
imageElement.setTagNoNull(TagD.get(Tag.ManufacturerModelName),
ifd0.getString(ExifDirectoryBase.TAG_MODEL));
// try {
// int orientation =
// ifd0.getInt(ExifIFD0Directory.TAG_ORIENTATION);
// } catch (MetadataException e) {
// e.printStackTrace();
// }
// AffineTransform affineTransform = new
// AffineTransform();
//
// switch (orientation) {
// case 1:
// break;
// case 2: // Flip X
// affineTransform.scale(-1.0, 1.0);
// affineTransform.translate(-width, 0);
// break;
// case 3: // PI rotation
// affineTransform.translate(width, height);
// affineTransform.rotate(Math.PI);
// break;
// case 4: // Flip Y
// affineTransform.scale(1.0, -1.0);
// affineTransform.translate(0, -height);
// break;
// case 5: // - PI/2 and Flip X
// affineTransform.rotate(-Math.PI / 2);
// affineTransform.scale(-1.0, 1.0);
// break;
// case 6: // -PI/2 and -width
// affineTransform.translate(height, 0);
// affineTransform.rotate(Math.PI / 2);
// break;
// case 7: // PI/2 and Flip
// affineTransform.scale(-1.0, 1.0);
// affineTransform.translate(-height, 0);
// affineTransform.translate(0, width);
// affineTransform.rotate(3 * Math.PI / 2);
// break;
// case 8: // PI / 2
// affineTransform.translate(0, width);
// affineTransform.rotate(3 * Math.PI / 2);
// break;
// default:
// break;
// }
//
// AffineTransformOp affineTransformOp = new
// AffineTransformOp(affineTransform,
// AffineTransformOp.TYPE_BILINEAR);
// BufferedImage destinationImage = new
// BufferedImage(originalImage.getHeight(),
// originalImage.getWidth(), originalImage.getType());
// destinationImage =
// affineTransformOp.filter(originalImage,
// destinationImage);
}
}
} catch (ImageProcessingException | IOException e) {
LOGGER.error("Error when reading exif tags", e); //$NON-NLS-1$
}
LocalDateTime dateTime = date == null
? LocalDateTime
.from(Instant.ofEpochMilli(imageElement.getLastModified()).atZone(ZoneId.systemDefault()))
: TagUtil.toLocalDateTime(date);
imageElement.setTagNoNull(TagD.get(Tag.ContentDate), dateTime.toLocalDate());
imageElement.setTagNoNull(TagD.get(Tag.ContentTime), dateTime.toLocalTime());
}
}
}