001    /*
002     * $Id: RangeProcessor.java,v 1.28 2012/04/10 15:32:42 rink Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    
031    package edu.wisc.ssec.mcidasv.data.hydra;
032    
033    import java.util.HashMap;
034    import java.util.ArrayList;
035    
036    import org.slf4j.Logger;
037    import org.slf4j.LoggerFactory;
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
210             * @param values
211             * @param subset
212             * @return
213             */
214            
215            public float[] processRange(byte[] values, HashMap subset) {
216                    
217                    if (subset != null) {
218                            if (subset.get(multiScaleDimName) != null) {
219                                    soIndex  = (int) ((double[])subset.get(multiScaleDimName))[0];
220                            }
221                    }
222            
223                    float[] new_values = new float[values.length];
224                    
225                    // if we are working with unsigned data, need to convert missing vals to unsigned too
226                    if (unsigned) {
227                            if (missing != null) {
228                                    for (int i = 0; i < missing.length; i++) {
229                                            missing[i] = (float) Util.unsignedByteToInt((byte) missing[i]);
230                                    }
231                            }
232                    }
233                    
234                    float val = 0f;
235                    int i = 0;
236                    boolean isMissing = false;
237                    
238                    for (int k = 0; k < values.length; k++) {
239                            
240                            val = (float) values[k];
241                            if (unsigned) {
242                                    i = Util.unsignedByteToInt(values[k]);
243                                    val = (float) i;
244                            }   
245                            
246                            // first, check the (possibly multiple) missing values
247                            isMissing = false;
248                            if (missing != null) {
249                                    for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
250                                            if (val == missing[mvIdx]) {
251                                                    isMissing = true;
252                                                    break;
253                                            }
254                                    }
255                            }
256                            
257                            if (isMissing) {
258                                    new_values[k] = Float.NaN;
259                                    continue;
260                            }
261                            
262                            if (rangeCheckBeforeScaling) {
263                                    if ((val < valid_low) || (val > valid_high)) {
264                                            new_values[k] = Float.NaN;
265                                            continue;
266                                    }
267                            }
268                            
269                            if (scale != null) {
270                                    if (unpack) {
271                                            new_values[k] = scale[soIndex] * (val) + offset[soIndex];
272                                    } else {
273                                            new_values[k] = scale[soIndex] * (val - offset[soIndex]);
274                                    }
275                            }
276                            else {
277                                    new_values[k] = val;
278                            }
279    
280                            // do valid range check AFTER scaling?
281                            if (! rangeCheckBeforeScaling) {
282                                    if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) {
283                                            new_values[k] = Float.NaN;
284                                    }
285                            }
286                    }
287                    return new_values;
288            }
289    
290            /**
291             * Process a range of data from a short array
292             * @param values
293             * @param subset
294             * @return
295             */
296            
297            public float[] processRange(short[] values, HashMap subset) {
298                    
299    
300                    if (subset != null) {
301                            if (subset.get(multiScaleDimName) != null) {
302                                    soIndex  = (int) ((double[])subset.get(multiScaleDimName))[0];
303                            }
304                    }
305    
306                    float[] new_values = new float[values.length];
307                    
308                    // if we are working with unsigned data, need to convert missing vals to unsigned too
309                    if (unsigned) {
310                            if (missing != null) {
311                                    for (int i = 0; i < missing.length; i++) {
312                                            missing[i] = (float) Util.unsignedShortToInt((short) missing[i]);
313                                    }
314                            }
315                    }
316    
317                    float val = 0f;
318                    int i = 0;
319                    boolean isMissing = false;
320                    
321                    for (int k = 0; k < values.length; k++) {
322                            
323                            val = (float) values[k];
324                            if (unsigned) {
325                                    i = Util.unsignedShortToInt(values[k]);
326                                    val = (float) i;
327                            }
328                            
329                            // first, check the (possibly multiple) missing values
330                            isMissing = false;
331                            if (missing != null) {
332                                    for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
333                                            if (val == missing[mvIdx]) {
334                                                    isMissing = true;
335                                                    break;
336                                            }
337                                    }
338                            }
339                            
340                            if (isMissing) {
341                                    new_values[k] = Float.NaN;
342                                    continue;
343                            }
344                            
345                            if (rangeCheckBeforeScaling) {
346                                    if ((val < valid_low) || (val > valid_high)) {
347                                            new_values[k] = Float.NaN;
348                                            continue;
349                                    }
350                            }
351    
352                            if (scale != null) {
353                                    if (unpack) {
354                                            new_values[k] = (scale[soIndex] * val) + offset[soIndex];
355                                    } else {
356                                            new_values[k] = scale[soIndex] * (val - offset[soIndex]);
357                                    }
358                            } else {
359                                    new_values[k] = val;
360                            }
361    
362                            // do valid range check AFTER scaling?
363                            if (! rangeCheckBeforeScaling) {
364                                    if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) {
365                                            new_values[k] = Float.NaN;
366                                    }
367                            }
368                            
369                    }
370                    return new_values;
371            }
372    
373            /**
374             * Process a range of data from a float array
375             * @param values
376             * @param subset
377             * @return
378             */
379            
380            public float[] processRange(float[] values, HashMap subset) {
381                    
382                    float[] new_values = null;
383    
384                    if ((missing != null) || (valid_range != null)) {
385                            new_values = new float[values.length];
386                    }
387                    else {
388                            return values;
389                    }
390    
391                    float val;
392    
393                    for (int k = 0; k < values.length; k++) {
394                            val = values[k];
395                            new_values[k] = val;
396                            
397                            // first, check the (possibly multiple) missing values
398                            if (missing != null) {
399                                    for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
400                                            if (val == missing[mvIdx]) {
401                                                    new_values[k] = Float.NaN;
402                                                    break;
403                                            }
404                                    }
405                            }
406                            
407                            if ((valid_range != null) && ((val < valid_low) || (val > valid_high))) {
408                                    new_values[k] = Float.NaN;
409                            }
410                            
411                    }
412    
413                    return new_values;
414            }
415    
416            /**
417             * Process a range of data from a double array
418             * @param values
419             * @param subset
420             * @return
421             */
422            
423            public double[] processRange(double[] values, HashMap subset) {
424                    
425                    double[] new_values = null;
426    
427                    if ((missing != null) || (valid_range != null)) {
428                            new_values = new double[values.length];
429                    }
430                    else {
431                            return values;
432                    }
433    
434                    double val;
435    
436                    for (int k = 0; k < values.length; k++) {
437                            val = values[k];
438                            new_values[k] = val;
439                            
440                            // first, check the (possibly multiple) missing values
441                            if (missing != null) {
442                                    for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
443                                            if (val == missing[mvIdx]) {
444                                                    new_values[k] = Float.NaN;
445                                                    break;
446                                            }
447                                    }
448                            }
449                            
450                            if ((valid_range != null) && ((val < valid_low) || (val > valid_high))) {
451                                    new_values[k] = Double.NaN;
452                            }
453                    }
454    
455                    return new_values;
456            }
457    
458            /**
459             * Process a range of data from a byte array
460             * @param values
461             * @return
462             */
463            
464            public float[] processAlongMultiScaleDim(byte[] values) {
465    
466                    float[] new_values = new float[values.length];
467    
468            // if we are working with unsigned data, need to convert missing vals to unsigned too
469            if (unsigned) {
470                    if (missing != null) {
471                            for (int i = 0; i < missing.length; i++) {
472                                    missing[i] = (float) Util.unsignedByteToInt((byte) missing[i]);
473                            }
474                    }
475            }
476    
477            float val = 0f;
478            int i = 0;
479            boolean isMissing = false;
480    
481                    for (int k = 0; k < values.length; k++) {
482                            
483                            val = (float) values[k];
484                if (unsigned) {
485                    i = Util.unsignedByteToInt(values[k]);
486                    val = (float) i;
487                }
488                
489                            // first, check the (possibly multiple) missing values
490                            isMissing = false;
491                            if (missing != null) {
492                                    for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
493                                            if (val == missing[mvIdx]) {
494                                                    isMissing = true;
495                                                    break;
496                                            }
497                                    }
498                            }
499                            
500                            if (isMissing) {
501                                    new_values[k] = Float.NaN;
502                                    continue;
503                            }
504                            
505                            if (rangeCheckBeforeScaling) {
506                                    if ((val < valid_low) || (val > valid_high)) {
507                                            new_values[k] = Float.NaN;
508                                            continue;
509                                    }
510                            }
511                            
512                            if (unpack) {
513                                    new_values[k] = scale[k] * val + offset[k];
514                            } else {
515                                    new_values[k] = scale[k] * (val - offset[k]);
516                            }
517    
518                            // do valid range check AFTER scaling?
519                            if (! rangeCheckBeforeScaling) {
520                                    if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) {
521                                            new_values[k] = Float.NaN;
522                                    }
523                            }
524                    }
525                    return new_values;
526            }
527    
528            /**
529             * Process a range of data from a short array
530             * @param values
531             * @return
532             */
533            
534            public float[] processAlongMultiScaleDim(short[] values) {
535    
536                    float[] new_values = new float[values.length];
537    
538            // if we are working with unsigned data, need to convert missing vals to unsigned too
539            if (unsigned) {
540                    if (missing != null) {
541                            for (int i = 0; i < missing.length; i++) {
542                                    missing[i] = (float) Util.unsignedShortToInt((short) missing[i]);
543                            }
544                    }
545            }
546    
547            float val = 0f;
548            int i = 0;
549            boolean isMissing = false;
550    
551                    for (int k = 0; k < values.length; k++) {
552                            
553                            val = (float) values[k];
554                if (unsigned) {
555                    i = Util.unsignedShortToInt(values[k]);
556                    val = (float) i;
557                }
558                
559                            // first, check the (possibly multiple) missing values
560                            isMissing = false;
561                            if (missing != null) {
562                                    for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
563                                            if (val == missing[mvIdx]) {
564                                                    isMissing = true;
565                                                    break;
566                                            }
567                                    }
568                            }
569                                    
570                            if (isMissing) {
571                                    new_values[k] = Float.NaN;
572                                    continue;
573                            }
574                            
575                            if (rangeCheckBeforeScaling) {
576                                    if ((val < valid_low) || (val > valid_high)) {
577                                            new_values[k] = Float.NaN;
578                                            continue;
579                                    }
580                            }
581    
582                            if (unpack) {
583                                    new_values[k] = scale[k] * val + offset[k];
584                            } else {
585                                    new_values[k] = scale[k] * (val - offset[k]);
586                            }
587    
588                            // do valid range check AFTER scaling?
589                            if (! rangeCheckBeforeScaling) {
590                                    if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) {
591                                            new_values[k] = Float.NaN;
592                                    }
593                            }
594                    }
595                    return new_values;
596            }
597    
598            public void setMultiScaleDimName(String multiScaleDimName) {
599                 this.multiScaleDimName = multiScaleDimName;
600            }
601    
602            public int getMultiScaleDimensionIndex() {
603                 return multiScaleDimensionIndex;
604            }
605    
606            public boolean hasMultiDimensionScale() {
607                 return hasMultiDimensionScale;
608            }
609    
610            public void setHasMultiDimensionScale(boolean yesno) {
611                 hasMultiDimensionScale = yesno;
612            }
613    
614            public void setMultiScaleIndex(int idx) {
615                 this.soIndex = idx;
616            }
617    
618    }
619    
620    class IASI_RangeProcessor extends RangeProcessor {
621    
622            public IASI_RangeProcessor() throws Exception {
623                    super();
624            }
625    
626            public float[] processRange(short[] values, HashMap subset) {
627                    int channelIndex = (int) ((double[]) subset.get(SpectrumAdapter.channelIndex_name))[0];
628    
629                    float[] new_values = IASI_L1C_Utility.getDecodedIASIImage(values, null, channelIndex);
630    
631                    double[] track_coords = (double[]) subset.get(SwathAdapter.track_name);
632                    double[] xtrack_coords = (double[]) subset.get(SwathAdapter.xtrack_name);
633    
634                    int numElems = ((int)(xtrack_coords[1] - xtrack_coords[0]) + 1);
635                    int numLines = ((int)(track_coords[1] - track_coords[0]) + 1);
636    
637                    new_values = IASI_L1C_Utility.psuedoScanReorder2(new_values, 60, numLines*2); 
638    
639                    //- subset here, if necessary
640    
641                    return new_values;
642            }
643    
644    }
645    
646    class CrIS_RangeProcessor extends RangeProcessor {
647    
648            public CrIS_RangeProcessor() throws Exception {
649                    super();
650            }
651    
652            public float[] processRange(float[] values, HashMap subset) {
653    
654                    double[] track_coords = (double[]) subset.get(SwathAdapter.track_name);
655                    double[] xtrack_coords = (double[]) subset.get(SwathAdapter.xtrack_name);
656    
657                    int numElems = ((int)(xtrack_coords[1] - xtrack_coords[0]) + 1);
658                    int numLines = ((int)(track_coords[1] - track_coords[0]) + 1);
659    
660                    values = CrIS_SDR_Utility.psuedoScanReorder(values, 90, numLines*3);
661    
662                    //- subset here, if necessary
663    
664                    return values;
665            }
666    
667    }
668    
669    
670    class CloudSat_2B_GEOPROF_RangeProcessor extends RangeProcessor {
671    
672            public CloudSat_2B_GEOPROF_RangeProcessor(MultiDimensionReader reader, HashMap metadata) throws Exception {
673                    super(reader, metadata);
674            }
675    
676            public float[] processRange(short[] values, HashMap subset) {
677                    float[] new_values = new float[values.length];
678                    for (int k=0; k<values.length;k++) {
679                            float val = (float) values[k];
680                            if (val == missing[0]) {
681                                    new_values[k] = Float.NaN;
682                            }
683                            else if ((val < valid_low) || (val > valid_high)) {
684                                    new_values[k] = -40f;
685                            }
686                            else {
687                                    new_values[k] = val/scale[0] + offset[0];
688                            }
689                    }
690                    return new_values;
691            }
692    
693    }
694    
695    class AggregationRangeProcessor extends RangeProcessor {
696    
697        ArrayList<RangeProcessor> rangeProcessors = new ArrayList<RangeProcessor>();
698    
699        int rngIdx = 0;
700    
701        public AggregationRangeProcessor(GranuleAggregation aggrReader, HashMap metadata) throws Exception {
702           super();
703    
704           ArrayList readers = aggrReader.getReaders();
705    
706           int num = 0;
707    
708           for (int rdrIdx = 0; rdrIdx < readers.size(); rdrIdx++) {
709               RangeProcessor rngProcessor = 
710                      RangeProcessor.createRangeProcessor((MultiDimensionReader)readers.get(rdrIdx), metadata);
711              
712               if (rngProcessor.hasMultiDimensionScale()) {
713                  num++;
714               }
715    
716               rangeProcessors.add(rngProcessor);
717           }
718    
719           if (num > 0 && num != readers.size()) {
720               throw new Exception("AggregationRangeProcessor: all or none can define a multiDimensionScale");
721           }
722           else if (num == readers.size()) {
723             setHasMultiDimensionScale(true);
724           }
725    
726           aggrReader.addRangeProcessor((String)metadata.get(SwathAdapter.array_name), this);
727        }
728    
729        public synchronized void setWhichRangeProcessor(int index) {
730          rngIdx = index;
731        }
732    
733        public synchronized void setMultiScaleIndex(int idx) {
734          rangeProcessors.get(rngIdx).setMultiScaleIndex(idx);
735        }
736        
737    
738        public synchronized float[] processRange(byte[] values, HashMap subset) {
739          return rangeProcessors.get(rngIdx).processRange(values, subset);
740        }
741    
742        public synchronized float[] processRange(short[] values, HashMap subset) {
743          return rangeProcessors.get(rngIdx).processRange(values, subset);
744        }
745    
746        public synchronized float[] processRange(float[] values, HashMap subset) {
747          return rangeProcessors.get(rngIdx).processRange(values, subset);
748        }
749    
750        public synchronized double[] processRange(double[] values, HashMap subset) {
751          return rangeProcessors.get(rngIdx).processRange(values, subset);
752        }
753    
754        public synchronized float[] processAlongMultiScaleDim(short[] values) {
755          return rangeProcessors.get(rngIdx).processAlongMultiScaleDim(values);
756        }
757    
758        public synchronized float[] processAlongMultiScaleDim(byte[] values) {
759          return rangeProcessors.get(rngIdx).processAlongMultiScaleDim(values);
760        }
761    }