1 /* Copyright (c) 2002-2008 The University of the West Indies
2  *
3  * Contact: robert.lancashire@uwimona.edu.jm
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2.1 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package jspecview.common;
21 
22 import java.util.Arrays;
23 import java.util.Comparator;
24 
25 import java.util.StringTokenizer;
26 
27 import javajs.util.Lst;
28 
29 
30 
31 
32 
33 /**
34  * The <code>Coordinate</code> class stores the x and y values of a coordinate.
35  *
36  * @author Debbie-Ann Facey
37  * @author Khari A. Bryan
38  * @author Prof Robert J. Lancashire
39  */
40 public class Coordinate {
41   /** the x value */
42   private double xVal = 0;
43   /** the y value */
44   private double yVal = 0;
45 
46   /**
47    * Constructor
48    */
Coordinate()49   public Coordinate() {
50   }
51 
52   /**
53    * Constructor
54    *
55    * @param x
56    *        the x value
57    * @param y
58    *        the y value
59    * @return this
60    */
set(double x, double y)61   public Coordinate set(double x, double y) {
62     xVal = x;
63     yVal = y;
64     return this;
65   }
66 
67   /**
68    * Returns the x value of the coordinate
69    *
70    * @return the x value of the coordinate
71    */
getXVal()72   public double getXVal() {
73     return xVal;
74   }
75 
76   /**
77    * Returns the y value of the coordinate
78    *
79    * @return the y value of the coordinate
80    */
getYVal()81   public double getYVal() {
82     return yVal;
83   }
84 
85   /**
86    * Sets the x value of the coordinate
87    *
88    * @param val
89    *        the x value
90    */
setXVal(double val)91   public void setXVal(double val) {
92     xVal = val;
93   }
94 
95   /**
96    * Sets the y value of the coordinate
97    *
98    * @param val
99    *        the y value
100    */
setYVal(double val)101   public void setYVal(double val) {
102     yVal = val;
103   }
104 
105   /**
106    * Returns a new coordinate that has the same x and y values of this
107    * coordinate
108    *
109    * @return Returns a new coordinate that has the same x and y values of this
110    *         coordinate
111    */
copy()112   public Coordinate copy() {
113     return new Coordinate().set(xVal, yVal);
114   }
115 
116   /**
117    * Indicates whether some other Coordinate is equal to this one
118    *
119    * @param coord
120    *        the reference coordinate
121    * @return true if the coordinates are equal, false otherwise
122    */
equals(Coordinate coord)123   public boolean equals(Coordinate coord) {
124     return (coord.xVal == xVal && coord.yVal == yVal);
125   }
126 
127   /**
128    * Overides Objects toString() method
129    *
130    * @return the String representation of this coordinate
131    */
132   @Override
toString()133   public String toString() {
134     return "[" + xVal + ", " + yVal + "]";
135   }
136 
137   /**
138    * Determines if the y values of a spectrum are in a certain range
139    *
140    * @param xyCoords
141    * @param min
142    * @param max
143   * @return true is in range, otherwise false
144    */
isYInRange(Coordinate[] xyCoords, double min, double max)145   public static boolean isYInRange(Coordinate[] xyCoords, double min,
146                                     double max) {
147     return (getMinY(xyCoords, 0, xyCoords.length - 1) >= min
148         && getMaxY(xyCoords, 0, xyCoords.length - 1) >= max);
149   }
150 
151   /**
152    * Normalises the y values of a spectrum to a certain range
153    *
154    * @param xyCoords
155    * @param min
156    * @param max
157    * @return array of normalised coordinates
158    */
normalise(Coordinate[] xyCoords, double min, double max)159   public static Coordinate[] normalise(Coordinate[] xyCoords, double min,
160                                         double max) {
161     Coordinate[] newXYCoords = new Coordinate[xyCoords.length];
162     double minY = getMinY(xyCoords, 0, xyCoords.length - 1);
163     double maxY = getMaxY(xyCoords, 0, xyCoords.length - 1);
164     double factor = (maxY - minY) / (max - min); // range = 0-5
165     for (int i = 0; i < xyCoords.length; i++)
166       newXYCoords[i] = new Coordinate().set(xyCoords[i].getXVal(),
167           ((xyCoords[i].getYVal() - minY) / factor) - min);
168     return newXYCoords;
169   }
170 
reverse(Coordinate[] x)171   public static Coordinate[] reverse(Coordinate[] x) {
172     int n = x.length;
173     for (int i = 0; i < n; i++) {
174       Coordinate v = x[i];
175       x[i] = x[--n];
176       x[n] = v;
177     }
178     return x;
179   }
180 
181   /**
182    * Parses data stored in x, y format
183    *
184    * @param dataPoints
185    *        the data as string
186    * @param xFactor
187    *        the factor to apply to x values
188    * @param yFactor
189    *        the factor to apply to y values
190    * @return an array of <code>Coordinate</code>s
191    */
parseDSV(String dataPoints, double xFactor, double yFactor)192   public static Coordinate[] parseDSV(String dataPoints, double xFactor,
193                                       double yFactor) {
194 
195     //int linenumber = 0;
196     Coordinate point;
197     double xval = 0;
198     double yval = 0;
199     Lst<Coordinate> xyCoords = new Lst<Coordinate>();
200 
201     String delim = " \t\n\r\f,;";
202     StringTokenizer st = new StringTokenizer(dataPoints, delim);
203     String tmp1, tmp2;
204 
205     while (st.hasMoreTokens()) {
206       tmp1 = st.nextToken().trim();
207       tmp2 = st.nextToken().trim();
208 
209       xval = Double.parseDouble(tmp1);
210       yval = Double.parseDouble(tmp2);
211       point = new Coordinate().set(xval * xFactor, yval * yFactor);
212       xyCoords.addLast(point);
213     }
214 
215     Coordinate[] coord = new Coordinate[xyCoords.size()];
216     return xyCoords.toArray(coord);
217   }
218 
219   /**
220    * Returns the Delta X value
221    *
222    * @param last
223    *        the last x value
224    * @param first
225    *        the first x value
226    * @param numPoints
227    *        the number of data points
228    * @return the Delta X value
229    */
deltaX(double last, double first, int numPoints)230   public static double deltaX(double last, double first, int numPoints) {
231     return (last - first) / (numPoints - 1);
232   }
233 
234   /**
235    * Removes the scale factor from the coordinates
236    *
237    * @param xyCoords
238    *        the array of coordinates
239    * @param xScale
240    *        the scale for the x values
241    * @param yScale
242    *        the scale for the y values
243    */
removeScale(Coordinate[] xyCoords, double xScale, double yScale)244   public static void removeScale(Coordinate[] xyCoords, double xScale,
245                                  double yScale) {
246     applyScale(xyCoords, (1 / xScale), (1 / yScale));
247   }
248 
249   /**
250    * Apply the scale factor to the coordinates
251    *
252    * @param xyCoords
253    *        the array of coordinates
254    * @param xScale
255    *        the scale for the x values
256    * @param yScale
257    *        the scale for the y values
258    */
applyScale(Coordinate[] xyCoords, double xScale, double yScale)259   public static void applyScale(Coordinate[] xyCoords, double xScale,
260                                 double yScale) {
261     if (xScale != 1 || yScale != 1) {
262       for (int i = 0; i < xyCoords.length; i++) {
263         xyCoords[i].setXVal(xyCoords[i].getXVal() * xScale);
264         xyCoords[i].setYVal(xyCoords[i].getYVal() * yScale);
265       }
266     }
267   }
268 
269   /**
270    * Returns the minimum x value of an array of <code>Coordinate</code>s
271    *
272    * @param coords
273    *        the coordinates
274    * @param start
275    *        the starting index
276    * @param end
277    *        the ending index
278    * @return the maximum x value of an array of <code>Coordinate</code>s
279    */
getMinX(Coordinate[] coords, int start, int end)280   public static double getMinX(Coordinate[] coords, int start, int end) {
281     double min = Double.MAX_VALUE;
282     for (int index = start; index <= end; index++) {
283       double tmp = coords[index].getXVal();
284       if (tmp < min)
285       min = tmp;
286     }
287     return min;
288   }
289 
290   /**
291    * Returns the minimum x value value from an array of arrays of
292    * <code>Coordinate</code>s.
293    * @param spectra
294    * @param vd
295    *
296    * @return the minimum x value value from an array of arrays of
297    *         <code>Coordinate</code>s
298    */
getMinX(Lst<Spectrum> spectra, ViewData vd)299   public static double getMinX(Lst<Spectrum> spectra, ViewData vd) {
300     double min = Double.MAX_VALUE;
301     for (int i = 0; i < spectra.size(); i++) {
302       Coordinate[] xyCoords = spectra.get(i).getXYCoords();
303       double tmp = getMinX(xyCoords, vd.getStartingPointIndex(i), vd.getEndingPointIndex(i));
304       if (tmp < min)
305         min = tmp;
306     }
307     return min;
308   }
309 
310   /**
311    * Returns the minimum x value of an array of <code>Coordinate</code>s
312    *
313    * @param coords
314    *        the coordinates
315    * @param start
316    *        the starting index
317    * @param end
318    *        the ending index
319    * @return the minimum x value of an array of <code>Coordinate</code>s
320    */
getMaxX(Coordinate[] coords, int start, int end)321   public static double getMaxX(Coordinate[] coords, int start, int end) {
322     double max = -Double.MAX_VALUE;
323     for (int index = start; index <= end; index++) {
324       double tmp = coords[index].getXVal();
325       if (tmp > max)
326         max = tmp;
327     }
328     return max;
329   }
330 
331   /**
332    * Returns the maximum x value value from an array of arrays of
333    * <code>Coordinate</code>s.
334    * @param spectra
335    * @param vd
336    * @return the maximum x value value from an array of arrays of
337    *         <code>Coordinate</code>s
338    */
getMaxX(Lst<Spectrum> spectra, ViewData vd)339   public static double getMaxX(Lst<Spectrum> spectra, ViewData vd) {
340     double max = -Double.MAX_VALUE;
341     for (int i = 0; i < spectra.size(); i++) {
342       Coordinate[] xyCoords = spectra.get(i).getXYCoords();
343       double tmp = getMaxX(xyCoords, vd.getStartingPointIndex(i), vd.getEndingPointIndex(i));
344       if (tmp > max)
345         max = tmp;
346     }
347     return max;
348   }
349 
350   /**
351    * Returns the minimum y value of an array of <code>Coordinate</code>s
352    *
353    * @param coords
354    *        the coordinates
355    * @param start
356    *        the starting index
357    * @param end
358    *        the ending index
359    * @return the minimum y value of an array of <code>Coordinate</code>s
360    */
getMinY(Coordinate[] coords, int start, int end)361   public static double getMinY(Coordinate[] coords, int start, int end) {
362     double min = Double.MAX_VALUE;
363     for (int index = start; index <= end; index++) {
364       double tmp = coords[index].getYVal();
365       if (tmp < min)
366       min = tmp;
367     }
368     return min;
369   }
370 
371 
372   /**
373    * Returns the minimum y value value from an array of arrays of
374    * <code>Coordinate</code>s.
375    * @param spectra
376    * @param vd
377    * @return the minimum y value value from an array of arrays of
378    *         <code>Coordinate</code>s
379    */
getMinYUser(Lst<Spectrum> spectra, ViewData vd)380   public static double getMinYUser(Lst<Spectrum> spectra, ViewData vd) {
381     double min = Double.MAX_VALUE;
382     for (int i = 0; i < spectra.size(); i++) {
383       double u = spectra.get(i).getUserYFactor();
384       double yref = spectra.get(i).getYRef();
385       Coordinate[] xyCoords = spectra.get(i).getXYCoords();
386       double tmp = (getMinY(xyCoords, vd.getStartingPointIndex(i), vd.getEndingPointIndex(i)) - yref) * u + yref;
387       if (tmp < min)
388         min = tmp;
389     }
390     return min;
391   }
392 
393   /**
394    * Returns the maximum y value of an array of <code>Coordinate</code>s
395    *
396    * @param coords
397    *        the coordinates
398    * @param start
399    *        the starting index
400    * @param end
401    *        the ending index
402    * @return the maximum y value of an array of <code>Coordinate</code>s
403    */
getMaxY(Coordinate[] coords, int start, int end)404   public static double getMaxY(Coordinate[] coords, int start, int end) {
405     double max = -Double.MAX_VALUE;
406     for (int index = start; index <= end; index++) {
407       double tmp = coords[index].getYVal();
408       if (tmp > max)
409         max = tmp;
410     }
411     return max;
412   }
413 
414   /**
415    * Returns the maximum y value value from an array of arrays of
416    * <code>Coordinate</code>s.
417    * @param spectra
418    * @param vd
419    * @return the maximum y value value from an array of arrays of
420    *         <code>Coordinate</code>s
421    */
getMaxYUser(Lst<Spectrum> spectra, ViewData vd)422   public static double getMaxYUser(Lst<Spectrum> spectra, ViewData vd) {
423     double max = -Double.MAX_VALUE;
424     for (int i = 0; i < spectra.size(); i++) {
425       double u = spectra.get(i).getUserYFactor();
426       double yref = spectra.get(i).getYRef();
427       Coordinate[] xyCoords = spectra.get(i).getXYCoords();
428       double tmp = (getMaxY(xyCoords, vd.getStartingPointIndex(i), vd.getEndingPointIndex(i)) - yref) * u + yref;
429       if (tmp > max)
430         max = tmp;
431     }
432     return max;
433   }
434 
435   private final static Comparator<Coordinate> c = new CoordComparator();
436 
getYValueAt(Coordinate[] xyCoords, double xPt)437   public static double getYValueAt(Coordinate[] xyCoords, double xPt) {
438     int i = getNearestIndexForX(xyCoords, xPt);
439     if (i == 0 || i == xyCoords.length)
440       return Double.NaN;
441     double x1 = xyCoords[i].getXVal();
442     double x0 = xyCoords[i - 1].getXVal();
443     double y1 = xyCoords[i].getYVal();
444     double y0 = xyCoords[i - 1].getYVal();
445     if (x1 == x0)
446       return y1;
447     return y0 + (y1 - y0) / (x1 - x0) * (xPt - x0);
448   }
449 
intoRange(int i, int i0, int i1)450   static int intoRange(int i, int i0, int i1) {
451     return Math.max(Math.min(i, i1), i0);
452   }
453 
getNearestIndexForX(Coordinate[] xyCoords, double xPt)454   public static int getNearestIndexForX(Coordinate[] xyCoords, double xPt) {
455     Coordinate x = new Coordinate().set(xPt, 0);
456     int i = Arrays.binarySearch(xyCoords, x, c);
457     if (i < 0) i = -1 - i;
458     if (i < 0)
459       return 0;
460     if (i > xyCoords.length - 1)
461       return xyCoords.length - 1;
462     return i;
463   }
464 
findXForPeakNearest(Coordinate[] xyCoords, double x, boolean isMin)465   public static double findXForPeakNearest(Coordinate[] xyCoords, double x,
466       boolean isMin) {
467     int pt = getNearestIndexForX(xyCoords, x);
468     double f = (isMin ? -1 : 1);
469     while (pt < xyCoords.length - 1 && f * (xyCoords[pt + 1].yVal - xyCoords[pt].yVal) > 0)
470         pt++;
471     while (pt >= 1 && f * (xyCoords[pt - 1].yVal - xyCoords[pt].yVal) > 0)
472       pt--;
473     // now at local max
474     // could leave it there?
475     // see https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html
476     if (pt == 0 || pt == xyCoords.length - 1)
477       return xyCoords[pt].xVal;
478     return parabolicInterpolation(xyCoords, pt);
479   }
480 
481   /**
482    *    see https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html
483    *
484    * @param xyCoords
485    * @param pt
486    * @return center
487    */
parabolicInterpolation(Coordinate[] xyCoords, int pt)488   public static double parabolicInterpolation(Coordinate[] xyCoords, int pt) {
489     double alpha = xyCoords[pt - 1].yVal;
490     double beta = xyCoords[pt].yVal;
491     double gamma = xyCoords[pt + 1].yVal;
492     double p = (alpha - gamma) / 2 / (alpha - 2 * beta + gamma);
493     return xyCoords[pt].xVal + p * (xyCoords[pt + 1].xVal - xyCoords[pt].xVal);
494   }
495 
getPickedCoordinates(Coordinate[] coordsClicked, Coordinate coordClicked, Coordinate coord, Coordinate actualCoord)496   static boolean getPickedCoordinates(Coordinate[] coordsClicked,
497       Coordinate coordClicked, Coordinate coord, Coordinate actualCoord) {
498     if (coordClicked == null)
499       return false;
500     double x = coordClicked.getXVal();
501     coord.setXVal(x);
502     coord.setYVal(coordClicked.getYVal());
503     if (actualCoord == null)
504       return true;
505     int pt = getNearestIndexForX(coordsClicked, x);
506     actualCoord.setXVal(coordsClicked[pt].getXVal());
507     actualCoord.setYVal(coordsClicked[pt].getYVal());
508     return true;
509   }
510 
shiftX(Coordinate[] xyCoords, double dx)511   public static void shiftX(Coordinate[] xyCoords, double dx) {
512     for (int i = xyCoords.length; --i >= 0;)
513       xyCoords[i].xVal += dx;
514   }
515 
516   /**
517    * discovers nearest peak left or right of x that is above threshold y
518    *
519    * @param xyCoords
520    * @param x
521    * @param y
522    * @param inverted
523    * @param andGreaterThanX
524    * @return   interpolated x value or NaN
525    */
getNearestXWithYAbove(Coordinate[] xyCoords, double x, double y, boolean inverted, boolean andGreaterThanX)526   public static double getNearestXWithYAbove(Coordinate[] xyCoords, double x,
527       double y, boolean inverted, boolean andGreaterThanX) {
528     int pt = getNearestIndexForX(xyCoords, x);
529     double f = (inverted ? -1 : 1);
530     if (andGreaterThanX)
531       while (pt < xyCoords.length && f * (xyCoords[pt].yVal - y) < 0)
532         pt++;
533     else
534       while (pt >= 0 && f * (xyCoords[pt].yVal - y) < 0)
535         pt--;
536     if (pt == -1 || pt == xyCoords.length)
537       return Double.NaN;
538     return findXForPeakNearest(xyCoords, xyCoords[pt].getXVal(), inverted);
539   }
540 }
541