001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 * image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause).
006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved.
007 * Visit https://imagingbook.com for additional details.
008 ******************************************************************************/
009package imagingbook.common.image.matching.lucaskanade;
010
011import ij.process.ImageProcessor;
012import imagingbook.common.geometry.basic.Pnt2d;
013import imagingbook.common.geometry.basic.Pnt2d.PntInt;
014import imagingbook.common.geometry.mappings.linear.AffineMapping2D;
015import imagingbook.common.geometry.mappings.linear.LinearMapping2D;
016import imagingbook.common.geometry.mappings.linear.ProjectiveMapping2D;
017import imagingbook.common.image.access.ImageAccessor;
018
019/**
020 * Used to extract warped images for testing class {@link LucasKanadeMatcher}.
021 * 
022 * @author WB
023 * @version 2022/09/16
024 */
025public class ImageExtractor {
026        // TODO: replace by ImageMapper
027        
028        private int interpolationMethod = ImageProcessor.BILINEAR;
029        private final ImageProcessor I;
030        
031        /**
032         * Creates a new instance of {@link ImageExtractor} for the image {@code I}.
033         * @param I the target image.
034         */
035        public ImageExtractor(ImageProcessor I) {
036                this.I = I;
037        }
038
039        /**
040         * Sets the interpolation method to be used for extracting sub-images (defined by ImageJ's {@link ImageProcessor}):
041         * {@code ImageProcessor.BILINEAR} (default), {@code NEAREST_NEIGHBOR}, {@code BICUBIC} or {@code NONE}.
042         *
043         * @param method the interpolation method to use
044         */
045        public void setInterpolationMethod(int method) {
046                this.interpolationMethod = method;
047        }
048
049        /**
050         * Extracts a sub-image of size {@code width} x {@code height} from the source image {@code I} (referenced by
051         * {@code this} {@link ImageExtractor}), using the specified transformation. The image {@code R} is extracted from a
052         * quadrilateral patch of the source image, defined by the transformation of the boundary of {@code R} by
053         * {@code T(x)}.
054         *
055         * @param width the width of the extracted image
056         * @param height the height of the extracted image
057         * @param T a {@link LinearMapping2D} instance
058         * @return the extracted image, which is of the same type as the source image.
059         */
060        public ImageProcessor extractImage(int width, int height, LinearMapping2D T) {
061                ImageProcessor R = I.createProcessor(width, height);
062                extractImage(R, T);
063                return R;
064        }
065        
066//      public ImageProcessor extractImage(int width, int height, Pnt2d[] sourcePnts) {
067//              ImageProcessor R = I.createProcessor(width, height);
068//              ProjectiveMapping2D T = getMapping(width, height, sourcePnts);
069//              extractImage(R, T);
070//              return R;
071//      }
072
073        /**
074         * Fills the image {@code R} from the source image {@code I} (referenced by {@code this} object). The image
075         * {@code R} is extracted from a quadrilateral patch of the source image, defined by the transformation of the
076         * boundary of {@code R} by {@code T(x)}. Grayscale and color images my not be mixed (i.e., {@code R} must be of the
077         * same type as {@code I}).
078         *
079         * @param R the image to be filled.
080         * @param T a {@link LinearMapping2D} object.
081         */
082        public void extractImage(ImageProcessor R, LinearMapping2D T) {
083                int prevInterpolationMethod = I.getInterpolationMethod();
084                // save current interpolation method
085                I.setInterpolationMethod(interpolationMethod);
086                
087                ImageAccessor iaI = ImageAccessor.create(I);
088                ImageAccessor iaR = ImageAccessor.create(R);
089        
090                int wT = R.getWidth();
091                int hT = R.getHeight();
092                for (int u = 0; u < wT; u++) {
093                        for (int v = 0; v < hT; v++) {
094                                Pnt2d uv = PntInt.from(u, v);
095                                Pnt2d xy = T.applyTo(uv);
096                                float[] val = iaI.getPix(xy.getX(), xy.getY());
097                                iaR.setPix(u, v, val);
098                        }
099                }
100                // restore interpolation method
101                I.setInterpolationMethod(prevInterpolationMethod);
102        }
103
104        /**
105         * Extracts a warped sub-image of the associated target image I, defined by a sequence of 3 or 4 points. In the case
106         * of 3 points in sourcePnts, an {@link AffineMapping2D} is used; with 4 points, a {@link ProjectiveMapping2D} is
107         * used. The 3 or 4 points map clockwise to the corner points of the target image R, starting with the top-left
108         * corner.
109         *
110         * @param R the target image
111         * @param sourcePnts an array of 3 or 4 {@link Pnt2d} objects
112         */
113        public void extractImage(ImageProcessor R, Pnt2d[] sourcePnts) {
114                ProjectiveMapping2D T = getMapping(R.getWidth(), R.getHeight(), sourcePnts);
115                extractImage(R, T);
116        }
117        
118        private ProjectiveMapping2D getMapping(int w, int h, Pnt2d[] sourcePnts) {
119                Pnt2d[] targetPnts = {
120                                PntInt.from(0, 0), PntInt.from(w - 1, 0),
121                                PntInt.from(w - 1, h - 1), PntInt.from(0, h - 1)
122                        };
123                ProjectiveMapping2D T = null;
124                switch (sourcePnts.length) {
125                case (3) : T = AffineMapping2D.fromPoints(targetPnts, sourcePnts); break;
126                case (4) : T = ProjectiveMapping2D.fromPoints(targetPnts, sourcePnts); break;
127                default : throw new IllegalArgumentException("wrong number of source points (3 or 4 required)");
128                }
129                return T;
130        }
131        
132}