001 /* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2013 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 029 package edu.wisc.ssec.mcidasv.data.hydra; 030 031 import java.util.HashMap; 032 import java.util.ArrayList; 033 034 import org.slf4j.Logger; 035 import org.slf4j.LoggerFactory; 036 037 import edu.wisc.ssec.mcidasv.data.QualityFlag; 038 039 import visad.util.Util; 040 041 public class RangeProcessor { 042 043 private static final Logger logger = LoggerFactory.getLogger(RangeProcessor.class); 044 045 static RangeProcessor createRangeProcessor(MultiDimensionReader reader, HashMap metadata) throws Exception { 046 if (reader instanceof GranuleAggregation) { 047 return new AggregationRangeProcessor((GranuleAggregation)reader, metadata); 048 } 049 050 if (metadata.get("scale_name") == null) { 051 String product_name = (String) metadata.get(SwathAdapter.product_name); 052 if (product_name == "IASI_L1C_xxx") { 053 return new IASI_RangeProcessor(); 054 } 055 return null; 056 } 057 else { 058 String product_name = (String) metadata.get(ProfileAlongTrack.product_name); 059 if (product_name == "2B-GEOPROF") { 060 return new CloudSat_2B_GEOPROF_RangeProcessor(reader, metadata); 061 } 062 else { 063 return new RangeProcessor(reader, metadata); 064 } 065 } 066 } 067 068 MultiDimensionReader reader; 069 HashMap metadata; 070 071 float[] scale = null; 072 float[] offset = null; 073 float[] missing = null; 074 float[] valid_range = null; 075 float valid_low = -Float.MAX_VALUE; 076 float valid_high = Float.MAX_VALUE; 077 float[] low = new float[] {-Float.MAX_VALUE}; 078 float[] high = new float[] {Float.MAX_VALUE}; 079 080 boolean unpack = false; 081 boolean unsigned = false; 082 boolean rangeCheckBeforeScaling = true; 083 084 int scaleOffsetLen = 1; 085 086 String multiScaleDimName = SpectrumAdapter.channelIndex_name; 087 boolean hasMultiDimensionScale = false; 088 089 int multiScaleDimensionIndex = 0; 090 091 int soIndex = 0; 092 093 public RangeProcessor() { 094 } 095 096 public RangeProcessor(float scale, float offset, float valid_low, float valid_high, float missing) { 097 this.scale = new float[] {scale}; 098 this.offset = new float[] {offset}; 099 this.missing = new float[] {missing}; 100 this.valid_low = valid_low; 101 this.valid_high = valid_high; 102 } 103 104 105 public RangeProcessor(MultiDimensionReader reader, HashMap metadata, String multiScaleDimName) throws Exception { 106 this(reader, metadata); 107 this.multiScaleDimName = multiScaleDimName; 108 } 109 110 public RangeProcessor(MultiDimensionReader reader, HashMap metadata) throws Exception { 111 this.reader = reader; 112 this.metadata = metadata; 113 114 if (metadata.get("unpack") != null) { 115 unpack = true; 116 } 117 118 if (metadata.get("unsigned") != null) { 119 unsigned = true; 120 } 121 122 if (metadata.get("range_check_after_scaling") != null) { 123 String s = (String) metadata.get("range_check_after_scaling"); 124 logger.debug("range_check_after_scaling: " + s); 125 rangeCheckBeforeScaling = false; 126 } 127 128 String array_name = (String) metadata.get("array_name"); 129 130 scale = getAttributeAsFloatArray(array_name, (String) metadata.get("scale_name")); 131 132 offset = getAttributeAsFloatArray(array_name, (String) metadata.get("offset_name")); 133 134 if (scale != null) { 135 scaleOffsetLen = scale.length; 136 137 if (offset != null) { 138 if (scale.length != offset.length) { 139 throw new Exception("RangeProcessor: scale and offset array lengths must be equal"); 140 } 141 } 142 else { 143 offset = new float[scaleOffsetLen]; 144 for (int i=0; i<offset.length; i++) offset[i] = 0f; 145 } 146 147 } 148 149 missing = getAttributeAsFloatArray(array_name, (String) metadata.get("fill_value_name")); 150 151 String metaStr = (String)metadata.get("valid_range"); 152 // attr name not supplied, so try the convention default 153 if (metaStr == null) { 154 metaStr = "valid_range"; 155 } 156 157 valid_range = getAttributeAsFloatArray(array_name, metaStr); 158 if (valid_range != null) { 159 160 valid_low = valid_range[0]; 161 valid_high = valid_range[1]; 162 163 if (valid_range[0] > valid_range[1]) { 164 valid_low = valid_range[1]; 165 valid_high = valid_range[0]; 166 } 167 } 168 169 String str = (String)metadata.get("multiScaleDimensionIndex"); 170 hasMultiDimensionScale = (str != null); 171 multiScaleDimensionIndex = (str != null) ? Integer.parseInt(str) : 0; 172 } 173 174 public float[] getAttributeAsFloatArray(String arrayName, String attrName) 175 throws Exception 176 { 177 float[] fltArray = null; 178 HDFArray arrayAttr = reader.getArrayAttribute(arrayName, attrName); 179 180 if (arrayAttr != null) { 181 182 if (arrayAttr.getType().equals(Float.TYPE)) { 183 float[] attr = (float[]) arrayAttr.getArray(); 184 fltArray = new float[attr.length]; 185 for (int k=0; k<attr.length; k++) fltArray[k] = attr[k]; 186 } 187 else if (arrayAttr.getType().equals(Short.TYPE)) { 188 short[] attr = (short[]) arrayAttr.getArray(); 189 fltArray = new float[attr.length]; 190 for (int k=0; k<attr.length; k++) fltArray[k] = (float) attr[k]; 191 } 192 else if (arrayAttr.getType().equals(Integer.TYPE)) { 193 int[] attr = (int[]) arrayAttr.getArray(); 194 fltArray = new float[attr.length]; 195 for (int k=0; k<attr.length; k++) fltArray[k] = (float) attr[k]; 196 } 197 else if (arrayAttr.getType().equals(Double.TYPE)) { 198 double[] attr = (double[]) arrayAttr.getArray(); 199 fltArray = new float[attr.length]; 200 for (int k=0; k<attr.length; k++) fltArray[k] = (float) attr[k]; 201 } 202 203 } 204 205 return fltArray; 206 } 207 208 /** 209 * Process a range of data from a byte array where bytes are packed bit 210 * or multi-bit fields of quality flags. Based on info in a QualityFlag 211 * object passed in, we extract and return values for that flag. 212 * 213 * @param values input values 214 * @param subset optional subset 215 * @param qf quality flag 216 * @return processed range 217 */ 218 219 public float[] processRangeQualityFlag(byte[] values, HashMap subset, QualityFlag qf) { 220 221 if (subset != null) { 222 if (subset.get(multiScaleDimName) != null) { 223 soIndex = (int) ((double[])subset.get(multiScaleDimName))[0]; 224 } 225 } 226 227 float[] newValues = new float[values.length]; 228 229 float val = 0f; 230 int bitOffset = qf.getBitOffset(); 231 int divisor = -1; 232 233 // map bit offset to a divisor 234 switch (bitOffset) { 235 case 1: 236 divisor = 2; 237 break; 238 case 2: 239 divisor = 4; 240 break; 241 case 3: 242 divisor = 8; 243 break; 244 case 4: 245 divisor = 16; 246 break; 247 case 5: 248 divisor = 32; 249 break; 250 case 6: 251 divisor = 64; 252 break; 253 case 7: 254 divisor = 128; 255 break; 256 default: 257 divisor = 1; 258 break; 259 } 260 261 // now map bit width to a mask 262 int numBits = qf.getNumBits(); 263 int mask = -1; 264 switch (numBits) { 265 case 1: 266 mask = (int) 0x00000001; 267 break; 268 case 2: 269 mask = (int) 0x00000003; 270 break; 271 case 3: 272 mask = (int) 0x00000007; 273 break; 274 case 4: 275 mask = (int) 0x0000000F; 276 break; 277 case 5: 278 mask = (int) 0x0000001F; 279 break; 280 case 6: 281 mask = (int) 0x0000003F; 282 break; 283 case 7: 284 mask = (int) 0x0000007F; 285 break; 286 default: 287 mask = (int) 0x00000000; 288 break; 289 } 290 291 int i = 0; 292 for (int k = 0; k < values.length; k++) { 293 val = (float) values[k]; 294 i = Util.unsignedByteToInt(values[k]); 295 val = (float) ((i / divisor) & mask); 296 newValues[k] = val; 297 } 298 299 return newValues; 300 } 301 302 /** 303 * Process a range of data from a byte array 304 * @param values 305 * @param subset 306 * @return 307 */ 308 309 public float[] processRange(byte[] values, HashMap subset) { 310 311 if (subset != null) { 312 if (subset.get(multiScaleDimName) != null) { 313 soIndex = (int) ((double[])subset.get(multiScaleDimName))[0]; 314 } 315 } 316 317 float[] new_values = new float[values.length]; 318 319 // if we are working with unsigned data, need to convert missing vals to unsigned too 320 if (unsigned) { 321 if (missing != null) { 322 for (int i = 0; i < missing.length; i++) { 323 missing[i] = (float) Util.unsignedByteToInt((byte) missing[i]); 324 } 325 } 326 } 327 328 float val = 0f; 329 int i = 0; 330 boolean isMissing = false; 331 332 for (int k = 0; k < values.length; k++) { 333 334 val = (float) values[k]; 335 if (unsigned) { 336 i = Util.unsignedByteToInt(values[k]); 337 val = (float) i; 338 } 339 340 // first, check the (possibly multiple) missing values 341 isMissing = false; 342 if (missing != null) { 343 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 344 if (val == missing[mvIdx]) { 345 isMissing = true; 346 break; 347 } 348 } 349 } 350 351 if (isMissing) { 352 new_values[k] = Float.NaN; 353 continue; 354 } 355 356 if (rangeCheckBeforeScaling) { 357 if ((val < valid_low) || (val > valid_high)) { 358 new_values[k] = Float.NaN; 359 continue; 360 } 361 } 362 363 if (scale != null) { 364 if (unpack) { 365 new_values[k] = scale[soIndex] * (val) + offset[soIndex]; 366 } else { 367 new_values[k] = scale[soIndex] * (val - offset[soIndex]); 368 } 369 } 370 else { 371 new_values[k] = val; 372 } 373 374 // do valid range check AFTER scaling? 375 if (! rangeCheckBeforeScaling) { 376 if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) { 377 new_values[k] = Float.NaN; 378 } 379 } 380 } 381 return new_values; 382 } 383 384 /** 385 * Process a range of data from a short array 386 * @param values 387 * @param subset 388 * @return 389 */ 390 391 public float[] processRange(short[] values, HashMap subset) { 392 393 394 if (subset != null) { 395 if (subset.get(multiScaleDimName) != null) { 396 soIndex = (int) ((double[])subset.get(multiScaleDimName))[0]; 397 } 398 } 399 400 float[] new_values = new float[values.length]; 401 402 // if we are working with unsigned data, need to convert missing vals to unsigned too 403 if (unsigned) { 404 if (missing != null) { 405 for (int i = 0; i < missing.length; i++) { 406 missing[i] = (float) Util.unsignedShortToInt((short) missing[i]); 407 } 408 } 409 } 410 411 float val = 0f; 412 int i = 0; 413 boolean isMissing = false; 414 415 for (int k = 0; k < values.length; k++) { 416 417 val = (float) values[k]; 418 if (unsigned) { 419 i = Util.unsignedShortToInt(values[k]); 420 val = (float) i; 421 } 422 423 // first, check the (possibly multiple) missing values 424 isMissing = false; 425 if (missing != null) { 426 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 427 if (val == missing[mvIdx]) { 428 isMissing = true; 429 break; 430 } 431 } 432 } 433 434 if (isMissing) { 435 new_values[k] = Float.NaN; 436 continue; 437 } 438 439 if (rangeCheckBeforeScaling) { 440 if ((val < valid_low) || (val > valid_high)) { 441 new_values[k] = Float.NaN; 442 continue; 443 } 444 } 445 446 if (scale != null) { 447 if (unpack) { 448 new_values[k] = (scale[soIndex] * val) + offset[soIndex]; 449 } else { 450 new_values[k] = scale[soIndex] * (val - offset[soIndex]); 451 } 452 } else { 453 new_values[k] = val; 454 } 455 456 // do valid range check AFTER scaling? 457 if (! rangeCheckBeforeScaling) { 458 if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) { 459 new_values[k] = Float.NaN; 460 } 461 } 462 463 } 464 return new_values; 465 } 466 467 /** 468 * Process a range of data from a float array 469 * @param values 470 * @param subset 471 * @return 472 */ 473 474 public float[] processRange(float[] values, HashMap subset) { 475 476 float[] new_values = null; 477 478 if ((missing != null) || (valid_range != null)) { 479 new_values = new float[values.length]; 480 } 481 else { 482 return values; 483 } 484 485 float val; 486 487 for (int k = 0; k < values.length; k++) { 488 val = values[k]; 489 new_values[k] = val; 490 491 // first, check the (possibly multiple) missing values 492 if (missing != null) { 493 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 494 if (val == missing[mvIdx]) { 495 new_values[k] = Float.NaN; 496 break; 497 } 498 } 499 } 500 501 if ((valid_range != null) && ((val < valid_low) || (val > valid_high))) { 502 new_values[k] = Float.NaN; 503 } 504 505 } 506 507 return new_values; 508 } 509 510 /** 511 * Process a range of data from a double array 512 * @param values 513 * @param subset 514 * @return 515 */ 516 517 public double[] processRange(double[] values, HashMap subset) { 518 519 double[] new_values = null; 520 521 if ((missing != null) || (valid_range != null)) { 522 new_values = new double[values.length]; 523 } 524 else { 525 return values; 526 } 527 528 double val; 529 530 for (int k = 0; k < values.length; k++) { 531 val = values[k]; 532 new_values[k] = val; 533 534 // first, check the (possibly multiple) missing values 535 if (missing != null) { 536 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 537 if (val == missing[mvIdx]) { 538 new_values[k] = Float.NaN; 539 break; 540 } 541 } 542 } 543 544 if ((valid_range != null) && ((val < valid_low) || (val > valid_high))) { 545 new_values[k] = Double.NaN; 546 } 547 } 548 549 return new_values; 550 } 551 552 /** 553 * Process a range of data from a byte array 554 * @param values 555 * @return 556 */ 557 558 public float[] processAlongMultiScaleDim(byte[] values) { 559 560 float[] new_values = new float[values.length]; 561 562 // if we are working with unsigned data, need to convert missing vals to unsigned too 563 if (unsigned) { 564 if (missing != null) { 565 for (int i = 0; i < missing.length; i++) { 566 missing[i] = (float) Util.unsignedByteToInt((byte) missing[i]); 567 } 568 } 569 } 570 571 float val = 0f; 572 int i = 0; 573 boolean isMissing = false; 574 575 for (int k = 0; k < values.length; k++) { 576 577 val = (float) values[k]; 578 if (unsigned) { 579 i = Util.unsignedByteToInt(values[k]); 580 val = (float) i; 581 } 582 583 // first, check the (possibly multiple) missing values 584 isMissing = false; 585 if (missing != null) { 586 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 587 if (val == missing[mvIdx]) { 588 isMissing = true; 589 break; 590 } 591 } 592 } 593 594 if (isMissing) { 595 new_values[k] = Float.NaN; 596 continue; 597 } 598 599 if (rangeCheckBeforeScaling) { 600 if ((val < valid_low) || (val > valid_high)) { 601 new_values[k] = Float.NaN; 602 continue; 603 } 604 } 605 606 if (unpack) { 607 new_values[k] = scale[k] * val + offset[k]; 608 } else { 609 new_values[k] = scale[k] * (val - offset[k]); 610 } 611 612 // do valid range check AFTER scaling? 613 if (! rangeCheckBeforeScaling) { 614 if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) { 615 new_values[k] = Float.NaN; 616 } 617 } 618 } 619 return new_values; 620 } 621 622 /** 623 * Process a range of data from a short array 624 * @param values 625 * @return 626 */ 627 628 public float[] processAlongMultiScaleDim(short[] values) { 629 630 float[] new_values = new float[values.length]; 631 632 // if we are working with unsigned data, need to convert missing vals to unsigned too 633 if (unsigned) { 634 if (missing != null) { 635 for (int i = 0; i < missing.length; i++) { 636 missing[i] = (float) Util.unsignedShortToInt((short) missing[i]); 637 } 638 } 639 } 640 641 float val = 0f; 642 int i = 0; 643 boolean isMissing = false; 644 645 for (int k = 0; k < values.length; k++) { 646 647 val = (float) values[k]; 648 if (unsigned) { 649 i = Util.unsignedShortToInt(values[k]); 650 val = (float) i; 651 } 652 653 // first, check the (possibly multiple) missing values 654 isMissing = false; 655 if (missing != null) { 656 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 657 if (val == missing[mvIdx]) { 658 isMissing = true; 659 break; 660 } 661 } 662 } 663 664 if (isMissing) { 665 new_values[k] = Float.NaN; 666 continue; 667 } 668 669 if (rangeCheckBeforeScaling) { 670 if ((val < valid_low) || (val > valid_high)) { 671 new_values[k] = Float.NaN; 672 continue; 673 } 674 } 675 676 if (unpack) { 677 new_values[k] = scale[k] * val + offset[k]; 678 } else { 679 new_values[k] = scale[k] * (val - offset[k]); 680 } 681 682 // do valid range check AFTER scaling? 683 if (! rangeCheckBeforeScaling) { 684 if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) { 685 new_values[k] = Float.NaN; 686 } 687 } 688 } 689 return new_values; 690 } 691 692 public void setMultiScaleDimName(String multiScaleDimName) { 693 this.multiScaleDimName = multiScaleDimName; 694 } 695 696 public int getMultiScaleDimensionIndex() { 697 return multiScaleDimensionIndex; 698 } 699 700 public boolean hasMultiDimensionScale() { 701 return hasMultiDimensionScale; 702 } 703 704 public void setHasMultiDimensionScale(boolean yesno) { 705 hasMultiDimensionScale = yesno; 706 } 707 708 public void setMultiScaleIndex(int idx) { 709 this.soIndex = idx; 710 } 711 712 } 713 714 class IASI_RangeProcessor extends RangeProcessor { 715 716 public IASI_RangeProcessor() throws Exception { 717 super(); 718 } 719 720 public float[] processRange(short[] values, HashMap subset) { 721 int channelIndex = (int) ((double[]) subset.get(SpectrumAdapter.channelIndex_name))[0]; 722 723 float[] new_values = IASI_L1C_Utility.getDecodedIASIImage(values, null, channelIndex); 724 725 double[] track_coords = (double[]) subset.get(SwathAdapter.track_name); 726 double[] xtrack_coords = (double[]) subset.get(SwathAdapter.xtrack_name); 727 728 int numElems = ((int)(xtrack_coords[1] - xtrack_coords[0]) + 1); 729 int numLines = ((int)(track_coords[1] - track_coords[0]) + 1); 730 731 new_values = IASI_L1C_Utility.psuedoScanReorder2(new_values, 60, numLines*2); 732 733 //- subset here, if necessary 734 735 return new_values; 736 } 737 738 } 739 740 class CrIS_RangeProcessor extends RangeProcessor { 741 742 public CrIS_RangeProcessor() throws Exception { 743 super(); 744 } 745 746 public float[] processRange(float[] values, HashMap subset) { 747 748 double[] track_coords = (double[]) subset.get(SwathAdapter.track_name); 749 double[] xtrack_coords = (double[]) subset.get(SwathAdapter.xtrack_name); 750 751 int numElems = ((int)(xtrack_coords[1] - xtrack_coords[0]) + 1); 752 int numLines = ((int)(track_coords[1] - track_coords[0]) + 1); 753 754 values = CrIS_SDR_Utility.psuedoScanReorder(values, 90, numLines*3); 755 756 //- subset here, if necessary 757 758 return values; 759 } 760 761 } 762 763 764 class CloudSat_2B_GEOPROF_RangeProcessor extends RangeProcessor { 765 766 public CloudSat_2B_GEOPROF_RangeProcessor(MultiDimensionReader reader, HashMap metadata) throws Exception { 767 super(reader, metadata); 768 if (scale == null) { // use implicit default value since E05, E06 has removed the scale/offset from the Radar Refl variable 769 scale = new float[] {100f}; 770 offset = new float[] {0f}; 771 } 772 } 773 774 public float[] processRange(short[] values, HashMap subset) { 775 float[] new_values = new float[values.length]; 776 for (int k=0; k<values.length;k++) { 777 float val = (float) values[k]; 778 if (val == missing[0]) { 779 new_values[k] = Float.NaN; 780 } 781 else if ((val < valid_low) || (val > valid_high)) { 782 new_values[k] = -40f; 783 } 784 else { 785 new_values[k] = val/scale[0] + offset[0]; 786 } 787 } 788 return new_values; 789 } 790 791 } 792 793 class AggregationRangeProcessor extends RangeProcessor { 794 795 ArrayList<RangeProcessor> rangeProcessors = new ArrayList<RangeProcessor>(); 796 797 int rngIdx = 0; 798 799 public AggregationRangeProcessor(GranuleAggregation aggrReader, HashMap metadata) throws Exception { 800 super(); 801 802 ArrayList readers = aggrReader.getReaders(); 803 804 int num = 0; 805 806 for (int rdrIdx = 0; rdrIdx < readers.size(); rdrIdx++) { 807 RangeProcessor rngProcessor = 808 RangeProcessor.createRangeProcessor((MultiDimensionReader)readers.get(rdrIdx), metadata); 809 810 if (rngProcessor.hasMultiDimensionScale()) { 811 num++; 812 } 813 814 rangeProcessors.add(rngProcessor); 815 } 816 817 if (num > 0 && num != readers.size()) { 818 throw new Exception("AggregationRangeProcessor: all or none can define a multiDimensionScale"); 819 } 820 else if (num == readers.size()) { 821 setHasMultiDimensionScale(true); 822 } 823 824 aggrReader.addRangeProcessor((String)metadata.get(SwathAdapter.array_name), this); 825 } 826 827 public synchronized void setWhichRangeProcessor(int index) { 828 rngIdx = index; 829 } 830 831 public synchronized void setMultiScaleIndex(int idx) { 832 rangeProcessors.get(rngIdx).setMultiScaleIndex(idx); 833 } 834 835 836 public synchronized float[] processRange(byte[] values, HashMap subset) { 837 return rangeProcessors.get(rngIdx).processRange(values, subset); 838 } 839 840 public synchronized float[] processRange(short[] values, HashMap subset) { 841 return rangeProcessors.get(rngIdx).processRange(values, subset); 842 } 843 844 public synchronized float[] processRange(float[] values, HashMap subset) { 845 return rangeProcessors.get(rngIdx).processRange(values, subset); 846 } 847 848 public synchronized double[] processRange(double[] values, HashMap subset) { 849 return rangeProcessors.get(rngIdx).processRange(values, subset); 850 } 851 852 public synchronized float[] processAlongMultiScaleDim(short[] values) { 853 return rangeProcessors.get(rngIdx).processAlongMultiScaleDim(values); 854 } 855 856 public synchronized float[] processAlongMultiScaleDim(byte[] values) { 857 return rangeProcessors.get(rngIdx).processAlongMultiScaleDim(values); 858 } 859 }