All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.algart.maps.pyramids.io.api.sources.RotatingPlanePyramidSource Maven / Gradle / Ivy

Go to download

Open-source libraries, providing the base functions for SciChains product in area of computer vision.

There is a newer version: 4.4.9
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2017-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.algart.maps.pyramids.io.api.sources;

import net.algart.arrays.*;
import net.algart.arrays.Arrays;
import net.algart.math.IPoint;
import net.algart.math.IRectangularArea;
import net.algart.math.functions.Func;
import net.algart.math.functions.LinearOperator;
import net.algart.maps.pyramids.io.api.PlanePyramidSource;

import java.nio.channels.NotYetConnectedException;
import java.util.*;

public final class RotatingPlanePyramidSource implements PlanePyramidSource {
    private static final boolean DEBUG_MODE = false;
    private static final System.Logger LOG = System.getLogger(RotatingPlanePyramidSource.class.getName());

    public enum RotationMode {
        NONE(1, 0, 0, 0, false, 0),

        CLOCKWISE_90(0, 1, 0, 1, true, 90),

        CLOCKWISE_180(-1, 0, 1, 1, false, 180),

        CLOCKWISE_270(0, -1, 1, 0, true, 270);

        final int rotationInDegrees;
        final long cos; // long field necessary for precise calculation of the bounds of the rectangular area
        final long sin;
        final long bX;
        final long bY;
        final boolean switchWidthAndHeight;

        RotationMode(
                long cos,
                long sin,
                long bX,
                long bY,
                boolean switchWidthAndHeight,
                int rotationInDegrees) {
            assert bX == 0 || bX == 1 || bY == 0 || bY == 1;
            this.cos = cos;
            this.sin = sin;
            this.bX = bX;
            this.bY = bY;
            this.switchWidthAndHeight = switchWidthAndHeight;
            this.rotationInDegrees = rotationInDegrees;
        }

        public static RotationMode valueOf(int clockwiseRotationInDegrees) {
            return switch (clockwiseRotationInDegrees) {
                case 0 -> NONE;
                case 90 -> CLOCKWISE_90;
                case 180 -> CLOCKWISE_180;
                case 270 -> CLOCKWISE_270;
                default -> throw new IllegalArgumentException(RotationMode.class.getCanonicalName()
                        + " does not support rotation by " + clockwiseRotationInDegrees + " degree");
            };
        }

        public static boolean isAngleSupported(int clockwiseRotationInDegrees) {
            return switch (clockwiseRotationInDegrees) {
                case 0, 90, 180, 270 -> true;
                default -> false;
            };
        }

        public int rotationInDegrees() {
            return rotationInDegrees;
        }

        public RotationMode reverse() {
            return rotationInDegrees == 0 ? this : valueOf(360 - rotationInDegrees);
        }

        public LinearOperator operator(long width, long height) {
            return LinearOperator.getInstance(a(), b(width, height));
        }

        public boolean isSwitchWidthAndHeight() {
            return switchWidthAndHeight;
        }

        public long[] correctDimensions(long[] dimensionsToCorrect) {
            if (switchWidthAndHeight) {
                dimensionsToCorrect = dimensionsToCorrect.clone();
                long temp = dimensionsToCorrect[2];
                dimensionsToCorrect[2] = dimensionsToCorrect[1];
                dimensionsToCorrect[1] = temp;
            }
            return dimensionsToCorrect;
        }

        public int[] correctDimensions(int[] dimensionsToCorrect) {
            if (switchWidthAndHeight) {
                dimensionsToCorrect = dimensionsToCorrect.clone();
                int temp = dimensionsToCorrect[2];
                dimensionsToCorrect[2] = dimensionsToCorrect[1];
                dimensionsToCorrect[1] = temp;
            }
            return dimensionsToCorrect;
        }

        public long[] correctWidthAndHeight(long[] widthAndHeightToCorrect) {
            if (switchWidthAndHeight) {
                widthAndHeightToCorrect = widthAndHeightToCorrect.clone();
                long temp = widthAndHeightToCorrect[1];
                widthAndHeightToCorrect[1] = widthAndHeightToCorrect[0];
                widthAndHeightToCorrect[0] = temp;
            }
            return widthAndHeightToCorrect;
        }

        public int[] correctWidthAndHeight(int[] widthAndHeightToCorrect) {
            if (switchWidthAndHeight) {
                widthAndHeightToCorrect = widthAndHeightToCorrect.clone();
                int temp = widthAndHeightToCorrect[1];
                widthAndHeightToCorrect[1] = widthAndHeightToCorrect[0];
                widthAndHeightToCorrect[0] = temp;
            }
            return widthAndHeightToCorrect;
        }

