diff --git a/src/main/java/cz/it4i/qcmp/benchmark/Benchmark.java b/src/main/java/cz/it4i/qcmp/benchmark/Benchmark.java
index 117259ddf6704a5c36f96730554c1a056aefee88..4012de9f9e49f7e5ddf274510fb8dcfd44d7e031 100644
--- a/src/main/java/cz/it4i/qcmp/benchmark/Benchmark.java
+++ b/src/main/java/cz/it4i/qcmp/benchmark/Benchmark.java
@@ -51,7 +51,7 @@ public class Benchmark extends BenchmarkBase {
             return;
         }
 
-        decompressOps.setInputDataInfo(new FileInputData(qcmpFilePath));
+        decompressOps.setInputDataInfo(new FileInputData(qcmpFilePath, null));
 
         final String decompressedFile = getFileNamePathIntoOutDir(String.format(QUANTIZED_FILE_TEMPLATE,
                                                                                 options.getInputDataInfo().getPlaneIndex(),
diff --git a/src/main/java/cz/it4i/qcmp/benchmark/BenchmarkBase.java b/src/main/java/cz/it4i/qcmp/benchmark/BenchmarkBase.java
index 2e7cf835fcae750b43316e80823a8d43c33cfbc1..6fc7dabf844b60c0573eb749496efd8d2633020d 100644
--- a/src/main/java/cz/it4i/qcmp/benchmark/BenchmarkBase.java
+++ b/src/main/java/cz/it4i/qcmp/benchmark/BenchmarkBase.java
@@ -2,8 +2,8 @@ package cz.it4i.qcmp.benchmark;
 
 import cz.it4i.qcmp.cli.CompressionOptionsCLIParser;
 import cz.it4i.qcmp.compression.CompressionOptions;
+import cz.it4i.qcmp.data.HyperStackDimensions;
 import cz.it4i.qcmp.data.ImageU16;
-import cz.it4i.qcmp.data.V3i;
 import cz.it4i.qcmp.io.InputData;
 import cz.it4i.qcmp.io.RawDataIO;
 import cz.it4i.qcmp.quantization.QTrainIteration;
@@ -26,7 +26,7 @@ abstract class BenchmarkBase {
     protected final String inputFile;
     protected final String outputDirectory;
     protected final int[] planes;
-    protected final V3i rawImageDims;
+    protected final HyperStackDimensions rawImageDims;
 
     protected final int codebookSize;
     protected final String cacheFolder;
@@ -60,9 +60,8 @@ abstract class BenchmarkBase {
                 this.planes[i] = from + i;
             }
         } else {
-            final int planeCount = ifi.getDimensions().getZ();
-            this.planes = new int[planeCount];
-            for (int i = 0; i < planeCount; i++) {
+            this.planes = new int[ifi.getDimensions().getPlaneCount()];
+            for (int i = 0; i < ifi.getDimensions().getPlaneCount(); i++) {
                 this.planes[i] = i;
             }
         }
@@ -118,11 +117,11 @@ abstract class BenchmarkBase {
      * @return True if file was saved.
      */
     protected boolean saveQuantizedPlaneData(final int[] data, final String filename) {
-        final ImageU16 img = new ImageU16(rawImageDims.getX(), rawImageDims.getY(), data);
+        final ImageU16 img = new ImageU16(rawImageDims.getWidth(), rawImageDims.getHeight(), data);
         try {
             // NOTE(Moravec): Use big endian so that FIJI can read the image.
             RawDataIO.writeImageU16(getFileNamePathIntoOutDir(filename), img, false);
-            System.out.println(String.format("Saved %s", filename));
+            System.out.printf("Saved %s\n", filename);
         } catch (final Exception e) {
             e.printStackTrace();
             return false;
@@ -146,8 +145,8 @@ abstract class BenchmarkBase {
         final String diffFilePath = getFileNamePathIntoOutDir(diffFile);
         final String absDiffFilePath = getFileNamePathIntoOutDir(absDiffFile);
 
-        final ImageU16 img = new ImageU16(rawImageDims.getX(),
-                                          rawImageDims.getY(),
+        final ImageU16 img = new ImageU16(rawImageDims.getWidth(),
+                                          rawImageDims.getHeight(),
                                           absDifferenceData);
         try {
             // NOTE(Moravec): Use little endian so that gnuplot can read the array.
diff --git a/src/main/java/cz/it4i/qcmp/benchmark/SQBenchmark.java b/src/main/java/cz/it4i/qcmp/benchmark/SQBenchmark.java
index a90c39df9e4534578e3a5e16539ca96818c702b5..5d99107ca84674d8bf564c85f938b06287cf58eb 100644
--- a/src/main/java/cz/it4i/qcmp/benchmark/SQBenchmark.java
+++ b/src/main/java/cz/it4i/qcmp/benchmark/SQBenchmark.java
@@ -54,7 +54,7 @@ public class SQBenchmark extends BenchmarkBase {
             quantizer = new ScalarQuantizer(codebook);
             System.out.println("Created quantizer from cache");
         } else if (options.getCodebookType() == CompressionOptions.CodebookType.MiddlePlane) {
-            final int middlePlaneIndex = rawImageDims.getZ() / 2;
+            final int middlePlaneIndex = rawImageDims.getPlaneCount() / 2;
 
             final int[] middlePlaneData;
             try {
diff --git a/src/main/java/cz/it4i/qcmp/benchmark/VQBenchmark.java b/src/main/java/cz/it4i/qcmp/benchmark/VQBenchmark.java
index 33e0a361be7dc68fc882ad89624fde17234caf5f..f595914ff789e54adea975a46a85b6b0d2c7e340 100644
--- a/src/main/java/cz/it4i/qcmp/benchmark/VQBenchmark.java
+++ b/src/main/java/cz/it4i/qcmp/benchmark/VQBenchmark.java
@@ -19,7 +19,7 @@ public class VQBenchmark extends BenchmarkBase {
 
     private ImageU16 reconstructImageFromQuantizedVectors(final int[][] vectors,
                                                           final V2i qVector) {
-        final Block reconstructedChunk = new Block(new V2i(rawImageDims.getX(), rawImageDims.getY()));
+        final Block reconstructedChunk = new Block(rawImageDims.getPlaneDimensions());
         if (qVector.getY() > 1) {
             reconstructedChunk.reconstructFrom2DVectors(vectors, qVector);
         } else {
diff --git a/src/main/java/cz/it4i/qcmp/cli/functions/EntropyCalculation.java b/src/main/java/cz/it4i/qcmp/cli/functions/EntropyCalculation.java
index d58a61e5ed7d53a13972f2f767fb1bbb8213dd95..b01629a950fd026ef984ee28190c6f13e75f6742 100644
--- a/src/main/java/cz/it4i/qcmp/cli/functions/EntropyCalculation.java
+++ b/src/main/java/cz/it4i/qcmp/cli/functions/EntropyCalculation.java
@@ -36,7 +36,7 @@ public class EntropyCalculation extends CustomFunctionBase {
 
             reportWriter.write("Plane;Entropy\n");
 
-            for (int planeIndex = 0; planeIndex < options.getInputDataInfo().getDimensions().getZ(); planeIndex++) {
+            for (int planeIndex = 0; planeIndex < options.getInputDataInfo().getDimensions().getPlaneCount(); planeIndex++) {
 
                 final int[] planeData = loader.loadPlaneData(planeIndex);
                 final double planeEntropy = Utils.calculateEntropy(planeData);
diff --git a/src/main/java/cz/it4i/qcmp/cli/functions/MeasurePlaneErrorFunction.java b/src/main/java/cz/it4i/qcmp/cli/functions/MeasurePlaneErrorFunction.java
index b3db0bca79bc5b2932a739b4d1599346ef41858b..9c6772a2a779a133419b54b0471c88982657f6cf 100644
--- a/src/main/java/cz/it4i/qcmp/cli/functions/MeasurePlaneErrorFunction.java
+++ b/src/main/java/cz/it4i/qcmp/cli/functions/MeasurePlaneErrorFunction.java
@@ -2,7 +2,7 @@ package cz.it4i.qcmp.cli.functions;
 
 import cz.it4i.qcmp.cli.CompressionOptionsCLIParser;
 import cz.it4i.qcmp.cli.CustomFunctionBase;
-import cz.it4i.qcmp.data.V3i;
+import cz.it4i.qcmp.data.HyperStackDimensions;
 import cz.it4i.qcmp.io.FileInputData;
 import cz.it4i.qcmp.io.loader.RawDataLoader;
 import cz.it4i.qcmp.utilities.Stopwatch;
@@ -20,12 +20,10 @@ public class MeasurePlaneErrorFunction extends CustomFunctionBase {
 
     private final String OriginalFileForChannel0 = "D:\\biology\\tiff_data\\fused_tp_10_ch_0_16bit.raw";
     private final String OriginalFileForChannel1 = "D:\\biology\\tiff_data\\fused_tp_10_ch_1_16bit.raw";
-    private final V3i ReferenceFileDimensions = new V3i(1041, 996, 946);
+    private final HyperStackDimensions ReferenceFileDimensions = new HyperStackDimensions(1041, 996, 946);
 
-    private int[][] loadPlanes(final String srcFile, final V3i imageDims) throws IOException {
-        final FileInputData inputDataInfo = new FileInputData(srcFile);
-        inputDataInfo.setDimension(imageDims);
-        final RawDataLoader loader = new RawDataLoader(inputDataInfo);
+    private int[][] loadPlanes(final String srcFile, final HyperStackDimensions imageDims) throws IOException {
+        final RawDataLoader loader = new RawDataLoader(new FileInputData(srcFile, imageDims));
 
         return loader.loadAllPlanesTo2DArray();
     }
@@ -132,10 +130,10 @@ public class MeasurePlaneErrorFunction extends CustomFunctionBase {
         reportWriter.write("=========================================\n");
         reportWriter.write("PlaneIndex;ErrorSum;MeanError\n");
 
-        final int planePixelCount = ReferenceFileDimensions.toV2i().multiplyTogether();
+        final int planePixelCount = ReferenceFileDimensions.getNumberOfElementsInDimension(2);
         final int[] diffData = new int[planePixelCount];
 
-        for (int plane = 0; plane < ReferenceFileDimensions.getZ(); plane++) {
+        for (int plane = 0; plane < ReferenceFileDimensions.getPlaneCount(); plane++) {
             Utils.differenceToArray(referenceData[plane], testData[plane], diffData);
             Utils.applyAbsFunction(diffData);
 
diff --git a/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java b/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java
index 2df7a95306a9114e5c99b6c0d0cf2fe3ca6eabf8..22b328d64310d8764bcc1b61f1cfa15909aecc02 100644
--- a/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java
+++ b/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java
@@ -119,7 +119,7 @@ public abstract class CompressorDecompressorBase {
             }
             return indices;
         } else {
-            return generateAllPlaneIndices(inputData.getDimensions().getZ());
+            return generateAllPlaneIndices(inputData.getDimensions().getPlaneCount());
         }
     }
 
@@ -144,7 +144,7 @@ public abstract class CompressorDecompressorBase {
      * @return Index of the middle plane.
      */
     protected int getMiddlePlaneIndex() {
-        return (options.getInputDataInfo().getDimensions().getZ() / 2);
+        return (options.getInputDataInfo().getDimensions().getPlaneCount() / 2);
     }
 
     /**
diff --git a/src/main/java/cz/it4i/qcmp/compression/ImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/ImageCompressor.java
index 61d5d45b5fdcd5e83f04004589f818736156d911..a7cd2ddac5a717c6007d7f0fe1e00ce166035544 100644
--- a/src/main/java/cz/it4i/qcmp/compression/ImageCompressor.java
+++ b/src/main/java/cz/it4i/qcmp/compression/ImageCompressor.java
@@ -131,7 +131,7 @@ public class ImageCompressor extends CompressorDecompressorBase {
         }
         duplicateAllListeners(imageCompressor);
 
-        long[] planeDataSizes = null;
+        final long[] planeDataSizes;
 
         try (final FileOutputStream fos = new FileOutputStream(options.getOutputFilePath(), false);
              final DataOutputStream compressStream = new DataOutputStream(new BufferedOutputStream(fos, 8192))) {
@@ -193,7 +193,7 @@ public class ImageCompressor extends CompressorDecompressorBase {
             final Range<Integer> planeRange = options.getInputDataInfo().getPlaneRange();
             return ((planeRange.getTo() + 1) - planeRange.getFrom());
         } else {
-            return options.getInputDataInfo().getDimensions().getZ();
+            return options.getInputDataInfo().getDimensions().getPlaneCount();
         }
     }
 
@@ -225,8 +225,8 @@ public class ImageCompressor extends CompressorDecompressorBase {
 
         header.setCodebookPerPlane(options.getCodebookType() == CompressionOptions.CodebookType.Individual);
 
-        header.setImageSizeX(options.getInputDataInfo().getDimensions().getX());
-        header.setImageSizeY(options.getInputDataInfo().getDimensions().getY());
+        header.setImageSizeX(options.getInputDataInfo().getDimensions().getWidth());
+        header.setImageSizeY(options.getInputDataInfo().getDimensions().getHeight());
         header.setImageSizeZ(getNumberOfPlanes());
 
         header.setVectorDimension(options.getQuantizationVector());
diff --git a/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java
index 78f9cbe214b3c8b1bc7c7ddabef02742b0f5c4b9..00ac7628818a6dd0371f380cae8955cd3ed0842b 100644
--- a/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java
+++ b/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java
@@ -203,7 +203,7 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm
         int[] trainData = null;
 
         if (options.getCodebookType() == CompressionOptions.CodebookType.MiddlePlane) {
-            final int middlePlaneIndex = inputDataInfo.getDimensions().getZ() / 2;
+            final int middlePlaneIndex = getMiddlePlaneIndex();
             reportStatusToListeners("Loading single plane data.");
             trainData = planeLoader.loadPlaneData(middlePlaneIndex);
         } else if (inputDataInfo.isPlaneIndexSet()) {
diff --git a/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java
index 60182aabc588020639bb3d3b39a1029d1a7813af..2a064ebc40c98e83d14630b7feac63803c34bbae 100644
--- a/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java
+++ b/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java
@@ -185,9 +185,9 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm
         if (streamMode) {
             try {
                 // Image dimensions
-                compressStream.writeShort(inputData.getDimensions().getX());
-                compressStream.writeShort(inputData.getDimensions().getY());
-                compressStream.writeShort(inputData.getDimensions().getZ());
+                compressStream.writeShort(inputData.getDimensions().getWidth());
+                compressStream.writeShort(inputData.getDimensions().getHeight());
+                compressStream.writeShort(inputData.getDimensions().getPlaneCount());
 
                 // Write voxel layer in stream mode.
                 compressStream.writeShort(planeIndices.length);
@@ -266,13 +266,13 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm
         }
 
         final int voxelLayerDepth = options.getQuantizationVector().getZ();
-        final int voxelLayerCount = calculateVoxelLayerCount(inputData.getDimensions().getZ(), voxelLayerDepth);
+        final int voxelLayerCount = calculateVoxelLayerCount(inputData.getDimensions().getPlaneCount(), voxelLayerDepth);
         if (streamMode) {
             try {
                 // Image dimensions
-                compressStream.writeShort(inputData.getDimensions().getX());
-                compressStream.writeShort(inputData.getDimensions().getY());
-                compressStream.writeShort(inputData.getDimensions().getZ());
+                compressStream.writeShort(inputData.getDimensions().getWidth());
+                compressStream.writeShort(inputData.getDimensions().getHeight());
+                compressStream.writeShort(inputData.getDimensions().getPlaneCount());
 
                 // Write voxel layer in stream mode.
                 compressStream.writeShort(voxelLayerCount);
@@ -294,7 +294,7 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm
             final int fromZ = (voxelLayerIndex * voxelLayerDepth);
 
             final int toZ = (voxelLayerIndex == voxelLayerCount - 1)
-                    ? inputData.getDimensions().getZ()
+                    ? inputData.getDimensions().getPlaneCount()
                     : (voxelLayerDepth + (voxelLayerIndex * voxelLayerDepth));
             assert (toZ >= fromZ);
 
@@ -407,7 +407,7 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm
     int[][] loadDataForCodebookTraining(final IPlaneLoader planeLoader) throws ImageCompressionException {
         final int[][] trainingData;
         if (options.getCodebookType() == CompressionOptions.CodebookType.MiddlePlane) {
-            final int middlePlaneIndex = (options.getInputDataInfo().getDimensions().getZ() / 2);
+            final int middlePlaneIndex = getMiddlePlaneIndex();
             trainingData = planeLoader.loadVectorsFromPlaneRange(options, new Range<>(middlePlaneIndex, middlePlaneIndex + 1));
         } else if (options.getInputDataInfo().isPlaneIndexSet()) {
             reportStatusToListeners("VQ: Loading single plane data.");
@@ -419,7 +419,8 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm
         } else {
             reportStatusToListeners("VQ: Loading all planes data.");
             trainingData = planeLoader.loadVectorsFromPlaneRange(options,
-                                                                 new Range<>(0, options.getInputDataInfo().getDimensions().getZ()));
+                                                                 new Range<>(0,
+                                                                             options.getInputDataInfo().getDimensions().getPlaneCount()));
         }
         return trainingData;
     }
diff --git a/src/main/java/cz/it4i/qcmp/data/HyperStackDimensions.java b/src/main/java/cz/it4i/qcmp/data/HyperStackDimensions.java
index 78a71be436c958544f02845ac3b66561f7581b29..101a5f5d2099fdc3a62a00558f22d7be5127296c 100644
--- a/src/main/java/cz/it4i/qcmp/data/HyperStackDimensions.java
+++ b/src/main/java/cz/it4i/qcmp/data/HyperStackDimensions.java
@@ -9,72 +9,107 @@ import java.util.Objects;
 public class HyperStackDimensions {
     private final int width;
     private final int height;
-    private final int sliceCount;
+    private final int planeCount;
     private final int numberOfTimepoints;
 
     /**
      * Create HyperStackDimensions.
      *
-     * @param width              Width of the slice.
-     * @param height             Height of the slice.
-     * @param sliceCount         Slice count in the stack.
+     * @param width              Width of the plane.
+     * @param height             Height of the plane.
+     * @param planeCount         Plane count in the stack.
      * @param numberOfTimepoints Number of stack timepoints.
      */
-    public HyperStackDimensions(final int width, final int height, final int sliceCount, final int numberOfTimepoints) {
+    public HyperStackDimensions(final int width, final int height, final int planeCount, final int numberOfTimepoints) {
         this.width = width;
         this.height = height;
-        this.sliceCount = sliceCount;
+        this.planeCount = planeCount;
         this.numberOfTimepoints = numberOfTimepoints;
     }
 
+    /**
+     * Get number of elements in hyperstack with dimensionality = dimension.
+     * When calculating the element count, overflow is checked. This is because result of this
+     * function is usually used in places where we want to allocate memory.
+     *
+     * @param dimension Maximum dimension.
+     * @return Number of elements.
+     */
+    @SuppressWarnings("DuplicateExpressions")
+    public int getNumberOfElementsInDimension(final int dimension) {
+        switch (dimension) {
+            case 1:
+                return width;
+            case 2:
+                return Math.multiplyExact(width, height);
+            case 3:
+                return Math.multiplyExact(planeCount, Math.multiplyExact(width, height));
+            case 4:
+                return Math.multiplyExact(numberOfTimepoints, Math.multiplyExact(planeCount, Math.multiplyExact(width, height)));
+            default:
+                assert (false) : "Wrong dimension in getNumberOfElementsInDimension";
+                return -1;
+        }
+    }
+
     /**
      * Create HyperStackDimensions for single timepoint.
      *
-     * @param width      Width of the slice.
-     * @param height     Height of the slice.
-     * @param sliceCount Slice count in the stack.
+     * @param width      Width of the plane.
+     * @param height     Height of the plane.
+     * @param planeCount Plane count in the stack.
      */
-    public HyperStackDimensions(final int width, final int height, final int sliceCount) {
-        this(width, height, sliceCount, 1);
+    public HyperStackDimensions(final int width, final int height, final int planeCount) {
+        this(width, height, planeCount, 1);
     }
 
     /**
-     * Create HyperStackDimensions for single slice and single timepoint.
+     * Create HyperStackDimensions for single plane and single timepoint.
      *
-     * @param width  Width of the slice.
-     * @param height Height of the slice.
+     * @param width  Width of the plane.
+     * @param height Height of the plane.
      */
     public HyperStackDimensions(final int width, final int height) {
         this(width, height, 1, 1);
     }
 
     /**
-     * Get single slice width. (X)
+     * Get single plane width. (X)
      *
-     * @return Slice width.
+     * @return Plane width.
      */
     public final int getWidth() {
         return width;
     }
 
     /**
-     * Get single slice height. (Y)
+     * Get single plane height. (Y)
      *
-     * @return Slice height.
+     * @return Plane height.
      */
     public final int getHeight() {
         return height;
     }
 
     /**
-     * Get slice count. (Z, Plane Count)
+     * Get plane count. (Z)
      *
-     * @return Slice count.
+     * @return Plane count.
      */
-    public final int getSliceCount() {
-        return sliceCount;
+    public final int getPlaneCount() {
+        return planeCount;
     }
 
+    /**
+     * Get dimensions of the single plane.
+     *
+     * @return Plane dimensions.
+     */
+    public V2i getPlaneDimensions() {
+        return new V2i(width, height);
+    }
+
+
     /**
      * Get number of timepoints of the stack.
      *
@@ -86,12 +121,12 @@ public class HyperStackDimensions {
 
     @Override
     public String toString() {
-        return String.format("X=%d;Y=%d;Z=%d;T=%d", width, height, sliceCount, numberOfTimepoints);
+        return String.format("X=%d;Y=%d;Z=%d;T=%d", width, height, planeCount, numberOfTimepoints);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(width, height, sliceCount, numberOfTimepoints);
+        return Objects.hash(width, height, planeCount, numberOfTimepoints);
     }
 
     @Override
@@ -99,7 +134,7 @@ public class HyperStackDimensions {
         if (obj instanceof HyperStackDimensions) {
             final HyperStackDimensions other = (HyperStackDimensions) obj;
             return (width == other.width && height == other.height &&
-                    sliceCount == other.sliceCount && numberOfTimepoints == other.numberOfTimepoints);
+                    planeCount == other.planeCount && numberOfTimepoints == other.numberOfTimepoints);
         }
         return super.equals(obj);
     }
diff --git a/src/main/java/cz/it4i/qcmp/data/V2i.java b/src/main/java/cz/it4i/qcmp/data/V2i.java
index aca7fe528e3fe2be6e23cc62337c877f3afe5493..fa9a4862fbbd7e24d084f1ceb4cf438755a25b70 100644
--- a/src/main/java/cz/it4i/qcmp/data/V2i.java
+++ b/src/main/java/cz/it4i/qcmp/data/V2i.java
@@ -22,6 +22,6 @@ public final class V2i extends V2<Integer> {
     }
 
     public final int multiplyTogether() {
-        return getX() * getY();
+        return Math.multiplyExact(getX(), getY());
     }
 }
diff --git a/src/main/java/cz/it4i/qcmp/data/V3i.java b/src/main/java/cz/it4i/qcmp/data/V3i.java
index b75767215a4a956d37e55f27d9ed01be0c33908d..50d760cb96bab8567d20a884abb2c1ef6d542f03 100644
--- a/src/main/java/cz/it4i/qcmp/data/V3i.java
+++ b/src/main/java/cz/it4i/qcmp/data/V3i.java
@@ -32,7 +32,7 @@ public final class V3i extends V3<Integer> {
         return new V2i(getX(), getY());
     }
 
-    public final long multiplyTogether() {
-        return ((long) getX() * (long) getY() * (long) getZ());
+    public final int multiplyTogether() {
+        return Math.multiplyExact(getZ(), Math.multiplyExact(getX(), getY()));
     }
 }
diff --git a/src/main/java/cz/it4i/qcmp/io/loader/CallbackLoader.java b/src/main/java/cz/it4i/qcmp/io/loader/CallbackLoader.java
index 629ac238110453a8e7aacbf0ff7f34432fa3df1f..a6b370b8154b6997032504350a88101a49060e69 100644
--- a/src/main/java/cz/it4i/qcmp/io/loader/CallbackLoader.java
+++ b/src/main/java/cz/it4i/qcmp/io/loader/CallbackLoader.java
@@ -8,7 +8,7 @@ import cz.it4i.qcmp.io.CallbackInputData;
 import java.io.IOException;
 import java.util.Arrays;
 
-public class CallbackLoader extends BasicLoader implements IPlaneLoader {
+public class CallbackLoader extends GenericLoader implements IPlaneLoader {
 
     private final CallbackInputData callbackInputData;
     private final CallbackInputData.LoadCallback pixelLoad;
@@ -27,12 +27,12 @@ public class CallbackLoader extends BasicLoader implements IPlaneLoader {
 
     @Override
     public int[] loadPlaneData(final int plane) {
-        final int planePixelCount = dims.getX() * dims.getY();
+        final int planePixelCount = dims.getNumberOfElementsInDimension(2);
         final int[] planeData = new int[planePixelCount];
 
         int index = 0;
-        for (int y = 0; y < dims.getY(); y++) {
-            for (int x = 0; x < dims.getX(); x++) {
+        for (int y = 0; y < dims.getHeight(); y++) {
+            for (int x = 0; x < dims.getWidth(); x++) {
                 planeData[index++] = pixelLoad.getValueAt(x, y, plane);
             }
         }
@@ -45,10 +45,10 @@ public class CallbackLoader extends BasicLoader implements IPlaneLoader {
             return new int[0];
         } else if (planes.length == 1) {
             return loadPlaneData(planes[0]);
-        } else if (planes.length == dims.getZ()) {
+        } else if (planes.length == dims.getPlaneCount()) {
             return loadAllPlanesU16Data();
         }
-        final int planePixelCount = dims.getX() * dims.getY();
+        final int planePixelCount = dims.getNumberOfElementsInDimension(2);
         final long totalValueCount = (long) planePixelCount * (long) planes.length;
         if (totalValueCount > (long) Integer.MAX_VALUE) {
             throw new IOException("Unable to load image data for planes, file size is too big.");
@@ -59,8 +59,8 @@ public class CallbackLoader extends BasicLoader implements IPlaneLoader {
         final int[] destBuffer = new int[(int) totalValueCount];
         int index = 0;
         for (final int plane : planes) {
-            for (int y = 0; y < dims.getY(); y++) {
-                for (int x = 0; x < dims.getX(); x++) {
+            for (int y = 0; y < dims.getHeight(); y++) {
+                for (int x = 0; x < dims.getWidth(); x++) {
                     destBuffer[index++] = pixelLoad.getValueAt(x, y, plane);
                 }
             }
@@ -69,17 +69,14 @@ public class CallbackLoader extends BasicLoader implements IPlaneLoader {
     }
 
     @Override
-    public int[] loadAllPlanesU16Data() throws IOException {
-        final long totalValueCount = dims.multiplyTogether();
-        if (totalValueCount > (long) Integer.MAX_VALUE) {
-            throw new IOException("Unable to load all image data, file size is too big.");
-        }
+    public int[] loadAllPlanesU16Data() {
+        final long totalValueCount = dims.getNumberOfElementsInDimension(3);
 
         final int[] destBuffer = new int[(int) totalValueCount];
         int index = 0;
-        for (int z = 0; z < dims.getZ(); z++) {
-            for (int y = 0; y < dims.getY(); y++) {
-                for (int x = 0; x < dims.getX(); x++) {
+        for (int z = 0; z < dims.getPlaneCount(); z++) {
+            for (int y = 0; y < dims.getHeight(); y++) {
+                for (int x = 0; x < dims.getWidth(); x++) {
                     destBuffer[index++] = pixelLoad.getValueAt(x, y, z);
                 }
             }
diff --git a/src/main/java/cz/it4i/qcmp/io/loader/FlatBufferLoader.java b/src/main/java/cz/it4i/qcmp/io/loader/FlatBufferLoader.java
index a5b60bb7760ce1da573da626625eeb9c6212cfc4..c580c0e62eecc88ee6e1fb2c7a8fa8f70db7dafa 100644
--- a/src/main/java/cz/it4i/qcmp/io/loader/FlatBufferLoader.java
+++ b/src/main/java/cz/it4i/qcmp/io/loader/FlatBufferLoader.java
@@ -13,7 +13,7 @@ import java.util.Arrays;
 /**
  * This loader is used when the entire dataset is stored in single buffer (array).
  */
-public class FlatBufferLoader extends BasicLoader implements IPlaneLoader {
+public class FlatBufferLoader extends GenericLoader implements IPlaneLoader {
     /**
      * Flat buffer information.
      */
@@ -27,7 +27,7 @@ public class FlatBufferLoader extends BasicLoader implements IPlaneLoader {
     public FlatBufferLoader(final FlatBufferInputData bufferDataInfo) {
         super(bufferDataInfo.getDimensions());
         this.bufferInputData = bufferDataInfo;
-        planePixelCount = dims.getX() * dims.getY();
+        planePixelCount = dims.getNumberOfElementsInDimension(2);
     }
 
     @Override
@@ -68,18 +68,15 @@ public class FlatBufferLoader extends BasicLoader implements IPlaneLoader {
             return new int[0];
         } else if (planes.length == 1) {
             return loadPlaneData(planes[0]);
-        } else if (planes.length == bufferInputData.getDimensions().getZ()) { // Maybe?
+        } else if (planes.length == bufferInputData.getDimensions().getPlaneCount()) {
             return loadAllPlanesU16Data();
         }
-        final long totalValueCount = (long) planePixelCount * (long) planes.length;
-        if (totalValueCount > (long) Integer.MAX_VALUE) {
-            throw new IOException("Unable to load image data for planes, file size is too big.");
-        }
+        final int totalValueCount = Math.multiplyExact(planePixelCount, planes.length);
 
         Arrays.sort(planes);
 
         final short[] flatBuffer = (short[]) bufferInputData.getPixelBuffer();
-        final int[] destBuffer = new int[(int) totalValueCount];
+        final int[] destBuffer = new int[totalValueCount];
         int destOffset = 0;
         for (final int planeIndex : planes) {
             final int planeOffset = planeIndex * planePixelCount;
diff --git a/src/main/java/cz/it4i/qcmp/io/loader/BasicLoader.java b/src/main/java/cz/it4i/qcmp/io/loader/GenericLoader.java
similarity index 80%
rename from src/main/java/cz/it4i/qcmp/io/loader/BasicLoader.java
rename to src/main/java/cz/it4i/qcmp/io/loader/GenericLoader.java
index a5bac1b035d88aac8927868dcee26f0f9a0a6150..8363538bf2dcf2809ebc5f4bd733045bece0d781 100644
--- a/src/main/java/cz/it4i/qcmp/io/loader/BasicLoader.java
+++ b/src/main/java/cz/it4i/qcmp/io/loader/GenericLoader.java
@@ -4,21 +4,20 @@ import cz.it4i.qcmp.data.*;
 
 import java.io.IOException;
 
-public abstract class BasicLoader {
-    protected final V3i dims;
+abstract class GenericLoader {
+    protected final HyperStackDimensions dims;
     protected int threadCount = 1;
 
     private DataWrappingStrategy wrappingStrategy = DataWrappingStrategy.MirroredRepeat;
 
-    protected BasicLoader(final V3i datasetDims) {
+    protected GenericLoader(final HyperStackDimensions datasetDims) {
         this.dims = datasetDims;
     }
 
-    public V3i getImageDimensions() {
+    public HyperStackDimensions getDatasetDimensions() {
         return dims;
     }
 
-
     public DataWrappingStrategy getWrappingStrategy() {
         return wrappingStrategy;
     }
@@ -37,19 +36,6 @@ public abstract class BasicLoader {
      */
     public abstract int[] loadPlaneData(final int plane) throws IOException;
 
-    //    /**
-    //     * Read value from plane at specified offset.
-    //     *
-    //     * @param plane  Zero based plane index.
-    //     * @param offset Offset on flattened plane data.
-    //     * @return Plane value at the index.
-    //     */
-    //    protected int valueAt(final int plane, final int offset) {
-    //        new Exception().printStackTrace(System.err);
-    //        assert (false) : "Unimplemented overload of BasicLoader::valueAt()";
-    //        return Integer.MIN_VALUE;
-    //    }
-
     protected abstract int valueAt(final int plane, final int x, final int y, final int width);
 
     /**
@@ -60,9 +46,9 @@ public abstract class BasicLoader {
      */
     private int wrapColumnIndex(final int x) {
         if (wrappingStrategy == DataWrappingStrategy.ClampToEdge) {
-            return dims.getX() - 1;
+            return dims.getWidth() - 1;
         } else if (wrappingStrategy == DataWrappingStrategy.MirroredRepeat) {
-            return (dims.getX() - (1 + (x % dims.getX())));
+            return (dims.getWidth() - (1 + (x % dims.getWidth())));
         }
         return x;
     }
@@ -75,9 +61,9 @@ public abstract class BasicLoader {
      */
     private int wrapRowIndex(final int y) {
         if (wrappingStrategy == DataWrappingStrategy.ClampToEdge) {
-            return dims.getY() - 1;
+            return dims.getHeight() - 1;
         } else if (wrappingStrategy == DataWrappingStrategy.MirroredRepeat) {
-            return (dims.getY() - (1 + (y % dims.getY())));
+            return (dims.getHeight() - (1 + (y % dims.getHeight())));
         }
         return y;
     }
@@ -90,17 +76,17 @@ public abstract class BasicLoader {
      */
     private int wrapPlaneIndex(final int z) {
         if (wrappingStrategy == DataWrappingStrategy.ClampToEdge) {
-            return dims.getZ() - 1;
+            return dims.getPlaneCount() - 1;
         } else if (wrappingStrategy == DataWrappingStrategy.MirroredRepeat) {
-            return (dims.getZ() - (1 + (z % dims.getZ())));
+            return (dims.getPlaneCount() - (1 + (z % dims.getPlaneCount())));
         }
         return z;
     }
 
     protected int[][] loadRowVectorsImplByLoadPlaneData(final int vectorSize, final Range<Integer> planeRange) throws IOException {
-        final int rowVectorCount = (int) Math.ceil((double) dims.getX() / (double) vectorSize);
+        final int rowVectorCount = (int) Math.ceil((double) dims.getWidth() / (double) vectorSize);
         final int planeCount = planeRange.getTo() - planeRange.getFrom();
-        final int vectorCount = planeCount * dims.getY() * rowVectorCount;
+        final int vectorCount = planeCount * dims.getHeight() * rowVectorCount;
         final int[][] rowVectors = new int[vectorCount][vectorSize];
 
         int vectorIndex = 0;
@@ -108,19 +94,19 @@ public abstract class BasicLoader {
 
         for (int plane = planeRange.getFrom(); plane < planeRange.getTo(); plane++) {
             final int[] planeData = loadPlaneData(plane);
-            for (int row = 0; row < dims.getY(); row++) {
+            for (int row = 0; row < dims.getHeight(); row++) {
                 for (int rowVectorIndex = 0; rowVectorIndex < rowVectorCount; rowVectorIndex++) {
                     // Copy single vector.
                     baseX = rowVectorIndex * vectorSize;
 
                     for (int vectorX = 0; vectorX < vectorSize; vectorX++) {
                         srcX = baseX + vectorX;
-                        if (srcX >= dims.getX()) {
+                        if (srcX >= dims.getWidth()) {
                             if (wrappingStrategy == DataWrappingStrategy.LeaveBlank)
                                 break;
                             srcX = wrapColumnIndex(srcX);
                         }
-                        rowVectors[vectorIndex][vectorX] = planeData[Block.index(srcX, row, dims.getX())];
+                        rowVectors[vectorIndex][vectorX] = planeData[Block.index(srcX, row, dims.getWidth())];
                     }
                     ++vectorIndex;
                 }
@@ -130,8 +116,8 @@ public abstract class BasicLoader {
     }
 
     protected int[][] loadRowVectorsImplByValueAt(final int vectorSize, final Range<Integer> planeRange) {
-        final int rowVectorCount = (int) Math.ceil((double) dims.getX() / (double) vectorSize);
-        final int vectorCount = dims.getZ() * dims.getY() * rowVectorCount;
+        final int rowVectorCount = (int) Math.ceil((double) dims.getWidth() / (double) vectorSize);
+        final int vectorCount = dims.getPlaneCount() * dims.getHeight() * rowVectorCount;
 
         final int[][] rowVectors = new int[vectorCount][vectorSize];
 
@@ -139,21 +125,21 @@ public abstract class BasicLoader {
         int baseX, srcX;
 
         for (int plane = planeRange.getFrom(); plane < planeRange.getTo(); plane++) {
-            for (int row = 0; row < dims.getY(); row++) {
+            for (int row = 0; row < dims.getHeight(); row++) {
                 for (int rowVectorIndex = 0; rowVectorIndex < rowVectorCount; rowVectorIndex++) {
                     // Copy single vector.
                     baseX = rowVectorIndex * vectorSize;
 
                     for (int vectorX = 0; vectorX < vectorSize; vectorX++) {
                         srcX = (baseX + vectorX);
-                        if (srcX >= dims.getX()) {
+                        if (srcX >= dims.getWidth()) {
                             if (wrappingStrategy == DataWrappingStrategy.LeaveBlank)
                                 break;
                             srcX = wrapColumnIndex(srcX);
                         }
 
-                        // TODO(Moravec): dims.getY() should probably be dims.getX()! Check this!
-                        rowVectors[vectorIndex][vectorX] = valueAt(plane, srcX, row, dims.getY());
+                        // TODO(Moravec): dims.getHeight() should probably be dims.getWidth()! Check this!
+                        rowVectors[vectorIndex][vectorX] = valueAt(plane, srcX, row, dims.getHeight());
                     }
                     ++vectorIndex;
                 }
@@ -165,15 +151,15 @@ public abstract class BasicLoader {
     protected int[][] loadBlocksImplByLoadPlaneData(final V2i blockDim, final Range<Integer> planeRange) throws IOException {
         final int blockSize = blockDim.multiplyTogether();
         final int planeCount = planeRange.getTo() - planeRange.getFrom();
-        final int blockCount = planeCount * Block.calculateRequiredChunkCount(dims.toV2i(), blockDim);
+        final int blockCount = planeCount * Block.calculateRequiredChunkCount(dims.getPlaneDimensions(), blockDim);
 
         final int[][] blocks = new int[blockCount][blockSize];
 
         int blockIndex = 0;
         for (int plane = planeRange.getFrom(); plane < planeRange.getTo(); plane++) {
             final int[] planeData = loadPlaneData(plane);
-            for (int blockYOffset = 0; blockYOffset < dims.getY(); blockYOffset += blockDim.getY()) {
-                for (int blockXOffset = 0; blockXOffset < dims.getX(); blockXOffset += blockDim.getX()) {
+            for (int blockYOffset = 0; blockYOffset < dims.getHeight(); blockYOffset += blockDim.getY()) {
+                for (int blockXOffset = 0; blockXOffset < dims.getWidth(); blockXOffset += blockDim.getX()) {
                     loadBlock(blocks[blockIndex++], planeData, blockXOffset, blockYOffset, blockDim);
                 }
             }
@@ -184,14 +170,14 @@ public abstract class BasicLoader {
     protected int[][] loadBlocksImplByValueAt(final V2i blockDim, final Range<Integer> planeRange) {
         final int blockSize = blockDim.multiplyTogether();
         final int planeCount = planeRange.getTo() - planeRange.getFrom();
-        final int blockCount = planeCount * Block.calculateRequiredChunkCount(dims.toV2i(), blockDim);
+        final int blockCount = planeCount * Block.calculateRequiredChunkCount(dims.getPlaneDimensions(), blockDim);
 
         final int[][] blocks = new int[blockCount][blockSize];
 
         int blockIndex = 0;
         for (int plane = planeRange.getFrom(); plane < planeRange.getTo(); plane++) {
-            for (int blockYOffset = 0; blockYOffset < dims.getY(); blockYOffset += blockDim.getY()) {
-                for (int blockXOffset = 0; blockXOffset < dims.getX(); blockXOffset += blockDim.getX()) {
+            for (int blockYOffset = 0; blockYOffset < dims.getHeight(); blockYOffset += blockDim.getY()) {
+                for (int blockXOffset = 0; blockXOffset < dims.getWidth(); blockXOffset += blockDim.getX()) {
                     loadBlock(blocks[blockIndex++], plane, blockXOffset, blockYOffset, blockDim);
                 }
             }
@@ -204,7 +190,7 @@ public abstract class BasicLoader {
         for (int y = 0; y < blockDim.getY(); y++) {
             srcY = blockYOffset + y;
             // Row overflow
-            if (srcY >= dims.getY()) {
+            if (srcY >= dims.getHeight()) {
                 if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
                     break;
                 }
@@ -215,25 +201,24 @@ public abstract class BasicLoader {
                 srcX = blockXOffset + x;
 
                 // Column overflow.
-                if (srcX >= dims.getX()) {
+                if (srcX >= dims.getWidth()) {
                     if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
                         break;
                     }
                     srcX = wrapColumnIndex(srcX);
                 }
 
-                block[Block.index(x, y, blockDim.getX())] = valueAt(planeIndex, srcX, srcY, dims.getX());
+                block[Block.index(x, y, blockDim.getX())] = valueAt(planeIndex, srcX, srcY, dims.getWidth());
             }
         }
     }
 
-
     private void loadBlock(final int[] block, final int[] planeData, final int blockXOffset, final int blockYOffset, final V2i blockDim) {
         int srcX, srcY;
         for (int y = 0; y < blockDim.getY(); y++) {
             srcY = blockYOffset + y;
             // Row overflow
-            if (srcY >= dims.getY()) {
+            if (srcY >= dims.getHeight()) {
                 if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
                     break;
                 }
@@ -242,14 +227,14 @@ public abstract class BasicLoader {
             for (int x = 0; x < blockDim.getX(); x++) {
                 srcX = blockXOffset + x;
                 // Column overflow.
-                if (srcX >= dims.getX()) {
+                if (srcX >= dims.getWidth()) {
                     if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
                         break;
                     }
                     srcX = wrapColumnIndex(srcX);
                 }
 
-                block[Block.index(x, y, blockDim.getX())] = planeData[Block.index(srcX, srcY, dims.getX())];
+                block[Block.index(x, y, blockDim.getX())] = planeData[Block.index(srcX, srcY, dims.getWidth())];
             }
         }
     }
@@ -259,7 +244,7 @@ public abstract class BasicLoader {
         for (int z = 0; z < voxelDim.getZ(); z++) {
             srcZ = voxelZOffset + z;
 
-            if (srcZ >= dims.getZ()) {
+            if (srcZ >= dims.getPlaneCount()) {
                 // Handle plane overflow.
                 if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
                     break;
@@ -270,7 +255,7 @@ public abstract class BasicLoader {
             for (int y = 0; y < voxelDim.getY(); y++) {
                 srcY = voxelYOffset + y;
 
-                if (srcY >= dims.getY()) {
+                if (srcY >= dims.getHeight()) {
                     // Handle row overflow.
                     if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
                         break;
@@ -281,7 +266,7 @@ public abstract class BasicLoader {
                 for (int x = 0; x < voxelDim.getX(); x++) {
                     srcX = voxelXOffset + x;
 
-                    if (srcX >= dims.getX()) {
+                    if (srcX >= dims.getWidth()) {
                         // Handle column overflow.
                         if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
                             break;
@@ -289,7 +274,7 @@ public abstract class BasicLoader {
                         srcX = wrapColumnIndex(srcX);
                     }
 
-                    voxel[Voxel.dataIndex(x, y, z, voxelDim)] = valueAt(srcZ, srcX, srcY, dims.getX());
+                    voxel[Voxel.dataIndex(x, y, z, voxelDim)] = valueAt(srcZ, srcX, srcY, dims.getWidth());
                 }
             }
         }
@@ -306,7 +291,7 @@ public abstract class BasicLoader {
             for (int y = 0; y < voxelDim.getY(); y++) {
                 srcY = voxelYOffset + y;
 
-                if (srcY >= dims.getY()) {
+                if (srcY >= dims.getHeight()) {
                     // Handle row overflow.
                     if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
                         break;
@@ -317,7 +302,7 @@ public abstract class BasicLoader {
                 for (int x = 0; x < voxelDim.getX(); x++) {
                     srcX = voxelXOffset + x;
 
-                    if (srcX >= dims.getX()) {
+                    if (srcX >= dims.getWidth()) {
                         // Handle column overflow.
                         if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
                             break;
@@ -325,7 +310,7 @@ public abstract class BasicLoader {
                         srcX = wrapColumnIndex(srcX);
                     }
 
-                    voxel[Voxel.dataIndex(x, y, z, voxelDim)] = planesData[z][Block.index(srcX, srcY, dims.getX())];
+                    voxel[Voxel.dataIndex(x, y, z, voxelDim)] = planesData[z][Block.index(srcX, srcY, dims.getWidth())];
                 }
             }
         }
@@ -339,9 +324,9 @@ public abstract class BasicLoader {
      * @return Allocated array.
      */
     private int[][] allocateVoxelArray(final V3i voxelDim, final Range<Integer> planeRange) {
-        final int voxelElementCount = (int) voxelDim.multiplyTogether();
+        final int voxelElementCount = voxelDim.multiplyTogether();
         final int rangeSize = planeRange.getTo() - planeRange.getFrom();
-        final V3i srcVoxel = new V3i(dims.getX(), dims.getY(), rangeSize);
+        final V3i srcVoxel = new V3i(dims.getWidth(), dims.getHeight(), rangeSize);
         final int voxelCount = Voxel.calculateRequiredVoxelCount(srcVoxel, voxelDim);
         return new int[voxelCount][voxelElementCount];
     }
@@ -359,8 +344,8 @@ public abstract class BasicLoader {
         int voxelIndex = 0;
 
         for (int voxelZOffset = planeRange.getFrom(); voxelZOffset < planeRange.getTo(); voxelZOffset += voxelDim.getZ()) {
-            for (int voxelYOffset = 0; voxelYOffset < dims.getY(); voxelYOffset += voxelDim.getY()) {
-                for (int voxelXOffset = 0; voxelXOffset < dims.getX(); voxelXOffset += voxelDim.getX()) {
+            for (int voxelYOffset = 0; voxelYOffset < dims.getHeight(); voxelYOffset += voxelDim.getY()) {
+                for (int voxelXOffset = 0; voxelXOffset < dims.getWidth(); voxelXOffset += voxelDim.getX()) {
                     loadVoxel(voxels[voxelIndex++], voxelXOffset, voxelYOffset, voxelZOffset, voxelDim);
                 }
             }
@@ -370,16 +355,16 @@ public abstract class BasicLoader {
 
     private void preloadPlanesData(final int[][] planesData, final int planeOffset, final int count) throws IOException {
         for (int i = 0; i < count; i++) {
-            if (planeOffset + i < dims.getZ())
+            if (planeOffset + i < dims.getPlaneCount())
                 planesData[i] = loadPlaneData(planeOffset + i);
             else {
                 if (wrappingStrategy == DataWrappingStrategy.LeaveBlank) {
-                    planesData[i] = new int[dims.toV2i().multiplyTogether()];
+                    planesData[i] = new int[dims.getWidth() * dims.getHeight()];
                 } else if (wrappingStrategy == DataWrappingStrategy.ClampToEdge) {
-                    assert (i > 0) : "Overflow on the first plane of voxel???";
+                    // assert (i > 0) : "Overflow on the first plane of voxel???";
                     planesData[i] = planesData[i - 1];
                 } else if (wrappingStrategy == DataWrappingStrategy.MirroredRepeat) {
-                    final int srcZ = dims.getZ() - (((planeOffset + i) - dims.getZ()) + 1);
+                    final int srcZ = dims.getPlaneCount() - (((planeOffset + i) - dims.getPlaneCount()) + 1);
                     planesData[i] = loadPlaneData(srcZ);
                 }
             }
@@ -404,8 +389,8 @@ public abstract class BasicLoader {
 
             preloadPlanesData(planesData, voxelZOffset, voxelDim.getZ());
 
-            for (int voxelYOffset = 0; voxelYOffset < dims.getY(); voxelYOffset += voxelDim.getY()) {
-                for (int voxelXOffset = 0; voxelXOffset < dims.getX(); voxelXOffset += voxelDim.getX()) {
+            for (int voxelYOffset = 0; voxelYOffset < dims.getHeight(); voxelYOffset += voxelDim.getY()) {
+                for (int voxelXOffset = 0; voxelXOffset < dims.getWidth(); voxelXOffset += voxelDim.getX()) {
                     loadVoxel(voxels[voxelIndex++], planesData, voxelXOffset, voxelYOffset, voxelDim);
                 }
             }
diff --git a/src/main/java/cz/it4i/qcmp/io/loader/IPlaneLoader.java b/src/main/java/cz/it4i/qcmp/io/loader/IPlaneLoader.java
index a864ac62bfe7b8209ec06f69043634caf9363944..4db0090b33b9efa2c1e7028e8c1ba24e8f130685 100644
--- a/src/main/java/cz/it4i/qcmp/io/loader/IPlaneLoader.java
+++ b/src/main/java/cz/it4i/qcmp/io/loader/IPlaneLoader.java
@@ -2,6 +2,7 @@ package cz.it4i.qcmp.io.loader;
 
 import cz.it4i.qcmp.compression.CompressionOptions;
 import cz.it4i.qcmp.compression.exception.ImageCompressionException;
+import cz.it4i.qcmp.data.HyperStackDimensions;
 import cz.it4i.qcmp.data.Range;
 import cz.it4i.qcmp.data.V2i;
 import cz.it4i.qcmp.data.V3i;
@@ -41,7 +42,7 @@ public interface IPlaneLoader {
      *
      * @return Image of the loader image.
      */
-    V3i getImageDimensions();
+    HyperStackDimensions getDatasetDimensions();
 
     /**
      * Load specified plane data.
@@ -92,7 +93,7 @@ public interface IPlaneLoader {
      * @throws IOException When fails to load plane data.
      */
     default int[][] loadRowVectors(final int vectorSize) throws IOException {
-        return loadRowVectors(vectorSize, new Range<>(0, getImageDimensions().getZ()));
+        return loadRowVectors(vectorSize, new Range<>(0, getDatasetDimensions().getPlaneCount()));
     }
 
     /**
@@ -113,7 +114,7 @@ public interface IPlaneLoader {
      * @throws IOException When fails to load plane data.
      */
     default int[][] loadBlocks(final V2i blockDim) throws IOException {
-        return loadBlocks(blockDim, new Range<>(0, getImageDimensions().getZ()));
+        return loadBlocks(blockDim, new Range<>(0, getDatasetDimensions().getPlaneCount()));
     }
 
     /**
@@ -134,7 +135,7 @@ public interface IPlaneLoader {
      * @throws IOException when fails to load plane data.
      */
     default int[][] loadVoxels(final V3i voxelDim) throws IOException {
-        return loadVoxels(voxelDim, new Range<>(0, getImageDimensions().getZ()));
+        return loadVoxels(voxelDim, new Range<>(0, getDatasetDimensions().getPlaneCount()));
     }
 
     /**
diff --git a/src/main/java/cz/it4i/qcmp/io/loader/ImageJBufferLoader.java b/src/main/java/cz/it4i/qcmp/io/loader/ImageJBufferLoader.java
index a0b8e7eb21759e5c539fc6a686c0cba4c5ccd7e4..e1769dde1288446d4e6c251a089628eda36d490e 100644
--- a/src/main/java/cz/it4i/qcmp/io/loader/ImageJBufferLoader.java
+++ b/src/main/java/cz/it4i/qcmp/io/loader/ImageJBufferLoader.java
@@ -5,20 +5,17 @@ import cz.it4i.qcmp.data.Range;
 import cz.it4i.qcmp.data.V2i;
 import cz.it4i.qcmp.data.V3i;
 import cz.it4i.qcmp.io.BufferInputData;
-import cz.it4i.qcmp.io.InputData;
 import cz.it4i.qcmp.utilities.TypeConverter;
 
 import java.io.IOException;
 import java.util.Arrays;
 
-public final class ImageJBufferLoader extends BasicLoader implements IPlaneLoader {
+public final class ImageJBufferLoader extends GenericLoader implements IPlaneLoader {
     private final BufferInputData bufferInputData;
 
     public ImageJBufferLoader(final BufferInputData bufferDataInfo) {
         super(bufferDataInfo.getDimensions());
         this.bufferInputData = bufferDataInfo;
-        // FIXME: Support more pixel types.
-        assert (this.bufferInputData.getPixelType() == InputData.PixelType.Gray16);
     }
 
     @Override
@@ -37,9 +34,9 @@ public final class ImageJBufferLoader extends BasicLoader implements IPlaneLoade
         final short[] srcBuffer = (short[]) bufferInputData.getPixelBuffer(plane);
         return TypeConverter.shortArrayToIntArray(srcBuffer);
     }
-    
+
     @Override
-    protected int valueAt(int plane, int x, int y, int width) {
+    protected int valueAt(final int plane, final int x, final int y, final int width) {
         return TypeConverter.shortToInt(((short[]) bufferInputData.getPixelBuffer(plane))[Block.index(x, y, width)]);
     }
 
@@ -49,20 +46,15 @@ public final class ImageJBufferLoader extends BasicLoader implements IPlaneLoade
             return new int[0];
         } else if (planes.length == 1) {
             return loadPlaneData(planes[0]);
-        } else if (planes.length == bufferInputData.getDimensions().getZ()) { // Maybe?
+        } else if (planes.length == dims.getPlaneCount()) {
             return loadAllPlanesU16Data();
         }
-        final int planePixelCount =
-                bufferInputData.getDimensions().getX() * bufferInputData.getDimensions().getY();
-        final long totalValueCount = (long) planePixelCount * (long) planes.length;
-
-        if (totalValueCount > (long) Integer.MAX_VALUE) {
-            throw new IOException("Unable to load image data for planes, file size is too big.");
-        }
+        final int planePixelCount = dims.getNumberOfElementsInDimension(2);
+        final int totalValueCount = Math.multiplyExact(planePixelCount, planes.length);
 
         Arrays.sort(planes);
 
-        final int[] destBuffer = new int[(int) totalValueCount];
+        final int[] destBuffer = new int[totalValueCount];
         int destOffset = 0;
         for (final int planeIndex : planes) {
             final short[] srcBuffer = (short[]) bufferInputData.getPixelBuffer(planeIndex);
@@ -74,17 +66,12 @@ public final class ImageJBufferLoader extends BasicLoader implements IPlaneLoade
 
     @Override
     public int[] loadAllPlanesU16Data() throws IOException {
-        final V3i imageDims = bufferInputData.getDimensions();
-        final long totalValueCount = imageDims.multiplyTogether();
-        final int planePixelCount = imageDims.getX() * imageDims.getY();
-
-        if (totalValueCount > (long) Integer.MAX_VALUE) {
-            throw new IOException("Unable to load all image data, file size is too big.");
-        }
+        final int planePixelCount = dims.getNumberOfElementsInDimension(2);
+        final int totalValueCount = dims.getNumberOfElementsInDimension(3);
 
-        final int[] destBuffer = new int[(int) totalValueCount];
+        final int[] destBuffer = new int[totalValueCount];
         int destOffset = 0;
-        for (int planeIndex = 0; planeIndex < imageDims.getZ(); planeIndex++) {
+        for (int planeIndex = 0; planeIndex < dims.getPlaneCount(); planeIndex++) {
             final short[] srcBuffer = (short[]) bufferInputData.getPixelBuffer(planeIndex);
             copyShortArrayIntoBuffer(srcBuffer, destBuffer, destOffset, planePixelCount);
             destOffset += planePixelCount;
diff --git a/src/main/java/cz/it4i/qcmp/io/loader/RawDataLoader.java b/src/main/java/cz/it4i/qcmp/io/loader/RawDataLoader.java
index 55f0e8595f62b7b7f8211a204cc5703ae273ba66..8d4834c2d0ddcfa39ed32d1d696185e794bb39ba 100644
--- a/src/main/java/cz/it4i/qcmp/io/loader/RawDataLoader.java
+++ b/src/main/java/cz/it4i/qcmp/io/loader/RawDataLoader.java
@@ -12,7 +12,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.Arrays;
 
-public final class RawDataLoader extends BasicLoader implements IPlaneLoader {
+public final class RawDataLoader extends GenericLoader implements IPlaneLoader {
     private final FileInputData inputDataInfo;
 
     private interface StorePlaneDataCallback {
@@ -36,8 +36,8 @@ public final class RawDataLoader extends BasicLoader implements IPlaneLoader {
         final byte[] buffer;
 
         try (final FileInputStream fileStream = new FileInputStream(inputDataInfo.getFilePath())) {
-            final long planeSize = (long) dims.getX() * (long) dims.getY() * 2;
-            final long expectedFileSize = planeSize * dims.getZ();
+            final long planeSize = dims.getNumberOfElementsInDimension(2) * 2;
+            final long expectedFileSize = planeSize * dims.getPlaneCount();
             final long fileSize = fileStream.getChannel().size();
 
             if (expectedFileSize != fileSize) {
@@ -72,7 +72,7 @@ public final class RawDataLoader extends BasicLoader implements IPlaneLoader {
             storeCallback.store(0, loadPlaneData(planes[0]));
         }
 
-        final int planeValueCount = inputDataInfo.getDimensions().getX() * inputDataInfo.getDimensions().getY();
+        final int planeValueCount = dims.getNumberOfElementsInDimension(2);
         final int planeDataSize = 2 * planeValueCount;
 
         final byte[] planeBuffer = new byte[planeDataSize];
@@ -117,13 +117,10 @@ public final class RawDataLoader extends BasicLoader implements IPlaneLoader {
 
     @Override
     public int[] loadPlanesU16Data(final int[] planes) throws IOException {
+        final int planeValueCount = dims.getNumberOfElementsInDimension(2);
+        final int totalValueCount = dims.getNumberOfElementsInDimension(3);
 
-        final int planeValueCount = inputDataInfo.getDimensions().getX() * inputDataInfo.getDimensions().getY();
-        final long totalValueCount = (long) planeValueCount * planes.length;
-        if (totalValueCount > (long) Integer.MAX_VALUE) {
-            throw new IOException("Integer count is too big.");
-        }
-        final int[] data = new int[(int) totalValueCount];
+        final int[] data = new int[totalValueCount];
 
         loadPlanesU16DataImpl(planes, (index, planeData) -> {
             System.arraycopy(planeData, 0, data, (index * planeValueCount), planeValueCount);
@@ -134,19 +131,16 @@ public final class RawDataLoader extends BasicLoader implements IPlaneLoader {
 
     @Override
     public int[] loadAllPlanesU16Data() throws IOException {
-        final V3i imageDims = inputDataInfo.getDimensions();
-        final long dataSize = (long) imageDims.getX() * (long) imageDims.getY() * (long) imageDims.getZ();
+        final int dataElementCount = dims.getNumberOfElementsInDimension(3);
 
-        if (dataSize > (long) Integer.MAX_VALUE) {
-            throw new IOException("RawFile size is too big.");
-        }
 
-        final int[] values = new int[(int) dataSize];
+        final int[] values = new int[(int) dataElementCount];
 
+        // TODO(Moravec): dis.readUnsignedShort() should be replaced with .read()!
         try (final FileInputStream fileStream = new FileInputStream(inputDataInfo.getFilePath());
              final DataInputStream dis = new DataInputStream(new BufferedInputStream(fileStream, 8192))) {
 
-            for (int i = 0; i < (int) dataSize; i++) {
+            for (int i = 0; i < (int) dataElementCount; i++) {
                 values[i] = dis.readUnsignedShort();
             }
         }
@@ -155,15 +149,14 @@ public final class RawDataLoader extends BasicLoader implements IPlaneLoader {
     }
 
     public int[][] loadAllPlanesTo2DArray() throws IOException {
-        final V3i imageDims = inputDataInfo.getDimensions();
-        final int planePixelCount = imageDims.getX() * imageDims.getY();
+        final int planePixelCount = dims.getNumberOfElementsInDimension(2);
         final int planeDataSize = planePixelCount * 2;
-        final int[][] result = new int[imageDims.getZ()][];
+        final int[][] result = new int[dims.getPlaneCount()][];
 
         final byte[] planeBuffer = new byte[planeDataSize];
         try (final FileInputStream fileStream = new FileInputStream(inputDataInfo.getFilePath())) {
 
-            for (int plane = 0; plane < imageDims.getZ(); plane++) {
+            for (int plane = 0; plane < dims.getPlaneCount(); plane++) {
                 int toRead = planeDataSize;
                 while (toRead > 0) {
                     final int read = fileStream.read(planeBuffer, planeDataSize - toRead, toRead);
diff --git a/src/main/java/cz/it4i/qcmp/io/loader/SCIFIOLoader.java b/src/main/java/cz/it4i/qcmp/io/loader/SCIFIOLoader.java
index a253532c640cdd23692cbfd6104815280f951911..2d0e8db1e112d9ad93cb45f24ab56e85cfbe8f98 100644
--- a/src/main/java/cz/it4i/qcmp/io/loader/SCIFIOLoader.java
+++ b/src/main/java/cz/it4i/qcmp/io/loader/SCIFIOLoader.java
@@ -12,7 +12,7 @@ import io.scif.Reader;
 import java.io.IOException;
 import java.util.Arrays;
 
-public final class SCIFIOLoader extends BasicLoader implements IPlaneLoader {
+public final class SCIFIOLoader extends GenericLoader implements IPlaneLoader {
     private final FileInputData inputDataInfo;
     private final Reader reader;
 
@@ -55,7 +55,7 @@ public final class SCIFIOLoader extends BasicLoader implements IPlaneLoader {
             return loadPlaneData(planes[0]);
         }
 
-        final int planeValueCount = inputDataInfo.getDimensions().getX() * inputDataInfo.getDimensions().getY();
+        final int planeValueCount = dims.getNumberOfElementsInDimension(2);
         final long planeDataSize = 2 * (long) planeValueCount;
 
         final long totalValueCount = (long) planeValueCount * planes.length;
@@ -87,9 +87,8 @@ public final class SCIFIOLoader extends BasicLoader implements IPlaneLoader {
 
     @Override
     public int[] loadAllPlanesU16Data() throws IOException {
-        final V3i imageDims = inputDataInfo.getDimensions();
-        final long planePixelCount = (long) imageDims.getX() * (long) imageDims.getY();
-        final long dataSize = planePixelCount * (long) imageDims.getZ();
+        final long planePixelCount = dims.getNumberOfElementsInDimension(2);
+        final long dataSize = planePixelCount * (long) dims.getPlaneCount();
 
         if (dataSize > (long) Integer.MAX_VALUE) {
             throw new IOException("FileSize is too big.");
@@ -97,7 +96,7 @@ public final class SCIFIOLoader extends BasicLoader implements IPlaneLoader {
 
         final int[] values = new int[(int) dataSize];
         byte[] planeBytes;
-        for (int plane = 0; plane < imageDims.getZ(); plane++) {
+        for (int plane = 0; plane < dims.getPlaneCount(); plane++) {
             try {
                 planeBytes = reader.openPlane(0, plane).getBytes();
             } catch (final FormatException e) {