View Javadoc
1   package com.github.celldynamics.quimp.plugin.utils;
2   
3   import java.util.Collections;
4   import java.util.Vector;
5   
6   import org.scijava.vecmath.Matrix3d;
7   import org.scijava.vecmath.Point3d;
8   import org.slf4j.Logger;
9   import org.slf4j.LoggerFactory;
10  
11  /**
12   * Represents rectangle bounding box.
13   * 
14   * <p>Bounding box is defined by four corners (in contrary to javafx.geometry.BoundingBox) that can
15   * be
16   * rotated by any angle.
17   * 
18   * @author p.baniukiewicz
19   */
20  class RectangleBox {
21  
22    static final Logger LOGGER = LoggerFactory.getLogger(RectangleBox.class.getName());
23  
24    private Vector<Double> px; // stores px coordinates of bounding box in clockwise order
25    private Vector<Double> py; // stores py coordinates of bounding box in clockwise order
26  
27    /**
28     * Creates bounding box object from px py vectors.
29     * 
30     * <p>Vectors define corners in clockwise direction. Vectors are referenced only, not copied. They
31     * are modified during rotation.
32     * 
33     * @param x coordinates of bounding box in clockwise order
34     * @param y coordinates of bounding box in clockwise order
35     * @throws IllegalArgumentException When empty vectors are passed to constructor or input
36     *         vectors have different length
37     */
38    @SuppressWarnings("unchecked")
39    public RectangleBox(Vector<Double> x, Vector<Double> y) throws IllegalArgumentException {
40      this.px = (Vector<Double>) x.clone();
41      this.py = (Vector<Double>) y.clone();
42      // get average of px and py
43      if (x.isEmpty() || y.isEmpty()) {
44        throw new IllegalArgumentException("Input vectors are empty");
45      }
46      if (x.size() != y.size()) {
47        throw new IllegalArgumentException("Input vectors are not equal");
48      }
49      double centerX = getAverage(x); // centre of mass
50      double centerY = getAverage(y); // centre of mass
51      // move input points to (0,0)
52      for (int i = 0; i < x.size(); i++) {
53        this.px.set(i, x.get(i) - centerX);
54        this.py.set(i, y.get(i) - centerY);
55      }
56    }
57  
58    /**
59     * Specifies bounding box centred at (0,0).
60     * 
61     * @param width Width of bounding box
62     * @param height Height of bounding box
63     */
64    public RectangleBox(double width, double height) {
65      px = new Vector<Double>();
66      py = new Vector<Double>();
67  
68      // generate artificial rectangle centered at (0,0)
69      px.add(-width / 2); // left top
70      px.add(width / 2); // right top
71      px.add(width / 2); // right down
72      px.add(-width / 2); // left down
73  
74      py.add(height / 2); // left top
75      py.add(height / 2); // right top
76      py.add(-height / 2); // right down
77      py.add(-height / 2); // left down
78    }
79  
80    /**
81     * Rotates bounding box.
82     * 
83     * @param angle Rotation angle
84     */
85    public void rotateBoundingBox(double angle) {
86  
87      // assume that image is centered at (0,0)
88      // convert to rad
89      double angleRad = angle * Math.PI / 180.0;
90  
91      // rotation matrix
92      Matrix3d rot = new Matrix3d();
93      // rotation with - because shear is defined in anti-clockwise and rotZ
94      // require counterclockwise (the same)
95      rot.rotZ(-angleRad); // generate rotation matrix of angle - bring input image to horizontal
96      // position
97  
98      // define corner points of image
99      Point3d[] cornerTable = new Point3d[4];
100     cornerTable[0] = new Point3d(px.get(0), py.get(0), 0); // left up
101     cornerTable[1] = new Point3d(px.get(1), py.get(1), 0); // right up
102     cornerTable[2] = new Point3d(px.get(2), py.get(2), 0); // right down
103     cornerTable[3] = new Point3d(px.get(3), py.get(3), 0); // right up
104 
105     int i = 0;
106     // rotate virtual image by angle
107     for (Point3d p : cornerTable) {
108       rot.transform(p); // multiply ROT*P and return result to P
109       px.set(i, p.x);
110       py.set(i, p.y);
111       i++;
112     }
113   }
114 
115   /**
116    * Gets width of bounding box as distance over \b px between outermost corners.
117    * 
118    * @return Width of bounding box
119    */
120   public double getWidth() {
121     return Math.abs(Collections.max(px) - Collections.min(px));
122   }
123 
124   /**
125    * Gets height of bounding box as distance over \b py between outermost corners.
126    * 
127    * @return Height of bounding box
128    */
129   public double getHeight() {
130     return Math.abs(Collections.max(py) - Collections.min(py));
131   }
132 
133   /**
134    * Gets mean value of input vector.
135    * 
136    * @param x Vector of to calculate mean
137    * @return Mean value of x
138    */
139   private double getAverage(Vector<Double> x) {
140     double sum = 0;
141     for (Double val : x) {
142       sum += val;
143     }
144     return sum / x.size();
145   }
146 }