        // Note that parentImageWidth/parentImageHeight are in ROTATED coordinate system (parent source),
        // if we consider from/to as arguments of readSubMatrix of the required virtual image (this source).
        public long[] correctFromAndTo(
                long parentImageWidth, long parentImageHeight, long fromX, long fromY, long toX, long toY) {
            // We recommend to understand the following algorithm in floating-point coordinates, it will be more clear
            long rotatedFromX = cos * fromX + sin * fromY + bX * parentImageWidth;
            long rotatedFromY = -sin * fromX + cos * fromY + bY * parentImageHeight;
            long rotatedSizeX = cos * (toX - fromX) + sin * (toY - fromY);
            long rotatedSizeY = -sin * (toX - fromX) + cos * (toY - fromY);
//            System.out.printf("Rotated = %d,%d; %dx%d%n", rotatedFromX, rotatedFromY, rotatedSizeX, rotatedSizeY);
            long newFromX = rotatedSizeX >= 0 ? rotatedFromX : rotatedFromX + rotatedSizeX;
            long newFromY = rotatedSizeY >= 0 ? rotatedFromY : rotatedFromY + rotatedSizeY;
            long newToX = newFromX + Math.abs(rotatedSizeX);
            long newToY = newFromY + Math.abs(rotatedSizeY);
//            System.out.printf("Result = %d,%d; %d,%d%n", newFromX, newFromY, newToX, newToY);
            return new long[]{newFromX, newFromY, newToX, newToY};
        }

        public IPoint correctPoint(
                long parentImageWidth, long parentImageHeight, IPoint point) {
            long rotatedX = cos * point.x() + sin * point.y() + bX * parentImageWidth;
            long rotatedY = -sin * point.x() + cos * point.y() + bY * parentImageHeight;
            return IPoint.valueOf(rotatedX, rotatedY);
        }

        public IRectangularArea correctRectangle(
                long parentImageWidth, long parentImageHeight, IRectangularArea rectangle) {
            long fromX = rectangle.min(0);
            long fromY = rectangle.min(1);
            long toX = rectangle.max(0) + 1;
            long toY = rectangle.max(1) + 1;
            long[] fromAndTo = correctFromAndTo(parentImageWidth, parentImageHeight, fromX, fromY, toX, toY);
            return IRectangularArea.valueOf(
                    IPoint.valueOf(fromAndTo[0], fromAndTo[1]),
                    IPoint.valueOf(fromAndTo[2] - 1, fromAndTo[3] - 1));
        }


        public Matrix asRotated(Matrix m) {
            final long[] newDimensions = correctDimensions(m.dimensions());
            if (Arrays.isNCopies(m.array())) {
                assert Arrays.longMul(newDimensions) == m.size();
                // - it is convenient that it is so, because allows simply to use the same array
                return Matrices.matrix(m.array(), newDimensions);
            }
            final LinearOperator operator = operator(m.dim(1), m.dim(2));
            final Func source = Matrices.asInterpolationFunc(m, Matrices.InterpolationMethod.STEP_FUNCTION, DEBUG_MODE);
            final Func rotated = operator.apply(source);
            return Matrices.asCoordFuncMatrix(rotated, m.type(PArray.class), newDimensions);
        }

        private double[] a() {
            return new double[]{
                    1.0, 0.0, 0.0, // coordinate #0 is 0,1,2 for R,G,B
                    0.0, cos, sin,
                    0.0, -sin, cos,
            };
        }

        private double[] b(long width, long height) {
            return new double[]{
                    0.0,
                    bX * (width - 1),
                    bY * (height - 1)
            };
        }
    }

    private final PlanePyramidSource parent;
    private final RotationMode rotationMode;

    private MemoryModel memoryModel = Arrays.SMM;

    private RotatingPlanePyramidSource(PlanePyramidSource parent, RotationMode rotationMode) {
        Objects.requireNonNull(parent, "Null parent");
        Objects.requireNonNull(rotationMode, "Null rotationMode");
        assert rotationMode != RotationMode.NONE;
        this.parent = parent;
        this.rotationMode = rotationMode;
    }

    public static PlanePyramidSource newInstance(PlanePyramidSource parent, RotationMode rotationMode) {
        Objects.requireNonNull(parent, "Null parent");
        if (rotationMode == RotationMode.NONE) {
            return parent;
        }
        return new RotatingPlanePyramidSource(parent, rotationMode);
    }

    public MemoryModel getMemoryModel() {
        return memoryModel;
    }

    public RotatingPlanePyramidSource setMemoryModel(MemoryModel memoryModel) {
        this.memoryModel = Objects.requireNonNull(memoryModel, "Null memoryModel");
        return this;
    }

    public int numberOfResolutions() {
        return parent.numberOfResolutions();
    }

    public int compression() {
        return parent.compression();
    }

    public int bandCount() {
        return parent.bandCount();
    }

    public boolean isResolutionLevelAvailable(int resolutionLevel) {
        return parent.isResolutionLevelAvailable(resolutionLevel);
    }

    public boolean[] getResolutionLevelsAvailability() {
        return parent.getResolutionLevelsAvailability();
    }

    public long[] dimensions(int resolutionLevel) {
        return rotationMode.correctDimensions(parent.dimensions(resolutionLevel));
    }

    public boolean isElementTypeSupported() {
        return parent.isElementTypeSupported();
    }

    public Class elementType() throws UnsupportedOperationException {
        return parent.elementType();
    }

    @Override
    public OptionalDouble pixelSizeInMicrons() {
        return parent.pixelSizeInMicrons();
    }

