001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2026 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 https://www.gnu.org/licenses/. 027 */ 028 029package edu.wisc.ssec.mcidasv.data.hydra; 030 031import java.util.Arrays; 032import java.util.Map; 033import java.util.Objects; 034 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import edu.wisc.ssec.mcidasv.data.QualityFlag; 039import visad.util.Util; 040 041public class RangeProcessor { 042 043 private static final Logger logger = 044 LoggerFactory.getLogger(RangeProcessor.class); 045 046 static RangeProcessor createRangeProcessor(MultiDimensionReader reader, 047 Map<String, Object> metadata) throws Exception { 048 if (reader instanceof GranuleAggregation) { 049 return new AggregationRangeProcessor((GranuleAggregation) reader, metadata); 050 } 051 052 if (metadata.get("scale_name") == null) { 053 String product_name = (String) metadata.get(SwathAdapter.product_name); 054 if (Objects.equals(product_name, "IASI_L1C_xxx")) { 055 return new IASI_RangeProcessor(); 056 } 057 return null; 058 } else { 059 String product_name = (String) metadata.get(ProfileAlongTrack.product_name); 060 if (Objects.equals(product_name, "2B-GEOPROF")) { 061 return new CloudSat_2B_GEOPROF_RangeProcessor(reader, metadata); 062 } else { 063 return new RangeProcessor(reader, metadata); 064 } 065 } 066 } 067 068 MultiDimensionReader reader; 069 Map<String, Object> metadata; 070 071 float[] scale = null; 072 float[] offset = null; 073 double[] missing = null; 074 double[] valid_range = null; 075 double valid_low = -Double.MAX_VALUE; 076 double valid_high = Double.MAX_VALUE; 077 078 boolean unpack = false; 079 boolean unsigned = false; 080 boolean rangeCheckBeforeScaling = true; 081 082 int scaleOffsetLen = 1; 083 084 String multiScaleDimName = SpectrumAdapter.channelIndex_name; 085 boolean hasMultiDimensionScale = false; 086 087 int multiScaleDimensionIndex = 0; 088 089 int soIndex = 0; 090 091 public RangeProcessor() { 092 } 093 094 public RangeProcessor(float scale, float offset, float valid_low, float valid_high, 095 float missing) { 096 this.scale = new float[] { scale }; 097 this.offset = new float[] { offset }; 098 this.missing = new double[] { missing }; 099 this.valid_low = valid_low; 100 this.valid_high = valid_high; 101 } 102 103 public RangeProcessor(MultiDimensionReader reader, Map<String, Object> metadata, 104 String multiScaleDimName) throws Exception { 105 this(reader, metadata); 106 this.multiScaleDimName = multiScaleDimName; 107 } 108 109 public RangeProcessor(MultiDimensionReader reader, Map<String, Object> metadata) 110 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 } else { 142 offset = new float[scaleOffsetLen]; 143 Arrays.fill(offset, 0f); 144 } 145 146 } 147 148 missing = getAttributeAsDoubleArray(array_name, (String) metadata.get("fill_value_name")); 149 150 // if we are working with unsigned data, need to convert missing vals to unsigned too 151 if (unsigned) { 152 if (missing != null) { 153 for (int i = 0; i < missing.length; i++) { 154 missing[i] = (float) Util.unsignedShortToInt((short) missing[i]); 155 } 156 } 157 } 158 159 String metaStr = (String) metadata.get("valid_range"); 160 // attr name not supplied, so try the convention default 161 if (metaStr == null) { 162 metaStr = "valid_range"; 163 } 164 165 valid_range = getAttributeAsDoubleArray(array_name, metaStr); 166 if (valid_range != null) { 167 168 valid_low = valid_range[0]; 169 valid_high = valid_range[1]; 170 171 if (valid_range[0] > valid_range[1]) { 172 valid_low = valid_range[1]; 173 valid_high = valid_range[0]; 174 } 175 } else { 176 metaStr = (String) metadata.get("valid_low"); 177 if (metaStr == null) { // attr name not supplied, so try the 178 // convention default 179 metaStr = "valid_min"; 180 } 181 double[] dblA = getAttributeAsDoubleArray(array_name, metaStr); 182 if (dblA != null) { 183 valid_low = dblA[0]; 184 } 185 186 metaStr = (String) metadata.get("valid_high"); 187 if (metaStr == null) { // attr name not supplied, so try the 188 // convention default 189 metaStr = "valid_max"; 190 } 191 dblA = getAttributeAsDoubleArray(array_name, metaStr); 192 if (dblA != null) { 193 valid_high = dblA[0]; 194 } 195 } 196 197 if (rangeCheckBeforeScaling) { 198 if (unsigned) { 199 if (valid_low != -Double.MAX_VALUE) { 200 valid_low = (double) Util.unsignedShortToInt((short) valid_low); 201 } 202 if (valid_high != Double.MAX_VALUE) { 203 valid_high = (double) Util.unsignedShortToInt((short) valid_high); 204 } 205 } 206 } 207 208 String str = (String) metadata.get("multiScaleDimensionIndex"); 209 hasMultiDimensionScale = (str != null); 210 multiScaleDimensionIndex = (str != null) ? Integer.parseInt(str) : 0; 211 } 212 213 public float[] getAttributeAsFloatArray(String arrayName, String attrName) throws Exception { 214 float[] fltArray = null; 215 HDFArray arrayAttr = reader.getArrayAttribute(arrayName, attrName); 216 217 if (arrayAttr != null) { 218 219 if (arrayAttr.getType().equals(Float.TYPE)) { 220 float[] attr = (float[]) arrayAttr.getArray(); 221 fltArray = new float[attr.length]; 222 for (int k = 0; k < attr.length; k++) 223 fltArray[k] = attr[k]; 224 } else if (arrayAttr.getType().equals(Short.TYPE)) { 225 short[] attr = (short[]) arrayAttr.getArray(); 226 fltArray = new float[attr.length]; 227 for (int k = 0; k < attr.length; k++) 228 fltArray[k] = (float) attr[k]; 229 } else if (arrayAttr.getType().equals(Integer.TYPE)) { 230 int[] attr = (int[]) arrayAttr.getArray(); 231 fltArray = new float[attr.length]; 232 for (int k = 0; k < attr.length; k++) 233 fltArray[k] = (float) attr[k]; 234 } else if (arrayAttr.getType().equals(Double.TYPE)) { 235 double[] attr = (double[]) arrayAttr.getArray(); 236 fltArray = new float[attr.length]; 237 for (int k = 0; k < attr.length; k++) 238 fltArray[k] = (float) attr[k]; 239 } 240 241 } 242 243 return fltArray; 244 } 245 246 public double[] getAttributeAsDoubleArray(String arrayName, String attrName) throws Exception { 247 if (attrName == null) { 248 return null; 249 } 250 251 double[] dblArray = null; 252 HDFArray arrayAttr = null; 253 try { 254 arrayAttr = reader.getArrayAttribute(arrayName, attrName); 255 } catch (Exception e) { 256 String msg = String.format("Problem getting array attribute. " + 257 "arrayName: '%s', attrName: '%s'", arrayName, attrName); 258 logger.error(msg, e); 259 } 260 261 if (arrayAttr != null) { 262 263 if (arrayAttr.getType().equals(Float.TYPE)) { 264 float[] attr = (float[]) arrayAttr.getArray(); 265 dblArray = new double[attr.length]; 266 for (int k = 0; k < attr.length; k++) 267 dblArray[k] = attr[k]; 268 } else if (arrayAttr.getType().equals(Short.TYPE)) { 269 short[] attr = (short[]) arrayAttr.getArray(); 270 dblArray = new double[attr.length]; 271 for (int k = 0; k < attr.length; k++) { 272 if (unsigned) { 273 dblArray[k] = (double) Util.unsignedShortToInt((short) attr[k]); 274 } else { 275 dblArray[k] = (double) attr[k]; 276 } 277 } 278 } else if (arrayAttr.getType().equals(Integer.TYPE)) { 279 int[] attr = (int[]) arrayAttr.getArray(); 280 dblArray = new double[attr.length]; 281 for (int k = 0; k < attr.length; k++) 282 dblArray[k] = (double) attr[k]; 283 } else if (arrayAttr.getType().equals(Double.TYPE)) { 284 double[] attr = (double[]) arrayAttr.getArray(); 285 dblArray = new double[attr.length]; 286 for (int k = 0; k < attr.length; k++) 287 dblArray[k] = (double) attr[k]; 288 } else if (arrayAttr.getType().equals(Byte.TYPE)) { 289 byte[] attr = (byte[]) arrayAttr.getArray(); 290 dblArray = new double[attr.length]; 291 for (int k = 0; k < attr.length; k++) 292 dblArray[k] = (double) attr[k]; 293 } else if (arrayAttr.getType().equals(String.class)) { 294 String[] attr = (String[]) arrayAttr.getArray(); 295 dblArray = new double[attr.length]; 296 for (int k = 0; k < attr.length; k++) 297 dblArray[k] = Double.valueOf(attr[0]); 298 } 299 } 300 301 return dblArray; 302 } 303 304 /** 305 * Process a range of data from an array of {@code byte} values where bytes 306 * are packed bit or multi-bit fields of quality flags. Based on info in a 307 * {@link QualityFlag} object passed in, we extract and return values for 308 * that flag. 309 * 310 * @param values 311 * Input byte values. Cannot be {@code null}. 312 * @param subset 313 * Optional subset. 314 * @param qf 315 * Quality flag. 316 * 317 * @return Processed range. 318 */ 319 320 public float[] processRangeQualityFlag(int[] values, Map<String, double[]> subset, QualityFlag qf) { 321 322 if (subset != null) { 323 if (subset.get(multiScaleDimName) != null) { 324 soIndex = (int) (subset.get(multiScaleDimName))[0]; 325 } 326 } 327 328 float[] newValues = new float[values.length]; 329 330 float val = 0f; 331 int bitOffset = qf.getBitOffset(); 332 int divisor = -1; 333 334 // map bit offset to a divisor 335 switch (bitOffset) { 336 case 1: 337 divisor = 2; 338 break; 339 case 2: 340 divisor = 4; 341 break; 342 case 3: 343 divisor = 8; 344 break; 345 case 4: 346 divisor = 16; 347 break; 348 case 5: 349 divisor = 32; 350 break; 351 case 6: 352 divisor = 64; 353 break; 354 case 7: 355 divisor = 128; 356 break; 357 default: 358 divisor = 1; 359 break; 360 } 361 362 // now map bit width to a mask 363 int numBits = qf.getNumBits(); 364 int mask = -1; 365 switch (numBits) { 366 case 1: 367 mask = (int) 0x00000001; 368 break; 369 case 2: 370 mask = (int) 0x00000003; 371 break; 372 case 3: 373 mask = (int) 0x00000007; 374 break; 375 case 4: 376 mask = (int) 0x0000000F; 377 break; 378 case 5: 379 mask = (int) 0x0000001F; 380 break; 381 case 6: 382 mask = (int) 0x0000003F; 383 break; 384 case 7: 385 mask = (int) 0x0000007F; 386 break; 387 default: 388 mask = (int) 0x00000000; 389 break; 390 } 391 392 for (int k = 0; k < values.length; k++) { 393 val = (float) ((values[k] / divisor) & mask); 394 newValues[k] = val; 395 } 396 397 return newValues; 398 } 399 400 /** 401 * Process a range of data from an array of {@code byte} values. 402 * 403 * @param values 404 * Input {@code byte} values. Cannot be {@code null}. 405 * @param subset 406 * Optional subset. 407 * 408 * @return Processed range. 409 */ 410 411 public float[] processRange(byte[] values, Map<String, double[]> subset) { 412 413 int multiScaleDimLen = 1; 414 415 if (subset != null) { 416 if (subset.get(multiScaleDimName) != null) { 417 double[] coords = subset.get(multiScaleDimName); 418 soIndex = (int) coords[0]; 419 multiScaleDimLen = (int) (coords[1] - coords[0] + 1.0); 420 } 421 } 422 423 float[] new_values = new float[values.length]; 424 425 float val = 0f; 426 int i = 0; 427 boolean isMissing = false; 428 429 for (int k = 0; k < values.length; k++) { 430 431 val = (float) values[k]; 432 if (unsigned) { 433 i = Util.unsignedByteToInt(values[k]); 434 val = (float) i; 435 } 436 437 // first, check the (possibly multiple) missing values 438 isMissing = false; 439 if (missing != null) { 440 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 441 if (val == missing[mvIdx]) { 442 isMissing = true; 443 break; 444 } 445 } 446 } 447 448 if (isMissing) { 449 new_values[k] = Float.NaN; 450 continue; 451 } 452 453 if (rangeCheckBeforeScaling) { 454 if ((val < valid_low) || (val > valid_high)) { 455 new_values[k] = Float.NaN; 456 continue; 457 } 458 } 459 460 if (scale != null) { 461 if (unpack) { 462 if (multiScaleDimLen == 1) { 463 new_values[k] = (scale[soIndex] * val) + offset[soIndex]; 464 } else { 465 new_values[k] = (scale[soIndex + k] * val) + offset[soIndex + k]; 466 } 467 } else { 468 if (multiScaleDimLen == 1) { 469 new_values[k] = scale[soIndex] * (val - offset[soIndex]); 470 } else { 471 new_values[k] = scale[soIndex + k] * (val - offset[soIndex + k]); 472 } 473 } 474 475 } else { 476 new_values[k] = val; 477 } 478 479 // do valid range check AFTER scaling? 480 if (!rangeCheckBeforeScaling) { 481 if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) { 482 new_values[k] = Float.NaN; 483 } 484 } 485 } 486 return new_values; 487 } 488 489 /** 490 * Process a range of data from an array of {@code short} values. 491 * 492 * @param values 493 * Input {@code short} values. Cannot be {@code null}. 494 * @param subset 495 * Optional subset. 496 * 497 * @return Processed range. 498 */ 499 500 public float[] processRange(short[] values, Map<String, double[]> subset) { 501 502 int multiScaleDimLen = 1; 503 504 if (subset != null) { 505 if (subset.get(multiScaleDimName) != null) { 506 double[] coords = subset.get(multiScaleDimName); 507 soIndex = (int) coords[0]; 508 multiScaleDimLen = (int) (coords[1] - coords[0] + 1.0); 509 } 510 } 511 512 float[] new_values = new float[values.length]; 513 514 float val = 0f; 515 int i = 0; 516 boolean isMissing = false; 517 518 for (int k = 0; k < values.length; k++) { 519 520 val = (float) values[k]; 521 if (unsigned) { 522 i = Util.unsignedShortToInt((short)values[k]); 523 val = (float) i; 524 } 525 526 // first, check the (possibly multiple) missing values 527 isMissing = false; 528 if (missing != null) { 529 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 530 if (val == missing[mvIdx]) { 531 isMissing = true; 532 break; 533 } 534 } 535 } 536 537 if (isMissing) { 538 new_values[k] = Float.NaN; 539 continue; 540 } 541 542 if (rangeCheckBeforeScaling) { 543 if ((val < valid_low) || (val > valid_high)) { 544 new_values[k] = Float.NaN; 545 continue; 546 } 547 } 548 549 if (scale != null) { 550 if (unpack) { 551 if (multiScaleDimLen == 1) { 552 new_values[k] = (scale[soIndex] * val) + offset[soIndex]; 553 } else { 554 new_values[k] = (scale[soIndex + k] * val) + offset[soIndex + k]; 555 } 556 } else { 557 if (multiScaleDimLen == 1) { 558 new_values[k] = scale[soIndex] * (val - offset[soIndex]); 559 } else { 560 561 new_values[k] = scale[soIndex + k] * (val - offset[soIndex + k]); 562 } 563 } 564 } else { 565 new_values[k] = val; 566 } 567 568 // do valid range check AFTER scaling? 569 if (!rangeCheckBeforeScaling) { 570 if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) { 571 new_values[k] = Float.NaN; 572 } 573 } 574 575 } 576 return new_values; 577 } 578 579 public float[] processRangeUshorts(int[] values, Map<String, double[]> subset) { 580 581 int multiScaleDimLen = 1; 582 583 if (subset != null) { 584 if (subset.get(multiScaleDimName) != null) { 585 double[] coords = subset.get(multiScaleDimName); 586 soIndex = (int) coords[0]; 587 multiScaleDimLen = (int) (coords[1] - coords[0] + 1.0); 588 } 589 } 590 591 float[] new_values = new float[values.length]; 592 593 float val = 0f; 594 int i = 0; 595 boolean isMissing = false; 596 597 for (int k = 0; k < values.length; k++) { 598 599 val = (float) values[k]; 600 if (unsigned) { 601 i = Util.unsignedShortToInt((short)values[k]); 602 val = (float) i; 603 } 604 605 // first, check the (possibly multiple) missing values 606 isMissing = false; 607 if (missing != null) { 608 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 609 if (val == missing[mvIdx]) { 610 isMissing = true; 611 break; 612 } 613 } 614 } 615 616 if (isMissing) { 617 new_values[k] = Float.NaN; 618 continue; 619 } 620 621 if (rangeCheckBeforeScaling) { 622 if ((val < valid_low) || (val > valid_high)) { 623 new_values[k] = Float.NaN; 624 continue; 625 } 626 } 627 628 if (scale != null) { 629 if (unpack) { 630 if (multiScaleDimLen == 1) { 631 new_values[k] = (scale[soIndex] * val) + offset[soIndex]; 632 } else { 633 new_values[k] = (scale[soIndex + k] * val) + offset[soIndex + k]; 634 } 635 } else { 636 if (multiScaleDimLen == 1) { 637 new_values[k] = scale[soIndex] * (val - offset[soIndex]); 638 } else { 639 640 new_values[k] = scale[soIndex + k] * (val - offset[soIndex + k]); 641 } 642 } 643 } else { 644 new_values[k] = val; 645 } 646 647 // do valid range check AFTER scaling? 648 if (!rangeCheckBeforeScaling) { 649 if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) { 650 new_values[k] = Float.NaN; 651 } 652 } 653 } 654 return new_values; 655 } 656 657 /** 658 * Process a range of data from an array of {@code float} values. 659 * 660 * @param values 661 * Input {@code float} values. Cannot be {@code null}. 662 * @param subset 663 * Optional subset. 664 * 665 * @return Processed array. 666 */ 667 668 public float[] processRange(float[] values, Map<String, double[]> subset) { 669 670 float[] new_values = null; 671 672 if ((missing != null) || (valid_range != null)) { 673 new_values = new float[values.length]; 674 } else { 675 return values; 676 } 677 678 float val; 679 680 for (int k = 0; k < values.length; k++) { 681 val = values[k]; 682 new_values[k] = val; 683 684 // first, check the (possibly multiple) missing values 685 if (missing != null) { 686 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 687 if (val == missing[mvIdx]) { 688 new_values[k] = Float.NaN; 689 break; 690 } 691 } 692 } 693 694 if ((valid_range != null) && ((val < valid_low) || (val > valid_high))) { 695 new_values[k] = Float.NaN; 696 } 697 698 } 699 700 return new_values; 701 } 702 703 /** 704 * Process a range of data from an array of {@code double} value. 705 * 706 * @param values 707 * Input {@code double} values. Cannot be {@code null}. 708 * @param subset 709 * Optional subset. 710 * 711 * @return Processed array. 712 */ 713 714 public double[] processRange(double[] values, Map<String, double[]> subset) { 715 716 double[] new_values = null; 717 718 if ((missing != null) || (valid_range != null)) { 719 new_values = new double[values.length]; 720 } else { 721 return values; 722 } 723 724 double val; 725 726 for (int k = 0; k < values.length; k++) { 727 val = values[k]; 728 new_values[k] = val; 729 730 // first, check the (possibly multiple) missing values 731 if (missing != null) { 732 for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) { 733 if (val == missing[mvIdx]) { 734 new_values[k] = Float.NaN; 735 break; 736 } 737 } 738 } 739 740 if ((valid_range != null) && ((val < valid_low) || (val > valid_high))) { 741 new_values[k] = Double.NaN; 742 } 743 } 744 745 return new_values; 746 } 747 748 public void setMultiScaleDimName(String multiScaleDimName) { 749 this.multiScaleDimName = multiScaleDimName; 750 } 751 752 public int getMultiScaleDimensionIndex() { 753 return multiScaleDimensionIndex; 754 } 755 756 public boolean hasMultiDimensionScale() { 757 return hasMultiDimensionScale; 758 } 759 760 public void setHasMultiDimensionScale(boolean yesno) { 761 hasMultiDimensionScale = yesno; 762 } 763 764 public void setMultiScaleIndex(int idx) { 765 this.soIndex = idx; 766 } 767 768 /** 769 * Should be generalized. For now works for short to float conversions 770 * 771 * @param values 772 * the set of input values to map 773 * @param lut 774 * the lookup table for direct mapping input to output values 775 * @return output array as primitive floats 776 */ 777 778 public Object processRangeApplyLUT(short[] values, float[] lut) { 779 780 float[] newValues = new float[values.length]; 781 782 int lutLen = lut.length; 783 784 for (int i = 0; i < values.length; i++) { 785 int tmpVal = values[i]; 786 if (tmpVal > 0) { 787 if (tmpVal < lutLen) { 788 newValues[i] = lut[tmpVal]; 789 } else { 790 newValues[i] = Float.NaN; 791 } 792 } else { 793 newValues[i] = Float.NaN; 794 } 795 } 796 797 return newValues; 798 } 799 800 public Object processRangeUshortsApplyLUT(int[] values, float[] lut) { 801 802 float[] newValues = new float[values.length]; 803 804 int lutLen = lut.length; 805 806 for (int i = 0; i < values.length; i++) { 807 int tmpVal = values[i]; 808 if (tmpVal > 0) { 809 if (tmpVal < lutLen) { 810 newValues[i] = lut[tmpVal]; 811 } else { 812 newValues[i] = Float.NaN; 813 } 814 } else { 815 newValues[i] = Float.NaN; 816 } 817 } 818 819 return newValues; 820 } 821 822}