sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/03: Fix erroneous handling of TIFF images when bands are stored in separated planes.
Date Sun, 11 Jul 2021 20:17:11 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

commit ee0e0df25495e8fc637026af286848d1262a9071
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sun Jul 11 22:03:34 2021 +0200

    Fix erroneous handling of TIFF images when bands are stored in separated planes.
---
 .../org/apache/sis/storage/geotiff/DataCube.java   |   6 ++
 .../org/apache/sis/storage/geotiff/DataSubset.java | 112 ++++++++++++++-------
 .../sis/storage/geotiff/ImageFileDirectory.java    |  23 ++++-
 .../sis/internal/netcdf/impl/VariableInfo.java     |   5 +-
 .../sis/internal/storage/TiledGridCoverage.java    |  10 --
 .../internal/storage/io/HyperRectangleReader.java  |  32 ++++--
 .../storage/io/HyperRectangleReaderTest.java       |   3 +-
 7 files changed, 129 insertions(+), 62 deletions(-)

diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
index 5089a02..1cbc0c1 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
@@ -102,6 +102,12 @@ abstract class DataCube extends TiledGridResource implements ResourceOnFileSyste
     protected abstract Number fillValue();
 
     /**
+     * Returns the total number of tiles. This is used for computing the stride between a
+     * band and the next band in {@link #tileOffsets} and {@link #tileByteCounts} vectors.
+     */
+    abstract long getNumTiles();
+
+    /**
      * Gets the stream position and the length in bytes of compressed tile arrays in the
GeoTIFF file.
      * Values in the returned vector are {@code long} primitive type.
      *
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
index 677fe3e..82eb663 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
@@ -35,6 +35,7 @@ import org.apache.sis.internal.storage.TiledGridResource;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.coverage.j2d.RasterFactory;
 import org.apache.sis.internal.geotiff.Resources;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Localized;
 import org.apache.sis.math.Vector;
 
@@ -42,6 +43,7 @@ import static java.lang.Math.addExact;
 import static java.lang.Math.subtractExact;
 import static java.lang.Math.multiplyExact;
 import static org.apache.sis.internal.jdk9.JDK9.multiplyFull;
+import static java.lang.Math.toIntExact;
 
 
 /**
@@ -93,6 +95,12 @@ class DataSubset extends TiledGridCoverage implements Localized {
     private final Vector tileByteCounts;
 
     /**
+     * Total number of tiles in the image. This is used for computing the stride between
+     * a band and the next band in {@link #tileOffsets} and {@link #tileByteCounts} vectors.
+     */
+    private final int numTiles;
+
+    /**
      * Creates a new data subset. All parameters should have been validated
      * by {@link ImageFileDirectory#validateMandatoryTags()} before this call.
      * This constructor should be invoked inside a synchronized block.
@@ -104,8 +112,9 @@ class DataSubset extends TiledGridCoverage implements Localized {
      */
     DataSubset(final DataCube source, final TiledGridResource.Subset subset) throws DataStoreException
{
         super(subset, source.getSampleModel(), source.getColorModel(), source.fillValue());
+        this.source   = source;
+        this.numTiles = toIntExact(source.getNumTiles());
         final Vector[] tileArrayInfo = source.getTileArrayInfo();
-        this.source         = source;
         this.tileOffsets    = tileArrayInfo[0];
         this.tileByteCounts = tileArrayInfo[1];
     }
@@ -133,21 +142,46 @@ class DataSubset extends TiledGridCoverage implements Localized {
     private static final class Tile extends Snapshot implements Comparable<Tile> {
         /**
          * Value of {@link DataSubset#tileOffsets} at index {@link #indexInTileVector}.
+         * If pixel data are stored in different planes ("banks" in Java2D terminology),
+         * this is the smallest value of all banks.
          * This is the value that we want in increasing order.
          *
          * @see #compareTo(Tile)
          */
-        final long byteOffset;
+        private final long byteOffset;
 
         /**
          * Stores information about a tile to be loaded.
          *
-         * @param iterator    the iterator for which to create a snapshot of its current
position.
-         * @param byteOffset  value of {@code tileOffsets.longValue(indexInTileVector)}.
+         * @param iterator  the iterator for which to create a snapshot of its current position.
          */
-        Tile(final AOI domain, final long byteOffset) {
+        Tile(final AOI domain, final Vector tileOffsets, final int numTiles) {
             super(domain);
-            this.byteOffset = byteOffset;
+            int p = indexInTileVector;
+            long offset = tileOffsets.longValue(p);
+            final int limit = tileOffsets.size() - numTiles;
+            while (p < limit) {
+                // TODO: should take in account only the bands selected by user.
+                offset = Math.min(offset, tileOffsets.longValue(p += numTiles));
+            }
+            byteOffset = offset;
+        }
+
+        /**
+         * Copies {@link #tileOffsets} or {@link #tileByteCounts} values into the given target
array.
+         * Values for different planes ("banks" in Java2D terminology) are packed as consecutive
values
+         * in the given target array.
+         *
+         * @param  source    {@link DataSubset#tileOffsets} or {@link DataSubset#tileByteCounts}.
+         * @param  target    the array where to copy vector values.
+         * @param  numTiles  value of {@link DataSubset#numTiles}.
+         */
+        final void copyTileInfo(final Vector source, final long[] target, final int numTiles)
{
+            int i = indexInTileVector;
+            for (int j=0; j<target.length; j++) {
+                target[j] = source.longValue(i);
+                i += numTiles;
+            }
         }
 
         /**
@@ -191,7 +225,7 @@ class DataSubset extends TiledGridCoverage implements Localized {
                     result[iterator.getIndexInResultArray()] = tile;
                 } else {
                     // Tile not yet loaded. Add to a queue of tiles to load later.
-                    missings[numMissings++] = new Tile(iterator, tileOffsets.longValue(iterator.getIndexInTileVector()));
+                    missings[numMissings++] = new Tile(iterator, tileOffsets, numTiles);
                 }
             } while (iterator.next());
             Arrays.sort(missings, 0, numMissings);
@@ -203,19 +237,21 @@ class DataSubset extends TiledGridCoverage implements Localized {
              * TODO: Use `tile.byteCount` for checking if two tiles are consecutive in the
TIFF file.
              * In such case we should send only one HTTP range request.
              */
-            final long[] lower       = new long[BIDIMENSIONAL + 1];   // Coordinates of the
first pixel to read relative to the tile.
-            final long[] upper       = new long[BIDIMENSIONAL + 1];   // Coordinates after
the last pixel to read relative to the tile.
-            final int[]  subsampling = new int [BIDIMENSIONAL + 1];
+            final long[] lower       = new long[BIDIMENSIONAL];   // Coordinates of the first
pixel to read relative to the tile.
+            final long[] upper       = new long[BIDIMENSIONAL];   // Coordinates after the
last pixel to read relative to the tile.
+            final int[]  subsampling = new int [BIDIMENSIONAL];
             final Point  origin      = new Point();
+            final long[] offsets     = new long[tileOffsets.size() / numTiles];
+            final long[] byteCounts  = new long[tileByteCounts.size() / numTiles];
             for (int i=0; i<numMissings; i++) {
                 final Tile tile = missings[i];
                 origin.x = tile.originX;
                 origin.y = tile.originY;
                 tile.getRegionInsideTile(lower, upper, subsampling, BIDIMENSIONAL);
-                result[tile.indexInResultArray] = tile.cache(readSlice(
-                        addExact(source.reader.origin, tile.byteOffset),
-                        tileByteCounts.longValue(tile.indexInTileVector),
-                        lower, upper, subsampling, origin));
+                tile.copyTileInfo(tileOffsets,    offsets,    numTiles);
+                tile.copyTileInfo(tileByteCounts, byteCounts, numTiles);
+                result[tile.indexInResultArray] = tile.cache(
+                        readSlice(offsets, byteCounts, lower, upper, subsampling, origin));
             }
         }
         return result;
@@ -223,22 +259,20 @@ class DataSubset extends TiledGridCoverage implements Localized {
 
     /**
      * Reads a two-dimensional slice of the data cube from the given input channel. This
method is usually
-     * invoked for reading the tile in full, in which case the {@code lower} argument is
(0,0,0) and the
+     * invoked for reading the tile in full, in which case the {@code lower} argument is
(0,0) and the
      * {@code upper} argument is the tile size. But those arguments may identify a smaller
region if the
      * {@link DataSubset} contains only one (potentially large) tile.
      *
-     * <p>The length of {@code lower}, {@code upper} and {@code subsampling} arrays
shall be 3,
-     * with the third dimension reserved for bands (this is not the <var>z</var>
dimension).
-     * This method may modify those arrays in-place.</p>
+     * <p>The length of {@code lower}, {@code upper} and {@code subsampling} arrays
shall be 2.</p>
      *
      * <p>The default implementation in this base class assumes uncompressed data.
      * Subclasses must override for handling decompression.</p>
      *
-     * @param  offset       position in the channel where tile data begins.
-     * @param  byteCount    number of bytes for the compressed tile data.
-     * @param  lower        (<var>x</var>, <var>y</var>, 0) coordinates
of the first pixel to read relative to the tile.
-     * @param  upper        (<var>x</var>, <var>y</var>, 0) coordinates
after the last pixel to read relative to the tile.
-     * @param  subsampling  (<var>sx</var>, <var>sy</var>, 1) subsampling
factors.
+     * @param  offsets      position in the channel where tile data begins, one value per
bank.
+     * @param  byteCounts   number of bytes for the compressed tile data, one value per bank.
+     * @param  lower        (<var>x</var>, <var>y</var>) coordinates
of the first pixel to read relative to the tile.
+     * @param  upper        (<var>x</var>, <var>y</var>) coordinates
after the last pixel to read relative to the tile.
+     * @param  subsampling  (<var>sx</var>, <var>sy</var>) subsampling
factors.
      * @param  location     pixel coordinates in the upper-left corner of the tile to return.
      * @return image decoded from the GeoTIFF file.
      * @throws IOException if an I/O error occurred.
@@ -246,7 +280,7 @@ class DataSubset extends TiledGridCoverage implements Localized {
      * @throws RuntimeException if the Java2D image can not be created for another reason
      *         (too many exception types to list them all).
      */
-    WritableRaster readSlice(final long offset, final long byteCount, final long[] lower,
final long[] upper,
+    WritableRaster readSlice(final long[] offsets, final long[] byteCounts, final long[]
lower, final long[] upper,
                              final int[] subsampling, final Point location) throws IOException,
DataStoreException
     {
         final int  type   = model.getDataType();
@@ -262,19 +296,19 @@ class DataSubset extends TiledGridCoverage implements Localized {
             numBanks = numInterleaved;
             numInterleaved = 1;
         }
+        if (numBanks > offsets.length) {
+            throw new DataStoreContentException(source.reader.errors().getString(
+                    Errors.Keys.TooFewCollectionElements_3, "tileOffsets", numBanks * numTiles,
tileOffsets.size()));
+        }
         /*
          * The number of bytes to read should not be greater than `byteCount`. It may be
smaller however if only
          * a subregion is read. Note that the `length` value may be different than `capacity`
if the tile to read
          * is smaller than the "standard" tile size of the image. It happens often when reading
the last strip.
          */
-        final long length = multiplyExact(DataBuffer.getDataTypeSize(type) / Byte.SIZE,
-                            multiplyExact(multiplyExact(width, height), numInterleaved));
-        if (length > byteCount) {
-            throw new DataStoreContentException(source.reader.resources().getString(
-                    Resources.Keys.UnexpectedTileLength_2, length, byteCount));
-        }
+        final long length  = multiplyExact(DataBuffer.getDataTypeSize(type) / Byte.SIZE,
+                             multiplyExact(multiplyExact(width, height), numInterleaved));
         final int capacity = multiplyExact(multiplyExact(model.getWidth(), model.getHeight()),
numInterleaved);
-        final long[] size = new long[] {multiplyFull(numInterleaved, getTileSize(0)), getTileSize(1),
numBanks};
+        final long[] size = new long[] {multiplyFull(numInterleaved, getTileSize(0)), getTileSize(1)};
         /*
          * If we use an interleaved sample model, each "element" from `HyperRectangleReader`
perspective is actually
          * a group of `numInterleaved` values. Note that in such case, we can not handle
subsampling on the first axis.
@@ -284,13 +318,19 @@ class DataSubset extends TiledGridCoverage implements Localized {
         }
         lower[0] *= numInterleaved;
         upper[0] *= numInterleaved;
-        final HyperRectangleReader hr = new HyperRectangleReader(ImageUtilities.toNumberEnum(type),
source.reader.input, offset);
+        /*
+         * Read each plane ("banks" in Java2D terminology). Note that a single bank contain
all bands
+         * in the interleaved sample model case.
+         */
+        final HyperRectangleReader hr = new HyperRectangleReader(ImageUtilities.toNumberEnum(type),
source.reader.input);
+        final Region region = new Region(size, lower, upper, subsampling);
         final Buffer[] banks = new Buffer[numBanks];
         for (int b=0; b<numBanks; b++) {
-            lower[BIDIMENSIONAL] = b;
-            upper[BIDIMENSIONAL] = b + 1;
-            subsampling[BIDIMENSIONAL] = 1;
-            final Region region = new Region(size, lower, upper, subsampling);
+            if (b < byteCounts.length && length > byteCounts[b]) {
+                throw new DataStoreContentException(source.reader.resources().getString(
+                        Resources.Keys.UnexpectedTileLength_2, length, byteCounts[b]));
+            }
+            hr.setOrigin(addExact(source.reader.origin, offsets[b]));
             final Buffer buffer = hr.readAsBuffer(region, capacity);
             /*
              * The buffer returned by `readAsBuffer(…)` may have less data than the buffer
capacity
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index b3a3ae4..4f6bb4a 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -43,6 +43,7 @@ import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -140,6 +141,9 @@ final class ImageFileDirectory extends DataCube {
      * height of tiles having the same width than the image. While the TIFF specification
handles
      * "tiles" and "strips" separately, Apache SIS handles strips as a special kind of tiles
where
      * only {@code tileHeight} is specified and {@code tileWidth} defaults to {@link #imageWidth}.
+     *
+     * @see #getTileSize()
+     * @see #getNumTiles()
      */
     private int tileWidth = -1, tileHeight = -1;
 
@@ -1171,13 +1175,10 @@ final class ImageFileDirectory extends DataCube {
         }
         /*
          * Report an error if the tile offset and tile byte count vectors do not have the
same length.
-         * Then ensure that the number of tiles is equal to the expected number. The formula
below is the
-         * one documented in the TIFF specification and reproduced in tileWidth & tileHeight
fields javadoc.
+         * Then ensure that the number of tiles is equal to the expected number.
          */
         ensureSameLength(offsetsTag, byteCountsTag, tileOffsets.size(), tileByteCounts.size());
-        long expectedCount = Math.multiplyExact(
-                Math.addExact(imageWidth,  tileWidth  - 1) / tileWidth,
-                Math.addExact(imageHeight, tileHeight - 1) / tileHeight);
+        long expectedCount = getNumTiles();
         if (isPlanar) {
             expectedCount = Math.multiplyExact(expectedCount, samplesPerPixel);
         }
@@ -1399,6 +1400,18 @@ final class ImageFileDirectory extends DataCube {
     }
 
     /**
+     * Returns the total number of tiles. The formulas used in this method are derived from
the formulas
+     * documented in the TIFF specification and reproduced in {@link #tileWidth} and {@link
#tileHeight}
+     * fields javadoc.
+     */
+    @Override
+    final long getNumTiles() {
+        return Math.multiplyExact(
+                Numerics.ceilDiv(imageWidth,  tileWidth),
+                Numerics.ceilDiv(imageHeight, tileHeight));
+    }
+
+    /**
      * Returns the type of raster data. The enumeration values are restricted to types compatible
with Java2D,
      * at the cost of using more bits than {@link #bitsPerSample} if there is no exact match.
      *
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
index f6d4545..17cc2d7 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
@@ -222,7 +222,8 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
                     throw new DataStoreContentException(getLocale(), Decoder.FORMAT_NAME,
input.filename, null);
                 }
             }
-            reader = new HyperRectangleReader(dataType.number, input, offset);
+            reader = new HyperRectangleReader(dataType.number, input);
+            reader.setOrigin(offset);
         } else {
             reader = null;
         }
@@ -812,7 +813,7 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
      */
     @Override
     public int compareTo(final VariableInfo other) {
-        int c = Long.compare(reader.origin, other.reader.origin);
+        int c = Long.compare(reader.getOrigin(), other.reader.getOrigin());
         if (c == 0) c = name.compareTo(other.name);                 // Should not happen,
but we are paranoiac.
         return c;
     }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
index 99e5962..6d7ca29 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
@@ -455,16 +455,6 @@ public abstract class TiledGridCoverage extends GridCoverage {
         }
 
         /**
-         * Returns the current iterator position as an index in the vector of tiles in the
{@link TiledGridResource}.
-         * Tiles are assumed stored in a row-major fashion. This position is incremented
by calls to {@link #next()}.
-         *
-         * @return current iterator position in the vector of all tiles in the resource.
-         */
-        public final int getIndexInTileVector() {
-            return indexInTileVector;
-        }
-
-        /**
          * Returns the cached tile for current iterator position.
          *
          * @return cached tile at current iterator position, or {@code null} if none.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java
index 585c931..f2a93d2 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java
@@ -43,21 +43,20 @@ public final class HyperRectangleReader {
 
     /**
      * The {@code input} position of the first sample (ignoring sub-area and subsampling).
-     * This is the {@code origin} argument given to the constructor, copied verbatim.
-     * This field is public for callers wanting to order {@code HyperRectangleReader} instances
-     * in increasing file offset order, for more sequential reading (less seek operations).
+     * This is initially the {@code origin} argument given to the constructor, copied verbatim.
+     *
+     * @see #getOrigin()
      */
-    public final long origin;
+    private long origin;
 
     /**
      * Creates a new reader for the given input and source region.
      *
      * @param  dataType  the type of elements to read, as one of the constants defined in
{@link Numbers}.
      * @param  input     the channel from which to read the values, together with a buffer
for transferring data.
-     * @param  origin    the position in the channel of the first sample value in the hyper-rectangle.
      * @throws DataStoreContentException if the given {@code dataType} is not one of the
supported values.
      */
-    public HyperRectangleReader(final byte dataType, final ChannelDataInput input, final
long origin)
+    public HyperRectangleReader(final byte dataType, final ChannelDataInput input)
             throws DataStoreContentException
     {
         switch (dataType) {
@@ -70,7 +69,6 @@ public final class HyperRectangleReader {
             case Numbers.DOUBLE:    reader = input.new DoublesReader((double[]) null); break;
             default: throw new DataStoreContentException(Errors.format(Errors.Keys.UnknownType_1,
dataType));
         }
-        this.origin = origin;
         final ByteBuffer buffer = input.buffer;
         final int pos = buffer.position();
         final int lim = buffer.limit();
@@ -92,7 +90,6 @@ public final class HyperRectangleReader {
      */
     public HyperRectangleReader(final String filename, final Buffer data) throws IOException
{
         reader = new MemoryDataTransfer(filename, data).reader();
-        origin = 0;
     }
 
     /**
@@ -114,6 +111,25 @@ public final class HyperRectangleReader {
     }
 
     /**
+     * Returns the {@code input} position of the first sample (ignoring sub-area and subsampling).
+     * This is initially the {@code origin} argument given to the constructor, copied verbatim.
+     *
+     * @return the {@code input} position of the first sample (ignoring sub-area and subsampling).
+     */
+    public long getOrigin() {
+        return origin;
+    }
+
+    /**
+     * Sets the {@code input} position of the first sample (ignoring sub-area and subsampling).
+     *
+     * @param  p  the new {@code input} position of the first sample (ignoring sub-area and
subsampling).
+     */
+    public void setOrigin(final long p) {
+        origin = p;
+    }
+
+    /**
      * Reads data in the given region. It is caller's responsibility to ensure that the {@code
Region}
      * object has been created with a {@code size} argument equals to this hyper-rectangle
size.
      *
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/HyperRectangleReaderTest.java
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/HyperRectangleReaderTest.java
index 7ca16ae..8d57fe4 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/HyperRectangleReaderTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/HyperRectangleReaderTest.java
@@ -135,7 +135,8 @@ public final strictfp class HyperRectangleReaderTest extends TestCase
{
             final ByteArrayChannel channel = new ByteArrayChannel(array, true);
             final ByteBuffer       buffer  = ByteBuffer.allocate(random.nextInt(20) + 20).order(ByteOrder.nativeOrder());
             final ChannelDataInput input   = new ChannelDataInput("HyperRectangle in channel",
channel, buffer, false);
-            reader = new HyperRectangleReader(Numbers.SHORT, input, origin);
+            reader = new HyperRectangleReader(Numbers.SHORT, input);
+            reader.setOrigin(origin);
         } else {
             view.clear();
             reader = new HyperRectangleReader("HyperRectangle in buffer", view);

Mime
View raw message