From 156530d1b8bd5857713e56f5eb78df1e6dfba8ba Mon Sep 17 00:00:00 2001
From: tpylak <tpylak>
Date: Thu, 10 Feb 2011 14:09:18 +0000
Subject: [PATCH] LMS-2027 configure machine load during thumbnails generation
 and quality of the thumbnails from the python dropbox

SVN: 19881
---
 .../dss/generic/shared/utils/ImageUtil.java   | 12 ++++---
 .../etl/AbstractImageStorageProcessor.java    | 22 ++++++------
 .../dss/etl/Hdf5ThumbnailGenerator.java       | 36 +++++++++++--------
 .../dto/api/v1/ThumbnailsStorageFormat.java   | 24 +++++++++++--
 .../server/images/ImageChannelsUtils.java     | 24 +++++++------
 .../server/images/dto/RequestedImageSize.java | 14 ++++++++
 6 files changed, 89 insertions(+), 43 deletions(-)

diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java
index 9a1f18c3cde..d8f2b1673dd 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java
@@ -265,7 +265,7 @@ public class ImageUtil
      */
     public static BufferedImage createThumbnail(BufferedImage image, int maxWidth, int maxHeight)
     {
-        return rescale(image, maxWidth, maxHeight, true);
+        return rescale(image, maxWidth, maxHeight, true, false);
     }
 
     /**
@@ -277,9 +277,11 @@ public class ImageUtil
      * @param maxHeight Maximum height of the result image.
      * @param enlargeIfNecessary if false and the image has smaller width and height than the
      *            specified limit, then the image is not changed.
+     * @param highQuality if true thumbnails will be of higher quality, but rescaling will take
+     *            longer (BICUBIC rescaling will be used instead of BILINEAR).
      */
     public static BufferedImage rescale(BufferedImage image, int maxWidth, int maxHeight,
-            boolean enlargeIfNecessary)
+            boolean enlargeIfNecessary, boolean highQuality)
     {
         int width = image.getWidth();
         int height = image.getHeight();
@@ -306,8 +308,10 @@ public class ImageUtil
                         : BufferedImage.TYPE_INT_RGB;
         BufferedImage thumbnail = new BufferedImage(thumbnailWidth, thumbnailHeight, imageType);
         Graphics2D graphics2D = thumbnail.createGraphics();
-        graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
-                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+        Object renderingHint =
+                highQuality ? RenderingHints.VALUE_INTERPOLATION_BICUBIC
+                        : RenderingHints.VALUE_INTERPOLATION_BILINEAR;
+        graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, renderingHint);
         graphics2D.drawImage(image, 0, 0, thumbnailWidth, thumbnailHeight, null);
         return thumbnail;
     }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageStorageProcessor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageStorageProcessor.java
index 64b210b939c..3200f364013 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageStorageProcessor.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageStorageProcessor.java
@@ -412,13 +412,11 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor im
         if (thumbnailsStorageFormatOrNull != null)
         {
             Hdf5Container container = new Hdf5Container(thumbnailsFile);
-            container.runWriterClient(
-                    thumbnailsStorageFormatOrNull.isStoreCompressed(),
-                    new Hdf5ThumbnailGenerator(plateImages, imagesInStoreFolder,
-                            thumbnailsStorageFormatOrNull.getMaxWidth(),
-                            thumbnailsStorageFormatOrNull.getMaxHeight(),
-                            relativeThumbnailFilePath, thumbnailsStorageFormatOrNull
-                                    .getAllowedMachineLoadDuringGeneration(), operationLog));
+            container
+                    .runWriterClient(thumbnailsStorageFormatOrNull.isStoreCompressed(),
+                            new Hdf5ThumbnailGenerator(plateImages, imagesInStoreFolder,
+                                    thumbnailsStorageFormatOrNull, relativeThumbnailFilePath,
+                                    operationLog));
         }
     }
 
