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     public final Integer getNullValueIndicator() {
265         return getBNull();
266     }
267 
268     /**
269      * Returns the quantization resolution level used for automatic qunatization. For Gaussian noise the quantization
270      * level is the standard deviation of the noise divided by this Q value. Thus Q values of a few will ensure that
271      * quantization retains just about all of the information in the noisy data.
272      * 
273      * @return The current Q value, defined as the number of quantized levels per standard deviation (for Gaussian
274      *             noise).
275      * 
276      * @see    #setQlevel(double)
277      * @see    #getBScale()
278      */
279     public double getQLevel() {
280         return config.qlevel;
281     }
282 
283     /**
284      * Gets the random seed value used for dithering
285      * 
286      * @return the random seed value used for dithering
287      * 
288      * @see    #setSeed(long)
289      * @see    RandomSequence
290      */
291     public long getSeed() {
292         return config.seed;
293     }
294 
295     /**
296      * Returns the sequential tile index that this option is currently configured for.
297      * 
298      * @return the sequential tile index that the quantization is configured for
299      * 
300      * @see    #setTileIndex(int)
301      */
302     public long getTileIndex() {
303         return tileIndex;
304     }
305 
306     /**
307      * Returns the tile height
308      * 
309      * @return the tile height in pixels
310      * 
311      * @see    #setTileHeight(int)
312      * @see    #getTileWidth()
313      */
314     @Override
315     public int getTileHeight() {
316         return tileHeight;
317     }
318 
319     /**
320      * Returns the tile width
321      * 
322      * @return the tile width in pixels
323      * 
324      * @see    #setTileWidth(int)
325      * @see    #getTileHeight()
326      */
327     @Override
328     public int getTileWidth() {
329         return tileWidth;
330     }
331 
332     /**
333      * Checks whether we force the integer quantized level 0 to correspond to a floating-point level 0.0, when using
334      * automatic quantization.
335      * 
336      * @return <code>true</code> if we want to keep `BZERO` at 0 when quantizing automatically.
337      * 
338      * @see    #setCenterOnZero(boolean)
339      */
340     public boolean isCenterOnZero() {
341         return config.centerOnZero;
342     }
343 
344     /**
345      * Whether the floating-point data may contain <code>null</code> values (normally NaNs).
346      * 
347      * @return <code>true</code> if we should expect <code>null</code> in the floating-point data. This is automatically
348      *             <code>true</code> if {@link #setBNull(Integer)} was called with a non-null value.
349      * 
350      * @see    #setBNull(Integer)
351      */
352     public boolean isCheckNull() {
353         return checkNull;
354     }
355 
356     /**
357      * Whether automatic quantization treats 0.0 as a special value. Normally values within the `BSCALE` quantization
358      * level around 0.0 will be assigned the same integer quanta, and will become indistinguishable in the quantized
359      * data. Some software may, in their misguided ways, assign exact zero values a special meaning (such as no data) in
360      * which case we may want to distinguish these as we apply quantization. However, it is generally not a good idea to
361      * use 0 as a special value.
362      * 
363      * @return <code>true</code> to treat 0.0 (exact) as a special value, or <code>false</code> to treat is as any other
364      *             measured value (recommended).
365      * 
366      * @see    #setCheckZero(boolean)
367      * @see    #getBScale()
368      */
369     public boolean isCheckZero() {
370         return config.checkZero;
371     }
372 
373     /**
374      * Whether dithering is enabled
375      * 
376      * @return <code>true</code> if dithering is enabled, or else <code>false</code>
377      * 
378      * @see    #setDither(boolean)
379      * @see    #isDither2()
380      */
381     public boolean isDither() {
382         return config.dither;
383     }
384 
385     /**
386      * Whether dither method 2 is used.
387      * 
388      * @return <code>true</code> if dither method 2 is used, or else <code>false</code>
389      * 
390      * @see    #setDither2(boolean)
391      * @see    #isDither()
392      */
393     public boolean isDither2() {
394         return config.dither2;
395     }
396 
397     @Override
398     public boolean isLossyCompression() {
399         return true;
400     }
401 
402     /**
403      * Sets the integer value that represents missing data (<code>null</code>) in the quantized representation.
404      * 
405      * @param  blank the new integer blanking value (that is one that denotes a missing or <code>null</code> datum).
406      *                   Setting this option to <code>null</code> disables the treatment of issing or <code>null</code>
407      *                   data.
408      * 
409      * @return       itself
410      * 
411      * @see          #getBNull()
412      * @see          #isCheckNull()
413      */
414     public ICompressOption setBNull(Integer blank) {
415         if (blank != null) {
416             nullValueIndicator = blank;
417             checkNull = true;
418         } else {
419             checkNull = false;
420         }
421         return this;
422     }
423 
424     /**
425      * Sets the quantization level.
426      * 
427      * @param  value the new floating-point difference between integer levels in the quantized data.
428      * 
429      * @return       itself
430      * 
431      * @see          #setQlevel(double)
432      * @see          #setBZero(double)
433      * @see          #getBScale()
434      */
435     public QuantizeOption setBScale(double value) {
436         bScale = value;
437         return this;
438     }
439 
440     /**
441      * Sets the quantization offset.
442      * 
443      * @param  value the new floating-point value corresponding to the integer level 0.
444      * 
445      * @return       itself
446      * 
447      * @see          #setBScale(double)
448      * @see          #getBZero()
449      */
450     public QuantizeOption setBZero(double value) {
451         bZero = value;
452         return this;
453     }
454 
455     /**
456      * Enabled or disables keeping `BZERO` at 0 when using automatic quantization.
457      * 
458      * @param  value <code>true</code> to keep `BZERO` at 0 when quantizing automatically, that is keep the integer
459      *                   quantized level 0 correspond to floating-point level 0.0. Or, <code>false</code> to let the
460      *                   automatic quantization algorithm determine the optimal quantization offset.
461      * 
462      * @return       iftself
463      * 
464      * @see          #isCenterOnZero()
465      */
466     public QuantizeOption setCenterOnZero(boolean value) {
467         config.centerOnZero = value;
468         return this;
469     }
470 
471     /**
472      * @deprecated       {@link #setBNull(Integer)} controls this feature automatically as needed. Sets whether we
473      *                       should expect the floating-point data to contain <code>null</code> values (normally NaNs).
474      * 
475      * @param      value <code>true</code> if the floating-point data may contain <code>null</code> values.
476      * 
477      * @return           itself
478      * 
479      * @see              #setCheckNull(boolean)
480      * @see              #setBNull(Integer)
481      * @see              #getNullValue()
482      */
483     public QuantizeOption setCheckNull(boolean value) {
484         checkNull = value;
485         if (nullValueIndicator == null) {
486             nullValueIndicator = NULL_VALUE;
487         }
488         return this;
489     }
490 
491     /**
492      * Sets whether automatic quantization is to treat 0.0 as a special value. Normally values within the `BSCALE`
493      * quantization level around 0.0 will be assigned the same integer quanta, and will become indistinguishable in the
494      * quantized data. However some software may assign exact zero values a special meaning (such as no data) in which
495      * case we may want to distinguish these as we apply qunatization. However, it is generally not a good idea to use 0
496      * as a special value. To mark missing data, the FITS standard recognises only NaN as a special value -- while all
497      * other values should constitute valid measurements.
498      * 
499      * @deprecated       It is strongly discouraged to treat 0.0 values as special. FITS only recognises NaN as a
500      *                       special floating-point value marking missing data. All other floating point values are
501      *                       considered valid measurements.
502      * 
503      * @param      value
504      * 
505      * @return           itself
506      * 
507      * @see              #isCheckZero()
508      */
509     public QuantizeOption setCheckZero(boolean value) {
510         config.checkZero = value;
511         return this;
512     }
513 
514     /**
515      * Enables or disables dithering.
516      * 
517      * @param  value <code>true</code> to enable dithering, or else <code>false</code> to disable
518      * 
519      * @return       itself
520      * 
521      * @see          #isDither()
522      * @see          #setDither2(boolean)
523      */
524     public QuantizeOption setDither(boolean value) {
525         config.dither = value;
526         return this;
527     }
528 
529     /**
530      * Sets whether dithering is to use method 2.
531      * 
532      * @param  value <code>true</code> to use dither method 2, or else <code>false</code> for method 1.
533      * 
534      * @return       itself
535      * 
536      * @see          #isDither2()
537      * @see          #setDither(boolean)
538      */
539     public QuantizeOption setDither2(boolean value) {
540         config.dither2 = value;
541         return this;
542     }
543 
544     /**
545      * Sets the maximum integer level in the quantized representation.
546      * 
547      * @param  value the new maximum integer level in the quantized data.
548      * 
549      * @return       itself
550      * 
551      * @see          #getIntMaxValue()
552      * @see          #setIntMinValue(int)
553      */
554     public QuantizeOption setIntMaxValue(int value) {
555         intMaxValue = value;
556         return this;
557     }
558 
559     /**
560      * Sets the minimum integer level in the quantized representation.
561      * 
562      * @param  value the new minimum integer level in the quantized data.
563      * 
564      * @return       itself
565      * 
566      * @see          #getIntMinValue()
567      * @see          #setIntMaxValue(int)
568      */
569     public QuantizeOption setIntMinValue(int value) {
570         intMinValue = value;
571         return this;
572     }
573 
574     /**
575      * Sets the maximum floating-point value in the data
576      * 
577      * @param  value the maximum floating-point value in the data before quantization.
578      * 
579      * @return       itself
580      * 
581      * @see          #getMaxValue()
582      * @see          #setMinValue(double)
583      */
584     public QuantizeOption setMaxValue(double value) {
585         maxValue = value;
586         return this;
587     }
588 
589     /**
590      * Sets the minimum floating-point value in the data
591      * 
592      * @param  value the mininum floating-point value in the data before quantization.
593      * 
594      * @return       itself
595      * 
596      * @see          #getMinValue()
597      * @see          #setMaxValue(double)
598      */
599     public QuantizeOption setMinValue(double value) {
600         minValue = value;
601         return this;
602     }
603 
604     /**
605      * @deprecated       The use of null values other than <code>NaN</code> for floating-point data types is not
606      *                       standard in FITS. You should therefore avoid using this method to change it. Returns the
607      *                       floating-point value that indicates a <code>null</code> datum in the image before
608      *                       quantization is applied. Normally, the FITS standard is that NaN values indicate
609      *                       <code>null</code> values in floating-point images. While this class allows using other
610      *                       values also, they are not recommended since they are not supported by FITS in a standard
611      *                       way.
612      * 
613      * @param      value the new floating-point value that represents a <code>null</code> value (missing data) in the
614      *                       image before quantization.
615      * 
616      * @return           itself
617      * 
618      * @see              #setNullValue(double)
619      */
620     public QuantizeOption setNullValue(double value) {
621         nullValue = value;
622         return this;
623     }
624 
625     @Override
626     public void setParameters(ICompressParameters parameters) {
627         if (parameters instanceof QuantizeParameters) {
628             this.parameters = (QuantizeParameters) parameters.copy(this);
629         } else if (parameters instanceof BundledParameters) {
630             BundledParameters bundle = (BundledParameters) parameters;
631             for (int i = 0; i < bundle.size(); i++) {
632                 setParameters(bundle.get(i));
633             }
634         } else if (compressOption != null) {
635             compressOption.setParameters(parameters);
636         }
637     }
638 
639     /**
640      * Sets the quantization resolution level to use for automatic quantization. For Gaussian noise the quantization
641      * level is the standard deviation of the noise divided by this Q value. Thus Q values of a few will ensusre that
642      * quantization retains just about all of the information contained in the noisy data.
643      * 
644      * @param  value The new Q value, defined as the number of quantized levels per standard deviation (for Gaussian
645      *                   noise).
646      * 
647      * @return       itself
648      * 
649      * @see          #getQLevel()
650      * @see          #setBScale(double)
651      */
652     public QuantizeOption setQlevel(double value) {
653         config.qlevel = value;
654         return this;
655     }
656 
657     /**
658      * Sets the seed value for the dither random generator
659      *
660      * @param  value The seed value, as in <code>ZDITHER0</code>, normally a number between 1 and 10000 (inclusive).
661      *
662      * @return       itself
663      *
664      * @see          #setTileIndex(int)
665      */
666     public QuantizeOption setSeed(long value) {
667         config.seed = value;
668         return this;
669     }
670 
671     /**
672      * Sets the tile index for which to initialize the random number generator with the given seed (i.e.
673      * <code>ZDITHER0</code> value).
674      *
675      * @param  index The 0-based tile index
676      *
677      * @return       itself
678      *
679      * @see          #setSeed(long)
680      */
681     public QuantizeOption setTileIndex(int index) {
682         tileIndex = index;
683         return this;
684     }
685 
686     @Override
687     public QuantizeOption setTileHeight(int value) {
688         tileHeight = value;
689         if (compressOption != null) {
690             compressOption.setTileHeight(value);
691         }
692         return this;
693     }
694 
695     @Override
696     public QuantizeOption setTileWidth(int value) {
697         tileWidth = value;
698         if (compressOption != null) {
699             compressOption.setTileWidth(value);
700         }
701         return this;
702     }
703 
704     @Override
705     public <T> T unwrap(Class<T> clazz) {
706         if (clazz.isAssignableFrom(this.getClass())) {
707             return clazz.cast(this);
708         }
709         if (compressOption != null) {
710             if (clazz.isAssignableFrom(compressOption.getClass())) {
711                 return clazz.cast(compressOption);
712             }
713         }
714         return null;
715     }
716 
717     /**
718      * Stores configuration in a way that can be shared and modified across enclosing option copies.
719      * 
720      * @author Attila Kovacs
721      *
722      * @since  1.18
723      */
724     private static final class Config {
725 
726         private boolean centerOnZero;
727 
728         private boolean checkZero;
729 
730         private boolean dither;
731 
732         private boolean dither2;
733 
734         private double qlevel = Double.NaN;
735 
736         private long seed = 1L;
737     }
738 }