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