View Javadoc
1   package com.github.celldynamics.quimp.plugin.utils;
2   
3   import org.slf4j.Logger;
4   import org.slf4j.LoggerFactory;
5   
6   import ij.process.ImageProcessor;
7   
8   /**
9    * Class implementing extra functionalities for ij.ImageProcessor
10   *
11   * <p>Check {@link #extendImageBeforeRotation(ImageProcessor, double)} for possible problems
12   * 
13   * @author p.baniukiewicz
14   */
15  public class ImageProcessorPlus {
16  
17    /**
18     * The Constant LOGGER.
19     */
20    static final Logger LOGGER = LoggerFactory.getLogger(ImageProcessorPlus.class.getName());
21  
22    /**
23     * Main constructor.
24     */
25    public ImageProcessorPlus() {
26    }
27  
28    /**
29     * Add borders around image to prevent cropping during rotating.
30     * 
31     * <p><b>Warning</b>
32     * 
33     * <p>Replaces original image and may not preserve all its attributes
34     * 
35     * @param ip ImageProcessor to be extended
36     * @param angle Angle to be image rotated
37     * @return copy of ip extended to size that allows to rotate it by angle without clipping
38     */
39    public ImageProcessor extendImageBeforeRotation(ImageProcessor ip, double angle) {
40      ImageProcessor ret;
41      int width = ip.getWidth();
42      int height = ip.getHeight();
43      // get bounding box after rotation
44      RectangleBoxquimp/plugin/utils/RectangleBox.html#RectangleBox">RectangleBox rb = new RectangleBox(width, height);
45      rb.rotateBoundingBox(angle);
46      int newWidth = (int) Math.round(rb.getWidth());
47      int newHeight = (int) Math.round(rb.getHeight());
48      if (newWidth < width) {
49        newWidth = width;
50      }
51      if (newHeight < height) {
52        newHeight = height;
53      }
54      // create new array resized
55      ret = ip.createProcessor(newWidth, newHeight);
56      // get current background - borders will have the same value
57      ret.setValue(ip.getBackgroundValue()); // set current fill value for extended image
58      ret.setBackgroundValue(ip.getBackgroundValue()); // set the same background as in original image
59      ret.fill(); // change color of extended image
60      ret.setInterpolationMethod(ip.getInterpolationMethod());
61      // insert original image into extended
62      ret.insert(ip, (newWidth - ip.getWidth()) / 2, (newHeight - ip.getHeight()) / 2);
63      ret.resetRoi();
64      return ret; // assign extended into current
65    }
66  
67    /**
68     * Rotate image by specified angle keeping correct rotation direction.
69     * 
70     * @param ip ImageProcessor to be rotated
71     * @param angle Angle of rotation in anti-clockwise direction
72     * @param addBorders if true rotates with extension, false use standard rotation with clipping
73     * @return rotated ip that is a copy of ip whenaddBorders is true or reference when addBorders
74     *         is false
75     */
76    public ImageProcessor rotate(ImageProcessor ip, double angle, boolean addBorders) {
77      ImageProcessor ret;
78      if (addBorders) {
79        ret = extendImageBeforeRotation(ip, angle);
80      } else {
81        ret = ip;
82      }
83      ret.rotate(angle);
84      return ret;
85    }
86  
87    /**
88     * Crop image.
89     * 
90     * <p><b>Warning</b>
91     * 
92     * <p>Modifies current object
93     * 
94     * @param ip ImageProcessor to be cropped
95     * @param luX Left upper corner x coordinate
96     * @param luY Left upper corner y coordinate
97     * @param width Width of clipped area
98     * @param height Height of clipped area
99     * @return Clipped image
100    */
101   public ImageProcessor crop(ImageProcessor ip, int luX, int luY, int width, int height) {
102     ip.setRoi(luX, luY, width, height);
103     ip = ip.crop();
104     ip.resetRoi();
105     return ip;
106   }
107 
108   /**
109    * Crop image.
110    * 
111    * <p><b>Warning</b>
112    * 
113    * <p>Modifies current object Designed to use with cooperation with
114    * extendImageBeforeRotation(ImageProcessor,double).
115    * 
116    * <p>Assumes that cropping area is centered in source image
117    * 
118    * @param ip ImageProcessor to be cropped
119    * @param width Width of clipped area
120    * @param height Height of clipped area
121    * @return Clipped image
122    */
123   public ImageProcessor cropImageAfterRotation(ImageProcessor ip, int width, int height) {
124     int sw = (ip.getWidth() - width) / 2;
125     int sh = (ip.getHeight() - height) / 2;
126     if (sw < 0) {
127       sw = 0;
128     }
129     if (sh < 0) {
130       sh = 0;
131     }
132     ip.setRoi(sw, sh, width, height);
133     ip = ip.crop();
134     ip.resetRoi();
135     return ip;
136   }
137 
138   /**
139    * Perform running mean on image using convolution.
140    * 
141    * @param ip ImageProcessor
142    * @param prefilterangle angle as k*45
143    * @param masksize odd size, 0 will skip processing
144    * @see ImageProcessorPlus.GenerateKernel
145    */
146   public void runningMean(ImageProcessor ip, String prefilterangle, int masksize) {
147     LOGGER.debug("Convolving: " + prefilterangle + " " + masksize);
148     if (masksize == 0) {
149       return;
150     }
151     float[] kernel = new GenerateKernel(masksize).generateKernel(prefilterangle);
152     ip.convolve(kernel, masksize, masksize);
153   }
154 
155   /**
156    * Support generating kernels for running mean.
157    * 
158    * @author p.baniukiewicz
159    *
160    */
161   class GenerateKernel {
162     private int size;
163 
164     /**
165      * 
166      * @param size Size of the kernel assuming its rectangularity.
167      */
168     public GenerateKernel(int size) {
169       this.size = size;
170     }
171 
172     /**
173      * Generate convolution kernel.
174      * 
175      * <p>Returned kernel is compatible with ij.process.ImageProcessor.convolve(float[], int, int)
176      * 
177      * @param option Option can be 0, 45, 90, 135 as string.
178      * @return 1D array as row ordered matrix. The kernel contains 1 on diagonal and it is
179      *         normalised.
180      */
181     public float[] generateKernel(String option) {
182       float[] ret = new float[size * size];
183       int mid = size / 2; // middle element (0 indexed)
184       switch (option) {
185         case "0": // row in middle
186           for (int i = 0; i < size; i++) {
187             ret[sub2lin(mid, i)] = 1.0f;
188           }
189           break;
190         case "135":
191           for (int i = 0; i < size; i++) {
192             ret[sub2lin(i, i)] = 1.0f;
193           }
194           break;
195         case "90":
196           for (int i = 0; i < size; i++) {
197             ret[sub2lin(i, mid)] = 1.0f;
198           }
199           break;
200         case "45":
201           for (int i = 0; i < size; i++) {
202             ret[sub2lin(i, size - 1 - i)] = 1.0f;
203           }
204           break;
205         default:
206           throw new UnsupportedOperationException("Unsupported mask angle.");
207       }
208 
209       return normalise(ret);
210     }
211 
212     /**
213      * Convert subscript indexes to linear.
214      * 
215      * @param row row counted from 0
216      * @param col column counted from 0
217      * @return Linear index based on row and col position
218      */
219     private int sub2lin(int row, int col) {
220       return row * size + col;
221     }
222 
223     /**
224      * Normalise the kernel.
225      * 
226      * <p>Divide every element by sum of elements.
227      * 
228      * @param kernel kernel to normalise
229      * @return normalised kernel (copy)
230      */
231     private float[] normalise(float[] kernel) {
232       float s = 0;
233       for (int i = 0; i < kernel.length; i++) {
234         s += kernel[i];
235       }
236       float[] ret = new float[kernel.length];
237       for (int i = 0; i < ret.length; i++) {
238         ret[i] = kernel[i] / s;
239       }
240       return ret;
241     }
242   }
243 }