    @Override
    public OptionalDouble magnification() {
        return parent.magnification();
    }

    public List zeroLevelActualRectangles() {
        final List parentRectangles = parent.zeroLevelActualRectangles();
        if (parentRectangles == null) {
            return null;
        }
        final long[] rotatedDim = this.dimensions(0);
        final long rotatedWidth = rotatedDim[DIM_WIDTH];
        final long rotatedHeight = rotatedDim[DIM_HEIGHT];
        final RotationMode reverseRotation = rotationMode.reverse();
        // Note: here we have reverse task in comparison with readSubMatrix
        final List result = new ArrayList(parentRectangles.size());
        for (IRectangularArea parentRectangle : parentRectangles) {
            final IRectangularArea rotatedRectangle = reverseRotation.correctRectangle(
                    rotatedWidth, rotatedHeight, parentRectangle);
            LOG.log(System.Logger.Level.DEBUG, () -> String.format(Locale.US,
                    "Rotating zero-level actual rectangle %s by %d degree to %s inside %dx%d",
                    parentRectangle, rotationMode.rotationInDegrees, rotatedRectangle, rotatedWidth, rotatedHeight));
            result.add(rotatedRectangle);
        }
        return result;
    }

    @Override
    public List>> zeroLevelActualAreaBoundaries() {
        final List>> boundaries = parent.zeroLevelActualAreaBoundaries();
        if (boundaries == null) {
            return null;
        }
        final long[] rotatedDim = this.dimensions(0);
        final long rotatedWidth = rotatedDim[DIM_WIDTH];
        final long rotatedHeight = rotatedDim[DIM_HEIGHT];
        final RotationMode reverseRotation = rotationMode.reverse();
        final List>> result = new ArrayList>>();
        for (List> area : boundaries) {
            final List> rotatedArea = new ArrayList>();
            for (List boundary : area) {
                final List rotatedBoundary = new ArrayList();
                for (IPoint p : boundary) {
                    rotatedBoundary.add(reverseRotation.correctPoint(rotatedWidth, rotatedHeight, p));
                }
                rotatedArea.add(rotatedBoundary);
            }
            result.add(rotatedArea);
        }
        return result;
    }

    public Matrix readSubMatrix(int resolutionLevel, long fromX, long fromY, long toX, long toY) {
        long t1 = System.nanoTime();
        long[] parentDim = parent.dimensions(resolutionLevel);
        long[] fromAndTo = rotationMode.correctFromAndTo(
                parentDim[DIM_WIDTH], parentDim[DIM_HEIGHT], fromX, fromY, toX, toY);
        Matrix parentSubMatrix = parent.readSubMatrix(
                resolutionLevel, fromAndTo[0], fromAndTo[1], fromAndTo[2], fromAndTo[3]);
        long t2 = System.nanoTime();
        Matrix rotated = rotated(parentSubMatrix);
        long t3 = System.nanoTime();
        LOG.log(System.Logger.Level.DEBUG, () -> String.format(Locale.US,
                "%s completed reading %d..%d x %d..%d, level %d in %.3f ms " +
                        "(%.3f parent reading + %.3f rotation)",
                ((Object) this).getClass().getSimpleName(), fromX, toX, fromY, toY, resolutionLevel,
                (t3 - t1) * 1e-6, (t2 - t1) * 1e-6, (t3 - t2) * 1e-6));
        return rotated;
    }

    public boolean isFullMatrixSupported() {
        return parent.isFullMatrixSupported();
    }

    public Matrix readFullMatrix(int resolutionLevel)
            throws UnsupportedOperationException {
        return rotated(parent.readFullMatrix(resolutionLevel));
    }

    public boolean isSpecialMatrixSupported(SpecialImageKind kind) {
        return parent.isSpecialMatrixSupported(kind);
    }

    public Optional> readSpecialMatrix(SpecialImageKind kind)
            throws NotYetConnectedException {
        if (kind == SpecialImageKind.NONE) {
            return Optional.empty();
        }
        return parent.readSpecialMatrix(kind).map(this::rotated);
    }

    public boolean isDataReady() {
        return parent.isDataReady();
    }

    public Optional metadata() {
        return parent.metadata();
    }

    public void loadResources() {
        parent.loadResources();
    }

    public void freeResources(FlushMode flushMode) {
        parent.freeResources(flushMode);
    }

    @Override
    public String toString() {
        return "RotatingPlanePyramidSource-" + rotationMode.rotationInDegrees() + ", based on " + parent;
    }

    private Matrix rotated(Matrix m) {
        final long[] newDimensions = rotationMode.correctDimensions(m.dimensions());
        if (Arrays.isNCopies(m.array())) {
            assert Arrays.longMul(newDimensions) == m.size();
            // - it is convenient, because allows simply to use the same array
            return Matrices.matrix(m.array(), newDimensions);
        }
        final Matrix lazy = rotationMode.asRotated(m);
        final Matrix actual = memoryModel.newMatrix(
                Arrays.SystemSettings.maxTempJavaMemory(),
                UpdatablePArray.class,
                lazy.elementType(),
                lazy.dimensions());
        Matrices.copy(null, actual, lazy, 0, false);
        return actual;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy