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 ******************************************************************************/
009
010package imagingbook.common.image.access;
011
012import ij.process.ColorProcessor;
013import ij.process.ImageProcessor;
014import imagingbook.common.image.GridIndexer2D;
015import imagingbook.common.image.OutOfBoundsStrategy;
016import imagingbook.common.image.PixelPack;
017import imagingbook.common.image.interpolation.InterpolationMethod;
018
019/**
020 * <p>
021 * An 'image accessor' is a wrapper around some {@link ImageProcessor} object to allow unified (read and write) access
022 * to its pixels values. This abstract class defines unified access functionality to all 4 types of images available in
023 * ImageJ: 8-bit, 16-bit, float, and color images. All pixel values are of type {@code float[]}, either containing a
024 * single element (for scalar-valued images) or three elements (for color images).
025 * </p>
026 * <p>
027 * A generic {@link ImageAccessor} is created, e.g., by {@link #create(ImageProcessor)}, which returns an instance of
028 * {@link ByteAccessor}, {@link ShortAccessor}, {@link FloatAccessor} or {@link RgbAccessor}. {@link ImageAccessor}
029 * itself can access any ImageJ image using the methods {@link #getPix(int, int)}, {@link #getPix(double, double)} for
030 * retrieving pixel values and {@link #setPix(int, int, float[])} to modify pixel values.
031 * </p>
032 * <p>
033 * In addition, the accessors for scalar-valued images ({@link ByteAccessor}, {@link ShortAccessor},
034 * {@link FloatAccessor}) provide the methods {@link ScalarAccessor#getVal(int, int)},
035 * {@link ScalarAccessor#getVal(double, double)} and {@link ScalarAccessor#setVal(int, int, float)} to read and write
036 * scalar-valued pixels passed as single {@code float} values. <br> The methods {@link #getPix(double, double)} and
037 * {@link ScalarAccessor#getVal(double, double)} perform interpolation at non-integer coordinates using the specified
038 * {@link InterpolationMethod}.
039 * </p>
040 * <p>
041 * A related concept for providing unified access to images is {@link PixelPack}, which copies all pixel data to
042 * internal {@code float} arrays. In contrast to {@link PixelPack}, {@link ImageAccessor} does not duplicate any data
043 * but reads and writes the original {@link ImageProcessor} pixel data directly.
044 * </p>
045 *
046 * @author WB
047 * @version 2020/12/27
048 * @see PixelPack
049 */
050public abstract class ImageAccessor {
051        
052        /** The default out-of-bounds strategy (see {@link OutOfBoundsStrategy}). */
053        public static final OutOfBoundsStrategy DefaultOutOfBoundsStrategy = OutOfBoundsStrategy.ZeroValues;
054        /** The default pixel interpolation method (see {@link InterpolationMethod}). */
055        public static final InterpolationMethod DefaultInterpolationMethod = InterpolationMethod.Bilinear;
056        
057        protected final float defaultValue = 0.0f;
058        
059        protected final ImageProcessor ip;
060        protected final int width;
061        protected final int height;
062        protected final GridIndexer2D indexer;          // implements the specified OutOfBoundsStrategy
063        protected final OutOfBoundsStrategy outOfBoundsStrategy;
064        protected final InterpolationMethod interpolationMethod;
065        
066        /**
067         * Creates a new {@code ImageAccessor} instance for the given image,
068         * using the default out-of-bounds strategy and interpolation method.
069         * The concrete type of the returned instance depends on the specified image.
070         * 
071         * @param ip the source image
072         * @return a new {@code ImageAccessor} instance
073         */
074        public static final ImageAccessor create(ImageProcessor ip) {
075                return create(ip, DefaultOutOfBoundsStrategy, DefaultInterpolationMethod);
076        }
077        
078        /**
079         * Creates a new {@code ImageAccessor} instance for the given image,
080         * using the specified out-of-bounds strategy and interpolation method.
081         * The concrete type of the returned instance depends on the specified image.
082         * 
083         * @param ip the source image
084         * @param obs the out-of-bounds strategy (use {@code null} for default settings)
085         * @param ipm the interpolation method (use {@code null} for default settings)
086         * @return a new {@code ImageAccessor} instance
087         */
088        public static ImageAccessor create(ImageProcessor ip,  OutOfBoundsStrategy obs, InterpolationMethod ipm) {
089                if (ip instanceof ColorProcessor) {
090                        return new RgbAccessor((ColorProcessor)ip, obs, ipm);
091                }
092                else {
093                        return ScalarAccessor.create(ip, obs, ipm);
094                }
095        }
096        
097        // constructor (used by all subtypes)
098        ImageAccessor(ImageProcessor ip, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
099                this.ip = ip;
100                this.width  = ip.getWidth();
101                this.height = ip.getHeight();
102                this.outOfBoundsStrategy = (obs != null) ? obs : DefaultOutOfBoundsStrategy;
103                this.interpolationMethod = (ipm != null) ? ipm : DefaultInterpolationMethod;
104                this.indexer = GridIndexer2D.create(width, height, this.outOfBoundsStrategy);
105        }
106        
107        /**
108         * Returns the width of the associated image.
109         * @return the image width.
110         */
111        public int getWidth() {
112                return this.width;
113        }
114        
115        /**
116         * Returns the height of the associated image.
117         * @return the image height.
118         */
119        public int getHeight() {
120                return this.height;
121        }
122        
123        /**
124         * Returns the source {@link ImageProcessor} associated with this
125         * {@link ImageAccessor}.
126         * 
127         * @return the image processor
128         */
129        public ImageProcessor getProcessor() {
130                return this.ip;
131        }
132        
133        /**
134         * Returns the depth (number of components) of this image
135         * accessor. 1 is returned if the image is scalar-valued.
136         * 
137         * @return the image depth.
138         */
139        public abstract int getDepth();
140        
141        /**
142         * Returns the {@link OutOfBoundsStrategy} specified for this
143         * {@link ImageAccessor}.
144         * 
145         * @return the out-of-bounds strategy
146         */
147        public OutOfBoundsStrategy getOutOfBoundsStrategy() {
148                return outOfBoundsStrategy;
149        }
150
151        /**
152         * Returns the {@link InterpolationMethod} specified for this
153         * {@link ImageAccessor}.
154         * 
155         * @return the interpolation method
156         */
157        public InterpolationMethod getInterpolationMethod() {
158                return interpolationMethod;
159        }
160        
161        /**
162         * Returns the pixel value for the specified floating-point 
163         * position as a {@code float[]} with either 1 element for scalar-valued images
164         * and or more elements (e.g., 3 for for RGB images).
165         * 
166         * @param u the x-coordinate
167         * @param v the y-coordinate
168         * @return the pixel value ({@code float[]})
169         */
170        public abstract float[] getPix(int u, int v);
171        
172        /**
173         * Returns the interpolated pixel value for the specified floating-point 
174         * position as a {@code float[]} with either 1 element for scalar-valued images
175         * and or more elements (e.g., 3 for for RGB images).
176         * Interpolation is used non-integer coordinates.
177         * 
178         * @param x the x-coordinate
179         * @param y the y-coordinate
180         * @return the interpolated pixel value ({@code float[]})
181         */
182        public abstract float[] getPix(double x, double y);             // returns interpolated pixel value at real position (x, y)
183        
184        /**
185         * Sets the pixel value at the specified integer position.
186         * The new value must be provided as {@code float[]} with 
187         * 1 element for scalar-valued images or 3 elements for RGB images.
188         * @param u the x-coordinate
189         * @param v the y-coordinate
190         * @param val the new pixel value ({@code float[]})
191         */
192        public abstract void setPix(int u, int v, float[] val);
193        
194        /**
195         * Returns the value of the pixel's k-th component at the
196         * specified position. If the associated image is scalar-valued,
197         * this is equivalent to component 0.
198         * See also {@link #getDepth()}.
199         * 
200         * @param u the x-coordinate
201         * @param v the y-coordinate
202         * @param k the component index
203         * @return the component value ({@code float})
204         */
205        public abstract float getVal(int u, int v, int k);
206        
207        
208        /**
209         * Returns the interpolated value of the pixel's k-th component at the
210         * specified position. If the associated image is scalar-valued,
211         * this is equivalent to component 0.
212         * See also {@link #getDepth()}.
213         * 
214         * @param x the x-coordinate
215         * @param y the y-coordinate
216         * @param k the component index
217         * @return the interpolated component value ({@code float[]})
218         */
219        public abstract float getVal(double x, double y, int k);
220        
221        /**
222         * Sets the value of the pixel's k-th component at the
223         * specified position. If the associated image is scalar-valued,
224         * this is equivalent to component 0.
225         * See also {@link #getDepth()}.
226         * 
227         * @param u the x-coordinate
228         * @param v the y-coordinate
229         * @param k the component index
230         * @param val the new component value
231         */
232        public abstract void setVal(int u, int v, int k, float val);
233        
234        /**
235         * Returns the {@link ImageAccessor} for the k-th component;
236         * the result is a sub-type of {@link ScalarAccessor}.
237         * In the case of a scalar-valued image, THIS object is returned.
238         * 
239         * @param k the component index
240         * @return the component accessor.
241         */
242        public abstract ScalarAccessor getComponentAccessor(int k);
243        
244        abstract void checkComponentIndex(int k);
245                
246}