View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.tiff;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertTrue;
21  import static org.junit.jupiter.api.Assertions.fail;
22  
23  import java.awt.Color;
24  import java.awt.image.BufferedImage;
25  import java.io.File;
26  import java.io.IOException;
27  import java.nio.ByteOrder;
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  import org.apache.commons.imaging.FormatCompliance;
32  import org.apache.commons.imaging.ImagingException;
33  import org.apache.commons.imaging.ImagingTestConstants;
34  import org.apache.commons.imaging.bytesource.ByteSource;
35  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.floatingpoint.PaletteEntry;
36  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.floatingpoint.PaletteEntryForRange;
37  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.floatingpoint.PaletteEntryForValue;
38  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.floatingpoint.PhotometricInterpreterFloat;
39  import org.junit.jupiter.api.Test;
40  
41  /**
42   * Performs tests that access the content of TIFF files containing floating point data.
43   */
44  public class TiffFloatingPointReadTest {
45  
46      private void checkSubImage(final File target, final TiffRasterData fullRaster, final int x0, final int y0, final int width, final int height)
47              throws ImagingException, IOException {
48          final TiffImagingParameters params = new TiffImagingParameters();
49          params.setSubImage(x0, y0, width, height);
50          final TiffRasterData partRaster = readRasterFromTIFF(target, params);
51          assertEquals(width, partRaster.getWidth(), "Invalid width in partial for " + target.getName());
52          assertEquals(height, partRaster.getHeight(), "Invalid height in partial for " + target.getName());
53          for (int y = y0; y < y0 + height; y++) {
54              for (int x = x0; x < x0 + width; x++) {
55                  final float vFull = fullRaster.getValue(x, y);
56                  final float vPart = partRaster.getValue(x - x0, y - y0);
57                  assertEquals(vFull, vPart, "Invalid value match for partial at (" + x + "," + y + ") for " + target.getName());
58              }
59          }
60      }
61  
62      /**
63       * Gets a file from the TIFF test directory that contains floating-point data.
64       *
65       * @param name a valid file name
66       * @return a valid file reference.
67       */
68      private File getTiffFile(final String name) {
69          final File tiffFolder = new File(ImagingTestConstants.TEST_IMAGE_FOLDER, "tiff");
70          final File fpFolder = new File(tiffFolder, "9");
71          return new File(fpFolder, name);
72      }
73  
74      /**
75       * Read a TIFF file using a PhotometricInterpreter with entries for the specified range of values and an arbitrary no-data value. If the image is
76       * successfully read, the interpreter instance will be returned.
77       *
78       * @param target the specified TIFF file
79       * @param f0     the expected minimum bound or lower
80       * @param f1     the expected maximum bound or higher
81       * @param fNot   an arbitrary non-data value or NaN
82       * @return if successful, a valid photometric interpreter.
83       * @throws ImagingException in the event of an unsupported or malformed file data element.
84       * @throws IOException      in the event of an I/O error
85       */
86      private PhotometricInterpreterFloat readAndInterpretTIFF(final File target, final float f0, final float f1, final float fNot)
87              throws ImagingException, IOException {
88          final ByteSource byteSource = ByteSource.file(target);
89          final TiffReader tiffReader = new TiffReader(true);
90          final TiffContents contents = tiffReader.readDirectories(byteSource, true, // indicates that application should read image data, if present
91                  FormatCompliance.getDefault());
92          final ByteOrder byteOrder = tiffReader.getByteOrder();
93          final TiffDirectory directory = contents.directories.get(0);
94          if (!directory.hasTiffFloatingPointRasterData()) {
95              fail("Internal error, sample file does not have floating-point data " + target.getName());
96          }
97          final List<PaletteEntry> pList = new ArrayList<>();
98          pList.add(new PaletteEntryForValue(fNot, Color.red));
99          pList.add(new PaletteEntryForRange(f0, f1, Color.black, Color.white));
100         final PhotometricInterpreterFloat pInterp = new PhotometricInterpreterFloat(pList);
101         final TiffImagingParameters params = new TiffImagingParameters();
102         params.setCustomPhotometricInterpreter(pInterp);
103         final BufferedImage bImage = directory.getTiffImage(byteOrder, params);
104         if (bImage == null) {
105             return null;
106         }
107         return pInterp;
108     }
109 
110     /**
111      * Read the floating-point content from a TIFF file.
112      *
113      * @param target the specified TIFF file
114      * @param params an optional map of parameters for reading.
115      * @return if successful, a valid raster data instance
116      * @throws ImagingException in the event of an unsupported or malformed file data element.
117      * @throws IOException      in the event of an I/O error
118      */
119     private TiffRasterData readRasterFromTIFF(final File target, final TiffImagingParameters params) throws ImagingException, IOException {
120         final ByteSource byteSource = ByteSource.file(target);
121         final TiffReader tiffReader = new TiffReader(true);
122         final TiffContents contents = tiffReader.readDirectories(byteSource, true, // indicates that application should read image data, if present
123                 FormatCompliance.getDefault());
124         final TiffDirectory directory = contents.directories.get(0);
125         return directory.getRasterData(params);
126     }
127 
128     @Test
129     public void test() throws ImagingException, IOException {
130         // These TIFF sample data includes files that contain known
131         // floating-point values in various formats. We know the range
132         // of values from inspection using separate utilies. This
133         // test verifies that the data can be fetched successfully.
134         // Note that when evaluating the range of values in a TIFF file,
135         // the photometric interpreter does not include the special no-data
136         // code in the tabulation. If you have a file that does not
137         // define a no-data value, just use Float.NaN for testing purposes.
138         //
139         // Test the satellite-derived cloud imagery file
140         // We know from inspection that this sample file contains values
141         // in the range 0 to 1 and uses 9999 as a "no-data" value.
142         File target = getTiffFile("Sample64BitFloatingPointPix451x337.tiff");
143         PhotometricInterpreterFloat pInterp = readAndInterpretTIFF(target, 0f, 1f, 9999f);
144         if (pInterp == null) {
145             fail("Failed to read image " + target.getAbsolutePath());
146         }
147         float minVal = pInterp.getMinFound();
148         float maxVal = pInterp.getMaxFound();
149         boolean testCondition = 0.0 <= minVal && minVal <= 1.0 && 0.0 <= maxVal && maxVal <= 1.0;
150         assertTrue(testCondition, "Min,Max values not in range 0 to 1: " + minVal + ", " + maxVal);
151         assertTrue(minVal <= maxVal, "Min Value not <= maxVal: " + minVal + ", " + maxVal);
152 
153         // To test the sub-image logic, read the full raster and then
154         // the sub-raster. Compare the results. We know from inspection
155         // that the source file is organized using strips of 2 rows each.
156         // The source file is of dimensions 451x337.
157         // The dimensions of the sub-image are arbitrary
158         TiffRasterData fullRaster = readRasterFromTIFF(target, new TiffImagingParameters());
159         int height = fullRaster.getHeight();
160         int width = fullRaster.getWidth();
161         // checks based on the 2-rows per strip model
162         checkSubImage(target, fullRaster, 17, 17, 200, 200);
163         checkSubImage(target, fullRaster, 1, 3, width - 2, 1);
164         checkSubImage(target, fullRaster, 1, 3, width - 2, 3);
165         checkSubImage(target, fullRaster, 1, 4, width - 2, 1);
166         checkSubImage(target, fullRaster, 1, 4, width - 2, 3);
167         // check the 4 edges
168         checkSubImage(target, fullRaster, 0, 0, width, 1); // bottom row
169         checkSubImage(target, fullRaster, 0, 0, 1, height); // left column
170         checkSubImage(target, fullRaster, 0, height - 1, width, 1); // top row
171         checkSubImage(target, fullRaster, width - 1, 0, 1, height); // right column
172 
173         // test along the main diagonal and a parallel that reaches the top-right corner
174         final int s = width - height;
175         for (int i = 0; i < height - 8; i++) {
176             checkSubImage(target, fullRaster, i, i, 8, 8);
177             checkSubImage(target, fullRaster, i + 1, i, 8, 8);
178         }
179 
180         // now read the entire image
181         checkSubImage(target, fullRaster, 0, 0, width, height);
182 
183         // Test the USGS overview file
184         // We know from inspection that this sample file contains values
185         // in the range -2 to 62 and uses -99999 as a "no-data" value.
186         // The file is organized using tiles of size 128-by-128.
187         // and that the overall image size is 338-by-338.
188         target = getTiffFile("USGS_13_n38w077_dir5.tiff");
189         pInterp = readAndInterpretTIFF(target, -2f, 62f, -99999f);
190         if (pInterp == null) {
191             fail("Failed to read image " + target.getAbsolutePath());
192         }
193         minVal = pInterp.getMinFound();
194         maxVal = pInterp.getMaxFound();
195         testCondition = -2 <= minVal && minVal <= 62 && -2 <= maxVal && maxVal <= 62;
196         assertTrue(testCondition, "Min,Max values not in range -2 to 62: " + minVal + ", " + maxVal);
197         assertTrue(minVal <= maxVal, "Min Value not <= maxVal: " + minVal + ", " + maxVal);
198 
199         fullRaster = readRasterFromTIFF(target, new TiffImagingParameters());
200         // The tile size for this file is 128-by-128. The following tests
201         // read subsections starting right before the tile transition and right after it.
202         height = fullRaster.getHeight();
203         width = fullRaster.getWidth();
204         // checks based on the 128-by-128 tile model
205         checkSubImage(target, fullRaster, 126, 126, 132, 132);
206         checkSubImage(target, fullRaster, 128, 128, 128, 128);
207         checkSubImage(target, fullRaster, 1, 1, width - 2, height - 2);
208         // check the 4 edges
209         checkSubImage(target, fullRaster, 0, 0, width, 1); // bottom row
210         checkSubImage(target, fullRaster, 0, 0, 1, height); // left column
211         checkSubImage(target, fullRaster, 0, height - 1, width, 1); // top row
212         checkSubImage(target, fullRaster, width - 1, 0, 1, height); // right column
213 
214         // now test along the main diagonal
215         for (int i = 0; i < height - 8; i++) {
216             checkSubImage(target, fullRaster, i, i, 8, 8);
217         }
218 
219         // now read the entire image
220         checkSubImage(target, fullRaster, 0, 0, width, height);
221     }
222 
223 }