sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Make ImageOperations configurable.
Date Tue, 03 Mar 2020 11:01:45 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new a203f47  Make ImageOperations configurable.
a203f47 is described below

commit a203f47f17a9678a7cd29e5b1339753eda204d97
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Mar 3 12:01:11 2020 +0100

    Make ImageOperations configurable.
---
 .../apache/sis/internal/gui/ImageRenderings.java   |   5 +-
 .../java/org/apache/sis/image/ImageOperations.java | 242 ++++++++++++++++-----
 .../apache/sis/image/StatisticsCalculatorTest.java |  13 +-
 3 files changed, 197 insertions(+), 63 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageRenderings.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageRenderings.java
index 2de16be..0b33812 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageRenderings.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageRenderings.java
@@ -37,7 +37,10 @@ public final class ImageRenderings {
      * @todo Creates our own instance which listen to logging messages.
      *       We need to create a logging panel first.
      */
-    private static final ImageOperations OPERATIONS = ImageOperations.LENIENT;
+    private static final ImageOperations OPERATIONS = new ImageOperations();
+    static {
+        OPERATIONS.setErrorAction(ImageOperations.ErrorAction.LOG);
+    }
 
     /**
      * Do not allow instantiation of this class.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageOperations.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageOperations.java
index e926c3c..9beda4d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageOperations.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageOperations.java
@@ -29,12 +29,39 @@ import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 
 /**
  * A predefined set of operations on images as convenience methods.
- * Operations can be executed in parallel if applied on image with a thread-safe
- * and concurrent implementation of {@link RenderedImage#getTile(int, int)}.
- * Otherwise the same operations can be executed sequentially in the caller thread.
- * Errors during calculation can either be propagated as an {@link ImagingOpException}
- * (in which case no result is available), or notified as a {@link LogRecord}
- * (in which case partial results may be available).
+ * After instantiation, {@code ImageOperations} can be configured for the following aspects:
+ *
+ * <ul class="verbose">
+ *   <li>
+ *     Whether operations can be executed in parallel. By default operations on unknown
+ *     {@link RenderedImage} implementations are executed sequentially in the caller thread,
for safety reasons.
+ *     Some operations can be parallelized, but it should be enabled only if the {@link RenderedImage}
is known
+ *     to be thread-safe and has concurrent (or fast) implementation of {@link RenderedImage#getTile(int,
int)}.
+ *     Apache SIS implementations of {@link RenderedImage} can be parallelized, but it may
not be the case of
+ *     images from other libraries.
+ *   </li><li>
+ *     Whether the operations should fail if an exception is thrown while processing a tile.
+ *     By default errors during calculation are propagated as an {@link ImagingOpException},
+ *     in which case no result is available. But errors can also be notified as a {@link
LogRecord} instead,
+ *     in which case partial results may be available.
+ *   </li>
+ * </ul>
+ *
+ * <h2>Error handling</h2>
+ * If an exception occurs during the computation of a tile, then the {@code ImageOperations}
behavior
+ * is controlled by the {@link #getErrorAction() errorAction} property:
+ *
+ * <ul>
+ *   <li>If {@link ErrorAction#THROW}, the exception is wrapped in an {@link ImagingOpException}
and thrown.</li>
+ *   <li>If {@link ErrorAction#LOG}, the exception is logged and a partial result is
returned.</li>
+ *   <li>If any other value, the exception is wrapped in a {@link LogRecord} and sent
to that filter.
+ *     The filter can store the log record, for example for showing later in a graphical
user interface (GUI).
+ *     If the filter returns {@code true}, the log record is also logged, otherwise it is
silently discarded.
+ *     In both cases a partial result is returned.</li>
+ * </ul>
+ *
+ * <h2>Thread-safety</h2>
+ * {@code ImageOperations} is thread-safe if its configuration is not modified after construction.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
@@ -43,85 +70,182 @@ import org.apache.sis.internal.coverage.j2d.ImageUtilities;
  */
 public class ImageOperations {
     /**
-     * The set of operations with default configuration. Operations executed by this instance
-     * will be multi-threaded if possible, and failures to compute a value cause an exception
-     * to be thrown.
+     * Execution modes specifying whether operations can be executed in parallel.
+     * If {@link #SEQUENTIAL}, operations are executed sequentially in the caller thread.
+     * If {@link #PARALLEL}, some operations may be parallelized using an arbitrary number
of threads.
+     *
+     * @see #getExecutionMode()
+     * @see #setExecutionMode(Mode)
      */
-    public static final ImageOperations PARALLEL = new ImageOperations(true, true, null);
+    public enum Mode {
+        /**
+         * Operations executed in multi-threaded mode if possible.
+         * This mode can be used if the {@link RenderedImage} instances are thread-safe and
provide
+         * a concurrent (or very fast) implementation of {@link RenderedImage#getTile(int,
int)}.
+         */
+        PARALLEL,
 
-    /**
-     * The set of operations where all executions are constrained to a single thread.
-     * Only the caller thread is used, with no parallelization. Sequential operations
-     * may be useful for processing {@link RenderedImage} that may not be thread-safe.
-     * The error handling policy is the same than {@link #PARALLEL}.
-     */
-    public static final ImageOperations SEQUENTIAL = new ImageOperations(false, true, null);
+        /**
+         * Operations executed in the caller thread, without parallelization.
+         * Sequential operations may be useful for processing {@link RenderedImage}
+         * implementations that may not be thread-safe.
+         */
+        SEQUENTIAL,
+
+        /**
+         * Operations are executed in multi-threaded mode if the {@link RenderedImage} instance
+         * is an implementation known to be thread-safe. All operations on image implementations
+         * unknown to Apache SIS are executed in sequential mode.
+         */
+        DEFAULT
+    }
 
     /**
-     * The set of operations executed without throwing an exception in case of failure.
-     * Instead the warnings are logged. Whether the operations are executed in parallel
-     * or not is implementation dependent.
+     * Specifies how exceptions occurring during calculation should be handled.
+     * This enumeration provides common actions, but the set of values that can
+     * be specified to {@link #setErrorAction(Filter)} is not limited to this enumeration.
      *
-     * <p>Users should prefer {@link #PARALLEL} or {@link #SEQUENTIAL} in most cases
since the use
-     * of {@code LENIENT} may cause errors to be unnoticed (not everyone read log messages).</p>
+     * @see #getErrorAction()
+     * @see #setErrorAction(Filter)
      */
-    public static final ImageOperations LENIENT = new ImageOperations(true, false, null);
+    public enum ErrorAction implements Filter {
+        /**
+         * Exceptions are wrapped in an {@link ImagingOpException} and thrown.
+         * In such case, no result is available. This is the default action.
+         */
+        THROW,
+
+        /**
+         * Exceptions are wrapped in a {@link LogRecord} and logged at {@link java.util.logging.Level#WARNING}.
+         * Only one log record is created for all tiles that failed for the same operation
on the same image.
+         * A partial result may be available.
+         *
+         * <p>Users are encouraged to use {@link #THROW} or to specify their own {@link
Filter}
+         * instead than using this error action, because not everyone read logging records.</p>
+         */
+        LOG;
+
+        /**
+         * Unconditionally returns {@code true} for allowing the given record to be logged.
+         * This method is not useful for this {@code ErrorAction} enumeration, but is useful
+         * for other instances given to {@link #setErrorAction(Filter)}.
+         *
+         * @param  record  the error that occurred during computation of a tile.
+         * @return always {@code true}.
+         */
+        @Override
+        public boolean isLoggable(final LogRecord record) {
+            return true;
+        }
+    }
 
     /**
      * Whether the operations can be executed in parallel.
+     *
+     * @see #getExecutionMode()
+     * @see #setExecutionMode(Mode)
      */
-    private final boolean parallel;
+    private Mode executionMode;
 
     /**
-     * Whether errors occurring during computation should be propagated instead than wrapped
in a {@link LogRecord}.
+     * Whether errors occurring during computation should be propagated or wrapped in a {@link
LogRecord}.
+     * If errors are wrapped in a {@link LogRecord}, this field specifies what to do with
the record.
+     * Only one log record is created for all tiles that failed for the same operation on
the same image.
+     *
+     * @see #getErrorAction()
+     * @see #setErrorAction(Filter)
      */
-    private final boolean failOnException;
+    private Filter errorAction;
 
     /**
-     * Where to send exceptions (wrapped in {@link LogRecord}) if an operation failed on
one or more tiles.
-     * Only one log record is created for all tiles that failed for the same operation on
the same image.
-     * This is always {@code null} if {@link #failOnException} is {@code true}.
+     * Creates a new set of image operations with default configuration.
+     * The execution mode is initialized to {@link Mode#DEFAULT} and the error action to
{@link ErrorAction#THROW}.
      */
-    private final Filter errorListener;
+    public ImageOperations() {
+        executionMode = Mode.DEFAULT;
+        errorAction   = ErrorAction.THROW;
+    }
 
     /**
-     * Creates a new set of image operations.
+     * Returns whether operations can be executed in parallel.
+     * If {@link Mode#SEQUENTIAL}, operations are executed sequentially in the caller thread.
+     * If {@link Mode#PARALLEL}, some operations may be parallelized using an arbitrary number
of threads.
      *
-     * <h4>Error handling</h4>
-     * If an exception occurs during the computation of a tile, then the {@code ImageOperations}
behavior
-     * is controlled by the following parameters:
+     * @return whether the operations can be executed in parallel.
+     */
+    public Mode getExecutionMode() {
+        return executionMode;
+    }
+
+    /**
+     * Sets whether operations can be executed in parallel.
+     * This value can be set to {@link Mode#PARALLEL} if the {@link RenderedImage} instances
are thread-safe
+     * and provide a concurrent (or very fast) implementation of {@link RenderedImage#getTile(int,
int)}.
+     * If {@link Mode#SEQUENTIAL}, only the caller thread is used. Sequential operations
may be useful
+     * for processing {@link RenderedImage} implementations that may not be thread-safe.
      *
-     * <ul>
-     *   <li>If {@code failOnException} is {@code true}, the exception is thrown as
an {@link ImagingOpException}.</li>
-     *   <li>If {@code failOnException} is {@code false}, then:<ul>
-     *     <li>If {@code errorListener} is {@code null}, the exception is logged and
a partial result is returned.</li>
-     *     <li>If {@code errorListener} is non-null, the exception is wrapped in a
{@link LogRecord} and sent to that handler.
-     *         The listener can store the log record, for example for showing later in a
graphical user interface (GUI).
-     *         If the listener returns {@code true}, the log record is also logged, otherwise
it is silently discarded.
-     *         In both cases a partial result is returned.</li>
-     *     </ul>
-     *   </li>
-     * </ul>
+     * <p>It is safe to set this flag to {@link Mode#PARALLEL} with {@link java.awt.image.BufferedImage}
+     * (it will actually have no effect in this particular case) or with Apache SIS implementations
of
+     * {@link RenderedImage}.</p>
      *
-     * @param  parallel         whether the operations can be executed in parallel.
-     * @param  failOnException  whether exceptions occurring during computation should be
propagated.
-     * @param  errorListener     handler to notify when an operation failed on one or more
tiles,
-     *                          or {@code null} for printing the exceptions with the default
logger.
-     *                          This is ignored if {@code failOnException} is {@code true}.
+     * @param  mode  whether the operations can be executed in parallel.
      */
-    public ImageOperations(final boolean parallel, final boolean failOnException, final Filter
errorListener) {
-        this.parallel        = parallel;
-        this.failOnException = failOnException;
-        this.errorListener   = failOnException ? null : errorListener;
+    public void setExecutionMode(final Mode mode) {
+        ArgumentChecks.ensureNonNull("mode", mode);
+        executionMode = mode;
     }
 
     /**
      * Whether the operations can be executed in parallel for the specified image.
-     * Should be a method overridden by {@link #LENIENT}, but for this simple need
-     * it is not yet worth to do sub-classing.
      */
     private boolean parallel(final RenderedImage source) {
-        return (this == LENIENT) ? source.getClass().getName().startsWith(Modules.CLASSNAME_PREFIX)
: parallel;
+        switch (executionMode) {
+            case PARALLEL:   return true;
+            case SEQUENTIAL: return false;
+            default:         return source.getClass().getName().startsWith(Modules.CLASSNAME_PREFIX);
+        }
+    }
+
+    /**
+     * Returns whether exceptions occurring during computation are propagated or logged.
+     * If {@link ErrorAction#THROW} (the default), exceptions are wrapped in {@link ImagingOpException}
and thrown.
+     * If any other value, exceptions are wrapped in a {@link LogRecord}, filtered then eventually
logged.
+     *
+     * @return whether exceptions occurring during computation are propagated or logged.
+     */
+    public Filter getErrorAction() {
+        return errorAction;
+    }
+
+    /**
+     * Sets whether exceptions occurring during computation are propagated or logged.
+     * The default behavior is to wrap exceptions in {@link ImagingOpException} and throw
them.
+     * If this property is set to {@link ErrorAction#LOG} or any other value, then exceptions
will
+     * be wrapped in {@link LogRecord} instead, in which case a partial result may be available.
+     * Only one log record is created for all tiles that failed for the same operation on
the same image.
+     *
+     * @param  action  filter to notify when an operation failed on one or more tiles,
+     *                 or {@link ErrorAction#THROW} for propagating the exception.
+     */
+    public void setErrorAction(final Filter action) {
+        ArgumentChecks.ensureNonNull("action", action);
+        errorAction = action;
+    }
+
+    /**
+     * Whether errors occurring during computation should be propagated instead than wrapped
in a {@link LogRecord}.
+     */
+    private boolean failOnException() {
+        return errorAction == ErrorAction.THROW;
+    }
+
+    /**
+     * Where to send exceptions (wrapped in {@link LogRecord}) if an operation failed on
one or more tiles.
+     * Only one log record is created for all tiles that failed for the same operation on
the same image.
+     * This is always {@code null} if {@link #failOnException()} is {@code true}.
+     */
+    private Filter errorListener() {
+        return (errorAction instanceof ErrorAction) ? null : errorAction;
     }
 
     /**
@@ -133,9 +257,9 @@ public class ImageOperations {
      */
     public Statistics[] statistics(final RenderedImage source) {
         ArgumentChecks.ensureNonNull("source", source);
-        final StatisticsCalculator calculator = new StatisticsCalculator(source, parallel(source),
failOnException);
+        final StatisticsCalculator calculator = new StatisticsCalculator(source, parallel(source),
failOnException());
         final Object property = calculator.getProperty(StatisticsCalculator.PROPERTY_NAME);
-        calculator.logAndClearError(ImageOperations.class, "statistics", errorListener);
+        calculator.logAndClearError(ImageOperations.class, "statistics", errorListener());
         if (property instanceof Statistics[]) {
             return (Statistics[]) property;
         }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
b/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
index 7be7cc1..47de299 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
@@ -80,9 +80,11 @@ public final strictfp class StatisticsCalculatorTest extends TestCase {
      */
     @Test
     public void testParallelExecution() {
+        final ImageOperations operations = new ImageOperations();
+        operations.setExecutionMode(ImageOperations.Mode.PARALLEL);
         final TiledImageMock image = createImage();
         final Statistics[] expected = StatisticsCalculator.computeSequentially(image);
-        final Statistics[] actual = ImageOperations.PARALLEL.statistics(image);
+        final Statistics[] actual = operations.statistics(image);
         for (int i=0; i<expected.length; i++) {
             final Statistics e = expected[i];
             final Statistics a = actual  [i];
@@ -98,10 +100,12 @@ public final strictfp class StatisticsCalculatorTest extends TestCase
{
      */
     @Test
     public void testWithFailures() {
+        final ImageOperations operations = new ImageOperations();
+        operations.setExecutionMode(ImageOperations.Mode.PARALLEL);
         final TiledImageMock image = createImage();
         image.failRandomly(new Random(-8739538736973900203L));
         try {
-            ImageOperations.PARALLEL.statistics(image);
+            operations.statistics(image);
             fail("Expected ImagingOpException.");
         } catch (ImagingOpException e) {
             final String message = e.getMessage();
@@ -116,9 +120,12 @@ public final strictfp class StatisticsCalculatorTest extends TestCase
{
      */
     @Test
     public void testWithLoggings() {
+        final ImageOperations operations = new ImageOperations();
+        operations.setExecutionMode(ImageOperations.Mode.PARALLEL);
+        operations.setErrorAction(ImageOperations.ErrorAction.LOG);
         final TiledImageMock image = createImage();
         image.failRandomly(new Random(8004277484984714811L));
-        final Statistics[] stats = ImageOperations.LENIENT.statistics(image);
+        final Statistics[] stats = operations.statistics(image);
         for (final Statistics a : stats) {
             assertTrue(a.count() > 0);
         }


Mime
View raw message