@@ -625,7 +623,8 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor im
     {
         checkParameters(incomingDataSetDirectory, storedDataDirectory);
 
-        final File originalDataFile = tryGetProprietaryData(storedDataDirectory);
+        final File originalDataFile =
+                tryGetProprietaryData(storedDataDirectory);
         if (originalDataFile == null)
         {
             // nothing has been stored in the file system yet,
@@ -639,9 +638,10 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor im
             moveFileToDirectory(originalDataFile, incomingDirectory);
             if (operationLog.isInfoEnabled())
             {
-                operationLog.info(String.format(
-                        "Directory '%s' has moved to incoming directory '%s'.", originalDataFile,
-                        incomingDirectory.getAbsolutePath()));
+                operationLog
+                        .info(String
+                                .format("Storage operation rollback: directory '%s' has moved to incoming directory '%s'.",
+                                        originalDataFile, incomingDirectory.getAbsolutePath()));
             }
         } catch (final EnvironmentFailureException ex)
         {
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java
index cc979c75d6a..098222c0cee 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java
@@ -31,6 +31,7 @@ import ch.systemsx.cisd.common.concurrent.ParallelizedExecutor;
 import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.etlserver.hdf5.Hdf5Container.IHdf5WriterClient;
 import ch.systemsx.cisd.hdf5.IHDF5SimpleWriter;
+import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.ThumbnailsStorageFormat;
 import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtil;
 
 /**
@@ -44,41 +45,39 @@ class Hdf5ThumbnailGenerator implements IHdf5WriterClient
 
     private final File imagesInStoreFolder;
 
-    private final int thumbnailMaxWidth;
-
-    private final int thumbnailMaxHeight;
+    private final ThumbnailsStorageFormat thumbnailsStorageFormat;
 
     private final String relativeThumbnailFilePath;
 
-    private final int allowedMachineLoadDuringGeneration;
-
     private final Logger operationLog;
 
     Hdf5ThumbnailGenerator(List<AcquiredSingleImage> plateImages, File imagesInStoreFolder,
-            int thumbnailMaxWidth, int thumbnailMaxHeight, String relativeThumbnailFilePath,
-            int allowedMachineLoadDuringGeneration, Logger operationLog)
+            ThumbnailsStorageFormat thumbnailsStorageFormat, String relativeThumbnailFilePath,
+            Logger operationLog)
     {
         this.plateImages = plateImages;
         this.imagesInStoreFolder = imagesInStoreFolder;
-        this.thumbnailMaxWidth = thumbnailMaxWidth;
-        this.thumbnailMaxHeight = thumbnailMaxHeight;
+        this.thumbnailsStorageFormat = thumbnailsStorageFormat;
         this.relativeThumbnailFilePath = relativeThumbnailFilePath;
-        this.allowedMachineLoadDuringGeneration = allowedMachineLoadDuringGeneration;
         this.operationLog = operationLog;
     }
 
     private Status generateThumbnail(IHDF5SimpleWriter writer, AcquiredSingleImage plateImage)
     {
+        long start = System.currentTimeMillis();
         RelativeImageReference imageReference = plateImage.getImageReference();
         String imagePath = imageReference.getRelativeImagePath();
         File img = new File(imagesInStoreFolder, imagePath);
         BufferedImage image = ImageUtil.loadImage(img);
         BufferedImage thumbnail =
-                ImageUtil.rescale(image, thumbnailMaxWidth, thumbnailMaxHeight, false);
+                ImageUtil.rescale(image, thumbnailsStorageFormat.getMaxWidth(),
+                        thumbnailsStorageFormat.getMaxHeight(), false,
+                        thumbnailsStorageFormat.isHighQuality());
         ByteArrayOutputStream output = new ByteArrayOutputStream();
         try
         {
             ImageIO.write(thumbnail, "png", output);
+
             String thumbnailPath = replaceExtensionToPng(imagePath);
 
             String path =
@@ -88,10 +87,16 @@ class Hdf5ThumbnailGenerator implements IHdf5WriterClient
             byte[] byteArray = output.toByteArray();
             if (operationLog.isDebugEnabled())
             {
-                operationLog.debug("thumbnail " + thumbnailPath + " (" + byteArray.length
-                        + " bytes)");
+                long now = System.currentTimeMillis();
+                operationLog.debug(Thread.currentThread().getName() + " thumbnail " + thumbnailPath
+                        + " (" + byteArray.length + " bytes) generated in " + (now - start)
+                        + " msec");
+            }
+
+            synchronized (writer)
+            {
+                writer.writeByteArray(thumbnailPath, byteArray);
             }
-            writer.writeByteArray(thumbnailPath, byteArray);
         } catch (IOException ex)
         {
             return Status.createError(ex.getMessage());
@@ -126,6 +131,7 @@ class Hdf5ThumbnailGenerator implements IHdf5WriterClient
     public void runWithSimpleWriter(IHDF5SimpleWriter writer)
     {
         ParallelizedExecutor.process(plateImages, createThumbnailGenerator(writer),
-                allowedMachineLoadDuringGeneration, 100, 1);
+                thumbnailsStorageFormat.getAllowedMachineLoadDuringGeneration(), 100,
+                "Thumbnails generation", 1);
     }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/ThumbnailsStorageFormat.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/ThumbnailsStorageFormat.java
index a3f7671a5ce..990a3b6f4de 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/ThumbnailsStorageFormat.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/ThumbnailsStorageFormat.java
@@ -37,7 +37,9 @@ public class ThumbnailsStorageFormat
 
     private boolean storeCompressed = DEFAULT_COMPRESS_THUMBNAILS;
 
-    private int allowedMachineLoadDuringGeneration = 1;
+    private double allowedMachineLoadDuringGeneration = 1;
+
+    private boolean highQuality = false;
 
     /**
      * Creates empty object which instructs that the thumbnails should be generated with default
@@ -62,11 +64,18 @@ public class ThumbnailsStorageFormat
         return storeCompressed;
     }
 
-    public int getAllowedMachineLoadDuringGeneration()
+    public double getAllowedMachineLoadDuringGeneration()
     {
         return allowedMachineLoadDuringGeneration;
     }
 
+    public boolean isHighQuality()
+    {
+        return highQuality;
+    }
+
+    // --- setters ---
+
     /** Sets the maximum width of a thumbnail. */
     public void setMaxWidth(int maxWidth)
     {
@@ -89,8 +98,17 @@ public class ThumbnailsStorageFormat
      * The number of threads which will be used during thumbnails generation will be equal to number
      * of processor cores * machineLoad.
      */
-    public void setAllowedMachineLoadDuringGeneration(int machineLoad)
+    public void setAllowedMachineLoadDuringGeneration(double machineLoad)
     {
         this.allowedMachineLoadDuringGeneration = machineLoad;
     }
+
+    /**
+     * Set to true if you want your thumbnails to be of higher quality. In such a case thumbnails
+     * generation during dataset registration will take longer. Recommended for overlay images.
+     */
+    public void setHighQuality(boolean highQuality)
+    {
+        this.highQuality = highQuality;
+    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java
index 6b22e8228d4..8c2f35c3390 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java
@@ -143,9 +143,9 @@ public class ImageChannelsUtils
         return calculateSingleImages(imageContents);
     }
 
-    private static RequestedImageSize getSize(BufferedImage img)
+    private static RequestedImageSize getSize(BufferedImage img, boolean highQuality)
     {
-        return new RequestedImageSize(new Size(img.getWidth(), img.getHeight()), true);
+        return new RequestedImageSize(new Size(img.getWidth(), img.getHeight()), true, highQuality);
     }
 
     private static RequestedImageSize calcOverlaySize(BufferedImage imageOrNull,
@@ -155,15 +155,19 @@ public class ImageChannelsUtils
         {
             // thumbnais are not used, so use the original size even if it does not match
             return RequestedImageSize.createOriginal();
-        }
-        if (imageOrNull != null)
-        {
-            // all overlays have to be of the same size as the basic image
-            return getSize(imageOrNull);
         } else
         {
-            // if there is no basic image yet, enlarging too small images is not necessary
-            return new RequestedImageSize(thumbnailSizeOrNull, false);
+            // we want higher quality only if thumbnail overlays are generated
+            boolean highQuality = true;
+            if (imageOrNull != null)
+            {
+                // all overlays have to be of the same size as the basic image
+                return getSize(imageOrNull, highQuality);
+            } else
+            {
+                // if there is no basic image yet, enlarging too small images is not necessary
+                return new RequestedImageSize(thumbnailSizeOrNull, false, highQuality);
+            }
         }
     }
 
@@ -444,7 +448,7 @@ public class ImageChannelsUtils
             start = operationLog.isDebugEnabled() ? System.currentTimeMillis() : 0;
             image =
                     ImageUtil.rescale(image, size.getWidth(), size.getHeight(),
-                            requestedSize.enlargeIfNecessary());
+                            requestedSize.enlargeIfNecessary(), requestedSize.isHighQualityRescalingRequired());
             if (operationLog.isDebugEnabled())
             {
                 operationLog.debug("Create thumbnail: " + (System.currentTimeMillis() - start));
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/RequestedImageSize.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/RequestedImageSize.java
index d986b3fad51..5e80e921a14 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/RequestedImageSize.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/RequestedImageSize.java
@@ -36,10 +36,19 @@ public class RequestedImageSize extends AbstractHashable
 
     private final boolean enlargeIfNecessary;
 
+    private final boolean highQualityRescalingRequired;
+
     public RequestedImageSize(Size thumbnailSizeOrNull, boolean enlargeIfNecessary)
+    {
+        this(thumbnailSizeOrNull, enlargeIfNecessary, false);
+    }
+
+    public RequestedImageSize(Size thumbnailSizeOrNull, boolean enlargeIfNecessary,
+            boolean highQualityRescalingRequired)
     {
         this.thumbnailSizeOrNull = thumbnailSizeOrNull;
         this.enlargeIfNecessary = enlargeIfNecessary;
+        this.highQualityRescalingRequired = highQualityRescalingRequired;
     }
 
     /** original size if null */
@@ -58,6 +67,11 @@ public class RequestedImageSize extends AbstractHashable
         return enlargeIfNecessary;
     }
 
+    public boolean isHighQualityRescalingRequired()
+    {
+        return highQualityRescalingRequired;
+    }
+
     @Override
     public String toString()
     {
-- 
GitLab