View Javadoc
1   package nom.tam.fits.compression.algorithm.quant;
2   
3   import nom.tam.fits.compression.algorithm.api.ICompressOption;
4   import nom.tam.fits.compression.provider.param.api.ICompressParameters;
5   import nom.tam.fits.compression.provider.param.base.BundledParameters;
6   import nom.tam.fits.compression.provider.param.quant.QuantizeParameters;
7   
8   /*
9    * #%L
10   * nom.tam FITS library
11   * %%
12   * Copyright (C) 1996 - 2024 nom-tam-fits
13   * %%
14   * This is free and unencumbered software released into the public domain.
15   *
16   * Anyone is free to copy, modify, publish, use, compile, sell, or
17   * distribute this software, either in source code form or as a compiled
18   * binary, for any purpose, commercial or non-commercial, and by any
19   * means.
20   *
21   * In jurisdictions that recognize copyright laws, the author or authors
22   * of this software dedicate any and all copyright interest in the
23   * software to the public domain. We make this dedication for the benefit
24   * of the public at large and to the detriment of our heirs and
25   * successors. We intend this dedication to be an overt act of
26   * relinquishment in perpetuity of all present and future rights to this
27   * software under copyright law.
28   *
29   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
32   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
33   * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
34   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
35   * OTHER DEALINGS IN THE SOFTWARE.
36   * #L%
37   */
38  
39  /**
40   * Quantization options when they are part of the compression scheme. When compressing tables and images includes
41   * quantization (integer representation of floating point data), users can control how exactly the quantization should
42   * be performed. When reading compressed FITS files, these options will be set automatically based on the header values
43   * recorded in the compressed HDU.
44   * 
45   * @see nom.tam.image.compression.hdu.CompressedImageHDU#setQuantAlgorithm(String)
46   * @see nom.tam.image.compression.hdu.CompressedImageHDU#getCompressOption(Class)
47   */
48  public class QuantizeOption implements ICompressOption {
49  
50      /**
51       * and including NULL_VALUE. These values may not be used to represent the quantized and scaled floating point pixel
52       * values If lossy Hcompression is used, and the tiledImageOperation contains null values, then it is also possible
53       * for the compressed values to slightly exceed the range of the actual (lossless) values so we must reserve a
54       * little more space value used to represent undefined pixels
55       */
56      private static final int NULL_VALUE = Integer.MIN_VALUE + 1;
57  
58      /** Shared configuration across copies */
59      private Config config;
60  
61      /** The parameters that represent settings for this option in the FITS headers and/or compressed data columns */
62      protected QuantizeParameters parameters;
63  
64      private ICompressOption compressOption;
65  
66      private double bScale = Double.NaN;
67  
68      private double bZero = Double.NaN;
69  
70      private double nullValue = Double.NaN;
71  
72      private Integer nullValueIndicator;
73  
74      private boolean checkNull;
75  
76      private int intMaxValue;
77  
78      private int intMinValue;
79  
80      private double maxValue;
81  
82      private double minValue;
83  
84      private int tileIndex = 0;
85  
86      private int tileHeight;
87  
88      private int tileWidth;
89  
90      QuantizeOption() {
91          this(null);
92      }
93  
94      /**
95       * Creates a new set of quantization options, to be used together with the specified compression options.
96       *
97       * @param compressOption Compression-specific options to pair with these quantization options, or <code>null</code>.
98       *
99       * @since                1.18
100      */
101     public QuantizeOption(ICompressOption compressOption) {
102         parameters = new QuantizeParameters(this);
103         config = new Config();
104         this.compressOption = compressOption;
105     }
106 
107     @Override
108     public QuantizeOption copy() {
109         try {
110             QuantizeOption copy = (QuantizeOption) clone();
111             if (compressOption != null) {
112                 copy.compressOption = compressOption.copy();
113             }
114             copy.parameters = parameters.copy(copy);
115             return copy;
116         } catch (CloneNotSupportedException e) {
117             throw new IllegalStateException("option could not be cloned", e);
118         }
119     }
120 
121     /**
122      * Returns the integer value that represents missing (<code>null</code>) data in the quantized representation.
123      * 
124      * @return the integer blanking value (<code>null</code> value).
125      * 
126      * @see    #setBNull(Integer)
127      */
128     public Integer getBNull() {
129         return nullValueIndicator;
130     }
131 
132     /**
133      * Returns the quantization level.
134      * 
135      * @return the floating-point difference between integer levels in the quantized data.
136      * 
137      * @see    #setBScale(double)
138      * @see    #getBZero()
139      */
140     public double getBScale() {
141         return bScale;
142     }
143 
144     /**
145      * Returns the quantization offset.
146      * 
147      * @return the floating-point value corresponding to the integer level 0.
148      * 
149      * @see    #setBZero(double)
150      * @see    #getBScale()
151      */
152     public double getBZero() {
153         return bZero;
154     }
155 
156     @Override
157     public ICompressParameters getCompressionParameters() {
158         if (compressOption == null) {
159             return parameters;
160         }
161         return new BundledParameters(parameters, compressOption.getCompressionParameters());
162     }
163 
164     /**
165      * Returns the compression or quantization options, recast for the selected option class.
166      * 
167      * @param  <T>   the generic type of the compression option
168      * @param  clazz the option class for the compression algorithm used with the quantization, or
169      *                   <code>QunatizeOption.class</code> for our own options.
170      * 
171      * @return       the recast options for the requested class or <code>null</code> id we do not have access to options
172      *                   of the requested class.
173      * 
174      * @see          #getCompressOption()
175      */
176     public <T> T getCompressOption(Class<T> clazz) {
177         return unwrap(clazz);
178     }
179 
180     /**
181      * Returns the options for the compression algorithm that accompanies quantization.
182      * 
183      * @return the options for the compression algorithm, or <code>null</code>
184      * 
185      * @see    #getCompressOption(Class)
186      */
187     public final ICompressOption getCompressOption() {
188         return compressOption;
189     }
190 
191     /**
192      * Returns the maximum integer level in the quantized representation.
193      * 
194      * @return the maximum integer level in the quantized data.
195      * 
196      * @see    #getMaxValue()
197      * @see    #getIntMinValue()
198      */
199     public int getIntMaxValue() {
200         return intMaxValue;
201     }
202 
203     /**
204      * Returns the maximum integer level in the quantized representation.
205      * 
206      * @return the maximum integer level in the quantized data.
207      * 
208      * @see    #getMinValue()
209      * @see    #getIntMinValue()
210      */
211     public int getIntMinValue() {
212         return intMinValue;
213     }
214 
215     /**
216      * Returns the maximum floating-point value in the data
217      * 
218      * @return the maximum floating-point value in the data before quantization.
219      * 
220      * @see    #getIntMaxValue()
221      * @see    #getMinValue()
222      */
223     public double getMaxValue() {
224         return maxValue;
225     }
226 
227     /**
228      * Returns the minimum floating-point value in the data
229      * 
230      * @return the minimum floating-point value in the data before quantization.
231      * 
232      * @see    #getIntMinValue()
233      * @see    #getMaxValue()
234      */
235     public double getMinValue() {
236         return minValue;
237     }
238 
239     /**
240      * Returns the floating-point value that indicates a <code>null</code> datum in the image before quantization is
241      * applied. Normally, the FITS standard is that NaN values indicate <code>null</code> values in floating-point
242      * images. While this class allows using other values also, they are not recommended since they are not supported by
243      * FITS in a standard way.
244      * 
245      * @return the floating-point value that represents a <code>null</code> value (missing data) in the image before
246      *             quantization.
247      * 
248      * @see    #setNullValue(double)
249      * @see    #getNullValueIndicator()
250      * @see    #isCheckNull()
251      */
252     public double getNullValue() {
253         return nullValue;
254     }
255 
256     /**
257      * @deprecated use {@link #getBNull()} instead (duplicate method). Returns the integer value that represents missing
258      *                 data (<code>null</code>) in the quantized representation.
259      * 
260      * @return     the integer blanking value (<code>null</code> value).
261      * 
262      * @see        #setBNull(Integer)
263      */
264     @Deprecated
265     public final Integer getNullValueIndicator() {
266         return getBNull();
267     }
268 
269     /**
270      * Returns the quantization resolution level used for automatic qunatization. For Gaussian noise the quantization
271      * level is the standard deviation of the noise divided by this Q value. Thus Q values of a few will ensure that
272      * quantization retains just about all of the information in the noisy data.
273      * 
274      * @return The current Q value, defined as the number of quantized levels per standard deviation (for Gaussian
275      *             noise).
276      * 
277      * @see    #setQlevel(double)
278      * @see    #getBScale()
279      */
280     public double getQLevel() {
281         return config.qlevel;
282     }
283 
284     /**
285      * Gets the random seed value used for dithering
286      * 
287      * @return the random seed value used for dithering
288      * 
289      * @see    #setSeed(long)
290      * @see    RandomSequence
291      */
292     public long getSeed() {
293         return config.seed;
294     }
295 
296     /**
297      * Returns the sequential tile index that this option is currently configured for.
298      * 
299      * @return the sequential tile index that the quantization is configured for
300      * 
301      * @see    #setTileIndex(int)
302      */
303     public long getTileIndex() {
304         return tileIndex;
305     }
306 
307     /**
308      * Returns the tile height
309      * 
310      * @return the tile height in pixels
311      * 
312      * @see    #setTileHeight(int)
313      * @see    #getTileWidth()
314      */
315     @Override
316     public int getTileHeight() {
317         return tileHeight;
318     }
319 
320     /**
321      * Returns the tile width
322      * 
323      * @return the tile width in pixels
324      * 
325      * @see    #setTileWidth(int)
326      * @see    #getTileHeight()
327      */
328     @Override
329     public int getTileWidth() {
330         return tileWidth;
331     }
332 
333     /**
334      * Checks whether we force the integer quantized level 0 to correspond to a floating-point level 0.0, when using
335      * automatic quantization.
336      * 
337      * @return <code>true</code> if we want to keep `BZERO` at 0 when quantizing automatically.
338      * 
339      * @see    #setCenterOnZero(boolean)
340      */
341     public boolean isCenterOnZero() {
342         return config.centerOnZero;
343     }
344 
345     /**
346      * Whether the floating-point data may contain <code>null</code> values (normally NaNs).
347      * 
348      * @return <code>true</code> if we should expect <code>null</code> in the floating-point data. This is automatically
349      *             <code>true</code> if {@link #setBNull(Integer)} was called with a non-null value.
350      * 
351      * @see    #setBNull(Integer)
352      */
353     public boolean isCheckNull() {
354         return checkNull;
355     }
356 
357     /**
358      * Whether automatic quantization treats 0.0 as a special value. Normally values within the `BSCALE` quantization
359      * level around 0.0 will be assigned the same integer quanta, and will become indistinguishable in the quantized
360      * data. Some software may, in their misguided ways, assign exact zero values a special meaning (such as no data) in
361      * which case we may want to distinguish these as we apply quantization. However, it is generally not a good idea to
362      * use 0 as a special value.
363      * 
364      * @return <code>true</code> to treat 0.0 (exact) as a special value, or <code>false</code> to treat is as any other
365      *             measured value (recommended).
366      * 
367      * @see    #setCheckZero(boolean)
368      * @see    #getBScale()
369      */
370     public boolean isCheckZero() {
371         return config.checkZero;
372     }
373 
374     /**
375      * Whether dithering is enabled
376      * 
377      * @return <code>true</code> if dithering is enabled, or else <code>false</code>
378      * 
379      * @see    #setDither(boolean)
380      * @see    #isDither2()
381      */
382     public boolean isDither() {
383         return config.dither;
384     }
385 
386     /**
387      * Whether dither method 2 is used.
388      * 
389      * @return <code>true</code> if dither method 2 is used, or else <code>false</code>
390      * 
391      * @see    #setDither2(boolean)
392      * @see    #isDither()
393      */
394     public boolean isDither2() {
395         return config.dither2;
396     }
397 
398     @Override
399     public boolean isLossyCompression() {
400         return true;
401     }
402 
403     /**
404      * Sets the integer value that represents missing data (<code>null</code>) in the quantized representation.
405      * 
406      * @param  blank the new integer blanking value (that is one that denotes a missing or <code>null</code> datum).
407      *                   Setting this option to <code>null</code> disables the treatment of issing or <code>null</code>
408      *                   data.
409      * 
410      * @return       itself
411      * 
412      * @see          #getBNull()
413      * @see          #isCheckNull()
414      */
415     public ICompressOption setBNull(Integer blank) {
416         if (blank != null) {
417             nullValueIndicator = blank;
418             checkNull = true;
419         } else {
420             checkNull = false;
421         }
422         return this;
423     }
424 
425     /**
426      * Sets the quantization level.
427      * 
428      * @param  value the new floating-point difference between integer levels in the quantized data.
429      * 
430      * @return       itself
431      * 
432      * @see          #setQlevel(double)
433      * @see          #setBZero(double)
434      * @see          #getBScale()
435      */
436     public QuantizeOption setBScale(double value) {
437         bScale = value;
438         return this;
439     }
440 
441     /**
442      * Sets the quantization offset.
443      * 
444      * @param  value the new floating-point value corresponding to the integer level 0.
445      * 
446      * @return       itself
447      * 
448      * @see          #setBScale(double)
449      * @see          #getBZero()
450      */
451     public QuantizeOption setBZero(double value) {
452         bZero = value;
453         return this;
454     }
455 
456     /**
457      * Enabled or disables keeping `BZERO` at 0 when using automatic quantization.
458      * 
459      * @param  value <code>true</code> to keep `BZERO` at 0 when quantizing automatically, that is keep the integer
460      *                   quantized level 0 correspond to floating-point level 0.0. Or, <code>false</code> to let the
461      *                   automatic quantization algorithm determine the optimal quantization offset.
462      * 
463      * @return       iftself
464      * 
465      * @see          #isCenterOnZero()
466      */
467     public QuantizeOption setCenterOnZero(boolean value) {
468         config.centerOnZero = value;
469         return this;
470     }
471 
472     /**
473      * @deprecated       {@link #setBNull(Integer)} controls this feature automatically as needed. Sets whether we
474      *                       should expect the floating-point data to contain <code>null</code> values (normally NaNs).
475      * 
476      * @param      value <code>true</code> if the floating-point data may contain <code>null</code> values.
477      * 
478      * @return           itself
479      * 
480      * @see              #setCheckNull(boolean)
481      * @see              #setBNull(Integer)
482      * @see              #getNullValue()
483      */
484     @Deprecated
485     public QuantizeOption setCheckNull(boolean value) {
486         checkNull = value;
487         if (nullValueIndicator == null) {
488             nullValueIndicator = NULL_VALUE;
489         }
490         return this;
491     }
492 
493     /**
494      * Sets whether automatic quantization is to treat 0.0 as a special value. Normally values within the `BSCALE`
495      * quantization level around 0.0 will be assigned the same integer quanta, and will become indistinguishable in the
496      * quantized data. However some software may assign exact zero values a special meaning (such as no data) in which
497      * case we may want to distinguish these as we apply qunatization. However, it is generally not a good idea to use 0
498      * as a special value. To mark missing data, the FITS standard recognises only NaN as a special value -- while all
499      * other values should constitute valid measurements.
500      * 
501      * @deprecated       It is strongly discouraged to treat 0.0 values as special. FITS only recognises NaN as a
502      *                       special floating-point value marking missing data. All other floating point values are
503      *                       considered valid measurements.
504      * 
505      * @param      value whether to treat values around 0.0 as special.
506      * 
507      * @return           itself
508      * 
509      * @see              #isCheckZero()
510      */
511     @Deprecated
512     public QuantizeOption setCheckZero(boolean value) {
513         config.checkZero = value;
514         return this;
515     }
516 
517     /**
518      * Enables or disables dithering.
519      * 
520      * @param  value <code>true</code> to enable dithering, or else <code>false</code> to disable
521      * 
522      * @return       itself
523      * 
524      * @see          #isDither()
525      * @see          #setDither2(boolean)
526      */
527     public QuantizeOption setDither(boolean value) {
528         config.dither = value;
529         return this;
530     }
531 
532     /**
533      * Sets whether dithering is to use method 2.
534      * 
535      * @param  value <code>true</code> to use dither method 2, or else <code>false</code> for method 1.
536      * 
537      * @return       itself
538      * 
539      * @see          #isDither2()
540      * @see          #setDither(boolean)
541      */
542     public QuantizeOption setDither2(boolean value) {
543         config.dither2 = value;
544         return this;
545     }
546 
547     /**
548      * Sets the maximum integer level in the quantized representation.
549      * 
550      * @param  value the new maximum integer level in the quantized data.
551      * 
552      * @return       itself
553      * 
554      * @see          #getIntMaxValue()
555      * @see          #setIntMinValue(int)
556      */
557     public QuantizeOption setIntMaxValue(int value) {
558         intMaxValue = value;
559         return this;
560     }
561 
562     /**
563      * Sets the minimum integer level in the quantized representation.
564      * 
565      * @param  value the new minimum integer level in the quantized data.
566      * 
567      * @return       itself
568      * 
569      * @see          #getIntMinValue()
570      * @see          #setIntMaxValue(int)
571      */
572     public QuantizeOption setIntMinValue(int value) {
573         intMinValue = value;
574         return this;
575     }
576 
577     /**
578      * Sets the maximum floating-point value in the data
579      * 
580      * @param  value the maximum floating-point value in the data before quantization.
581      * 
582      * @return       itself
583      * 
584      * @see          #getMaxValue()
585      * @see          #setMinValue(double)
586      */
587     public QuantizeOption setMaxValue(double value) {
588         maxValue = value;
589         return this;
590     }
591 
592     /**
593      * Sets the minimum floating-point value in the data
594      * 
595      * @param  value the mininum floating-point value in the data before quantization.
596      * 
597      * @return       itself
598      * 
599      * @see          #getMinValue()
600      * @see          #setMaxValue(double)
601      */
602     public QuantizeOption setMinValue(double value) {
603         minValue = value;
604         return this;
605     }
606 
607     /**
608      * @deprecated       The use of null values other than <code>NaN</code> for floating-point data types is not
609      *                       standard in FITS. You should therefore avoid using this method to change it. Returns the
610      *                       floating-point value that indicates a <code>null</code> datum in the image before
611      *                       quantization is applied. Normally, the FITS standard is that NaN values indicate
612      *                       <code>null</code> values in floating-point images. While this class allows using other
613      *                       values also, they are not recommended since they are not supported by FITS in a standard
614      *                       way.
615      * 
616      * @param      value the new floating-point value that represents a <code>null</code> value (missing data) in the
617      *                       image before quantization.
618      * 
619      * @return           itself
620      * 
621      * @see              #setNullValue(double)
622      */
623     @Deprecated
624     public QuantizeOption setNullValue(double value) {
625         nullValue = value;
626         return this;
627     }
628 
629     @Override
630     public void setParameters(ICompressParameters parameters) {
631         if (parameters instanceof QuantizeParameters) {
632             this.parameters = (QuantizeParameters) parameters.copy(this);
633         } else if (parameters instanceof BundledParameters) {
634             BundledParameters bundle = (BundledParameters) parameters;
635             for (int i = 0; i < bundle.size(); i++) {
636                 setParameters(bundle.get(i));
637             }
638         } else if (compressOption != null) {
639             compressOption.setParameters(parameters);
640         }
641     }
642 
643     /**
644      * Sets the quantization resolution level to use for automatic quantization. For Gaussian noise the quantization
645      * level is the standard deviation of the noise divided by this Q value. Thus Q values of a few will ensusre that
646      * quantization retains just about all of the information contained in the noisy data.
647      * 
648      * @param  value The new Q value, defined as the number of quantized levels per standard deviation (for Gaussian
649      *                   noise).
650      * 
651      * @return       itself
652      * 
653      * @see          #getQLevel()
654      * @see          #setBScale(double)
655      */
656     public QuantizeOption setQlevel(double value) {
657         config.qlevel = value;
658         return this;
659     }
660 
661     /**
662      * Sets the seed value for the dither random generator
663      *
664      * @param  value The seed value, as in <code>ZDITHER0</code>, normally a number between 1 and 10000 (inclusive).
665      *
666      * @return       itself
667      *
668      * @see          #setTileIndex(int)
669      */
670     public QuantizeOption setSeed(long value) {
671         config.seed = value;
672         return this;
673     }
674 
675     /**
676      * Sets the tile index for which to initialize the random number generator with the given seed (i.e.
677      * <code>ZDITHER0</code> value).
678      *
679      * @param  index The 0-based tile index
680      *
681      * @return       itself
682      *
683      * @see          #setSeed(long)
684      */
685     public QuantizeOption setTileIndex(int index) {
686         tileIndex = index;
687         return this;
688     }
689 
690     @Override
691     public QuantizeOption setTileHeight(int value) {
692         tileHeight = value;
693         if (compressOption != null) {
694             compressOption.setTileHeight(value);
695         }
696         return this;
697     }
698 
699     @Override
700     public QuantizeOption setTileWidth(int value) {
701         tileWidth = value;
702         if (compressOption != null) {
703             compressOption.setTileWidth(value);
704         }
705         return this;
706     }
707 
708     @Override
709     public <T> T unwrap(Class<T> clazz) {
710         if (clazz.isAssignableFrom(this.getClass())) {
711             return clazz.cast(this);
712         }
713         if (compressOption != null) {
714             if (clazz.isAssignableFrom(compressOption.getClass())) {
715                 return clazz.cast(compressOption);
716             }
717         }
718         return null;
719     }
720 
721     /**
722      * Stores configuration in a way that can be shared and modified across enclosing option copies.
723      * 
724      * @author Attila Kovacs
725      *
726      * @since  1.18
727      */
728     private static final class Config {
729 
730         private boolean centerOnZero;
731 
732         private boolean checkZero;
733 
734         private boolean dither;
735 
736         private boolean dither2;
737 
738         private double qlevel = Double.NaN;
739 
740         private long seed = 1L;
741     }
742 }