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