2

Suppose I manually created pixel values for more than 4 bands and I want to store them in a tiff file.

Those bands can be for R, G, B, temperature (values of temperature are not in the range of 0 to 255 thus I am using int instead of byte for pexils), etc. i.e. any information that can be taken from the satellite

Now I want to save those pixels to a tiff file. In java there is a BufferedImage class which has many types like: TYPE_4BYTE_ABGR, TYPE_BYTE_GRAY, etc. However none of them for multi bands more than 4 bands. There is TYPE_CUSTOM but when specifying it and trying to save the data to a Tiff file it gives you an exception because it is not supported for write operation (only for read operation i.e. it can read the file and set the type to TYPE_CUSTOM if it didn't understand the type but it can not write the file in a not understood type).

The below code worked for 3 bands even not properly (it doesn't show colored image and it looked like a distorted image with missing lines) but for more than 4 bands how can I do that?

    ImageOutputStream ios = ImageIO.createImageOutputStream(os);
    Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("tiff");
    ImageWriter writer = writers.next();
    writer.setOutput(ios);
        int index = 0;
        int[] pixels = new int[width*height*numberOfBands];
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                for (int k = 0; k < numberOfBands; k++) {
                    pixels[index++] = //any values;
                }
            }
        }

    DataBuffer dataBuffer = new DataBufferInt(pixels, pixels.length);

    // Create Raster
    WritableRaster writableRaster = Raster.createBandedRaster
        (dataBuffer, width, height,
         width,                      // scanlineStride
         new int[numberOfBands],             // bankIndices,
         new int[numberOfBands],             // bandOffsets,
         null);                     // location

    // Create the image
    BufferedImage bufferedImage = new BufferedImage
            (width, height, BufferedImage.TYPE_BYTE_RGB);
    bufferedImage.setData(writableRaster);
    IIOImage iioImage = new IIOImage(bufferedImage, null, null);
    ImageWriteParam param = writer.getDefaultWriteParam();
    writer.write(null, iioImage, param);

I am using GeoTools by the way

Edited: According to @iant I changed the code but it is giving blank transparent background only, even I kept the same number of bands i.e. 3 bands. @iant Could you check the code below.

    package examples;

    import java.awt.image.WritableRaster;
    import java.io.File;
    import java.io.IOException;

    import javax.media.jai.RasterFactory;

    import org.geotools.coverage.CoverageFactoryFinder;
    import org.geotools.coverage.grid.GridCoordinates2D;
    import org.geotools.coverage.grid.GridCoverage2D;
    import org.geotools.coverage.grid.GridCoverageFactory;
    import org.geotools.coverage.grid.GridEnvelope2D;
    import org.geotools.coverage.grid.GridGeometry2D;
    import org.geotools.coverage.grid.io.AbstractGridFormat;
    import org.geotools.coverage.grid.io.OverviewPolicy;
    import org.geotools.gce.geotiff.GeoTiffFormat;
    import org.geotools.gce.geotiff.GeoTiffReader;
    import org.opengis.coverage.grid.GridCoverageWriter;
    import org.opengis.parameter.GeneralParameterValue;
    import org.opengis.parameter.ParameterValue;

    public class CreateTiffImageTest2 {

    public static void main(String[] args) throws IOException {

    File file = new File("/home/mosab/Desktop/input/tif.tif");

    ParameterValue<OverviewPolicy> policy = AbstractGridFormat.OVERVIEW_POLICY.createValue();
    policy.setValue(OverviewPolicy.IGNORE);
    ParameterValue<String> gridsize = AbstractGridFormat.SUGGESTED_TILE_SIZE.createValue();
    ParameterValue<Boolean> useJaiRead = AbstractGridFormat.USE_JAI_IMAGEREAD.createValue();
    useJaiRead.setValue(true);

    GeoTiffReader geoTiffReader = new GeoTiffReader(file);
    GridCoverage2D cov = geoTiffReader.read(new GeneralParameterValue[] { policy, gridsize, useJaiRead });
    GridGeometry2D geometry = cov.getGridGeometry();
    GridEnvelope2D gridEnvelope = geometry.getGridRange2D();
    int w = (int) gridEnvelope.getWidth();
    int h = (int) gridEnvelope.getHeight();

    WritableRaster writableRaster = RasterFactory.createBandedRaster(java.awt.image.DataBuffer.TYPE_DOUBLE, w, h, 3,
            null);
    double[] data = new double[3];
    double[] dest = new double[3];
    for (int i = 0; i < w; i++) {
        for (int j = 0; j < h; j++) {
            GridCoordinates2D coord = new GridCoordinates2D(i, j);
            cov.evaluate(coord, dest);

            data[0] = dest[0];
            data[1] = dest[1];
            data[2] = dest[2];
            writableRaster.setPixel(i, j, data);
        }
        float perc = 100.0f * i / w;
        if (i % 100 == 0) {
            System.out.println("done " + perc);
        }
    }
    // Wrap the raster as a Coverage
    GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
    GridCoverage2D gc = factory.create("name", writableRaster, cov.getEnvelope());
    File out = new File("/home/mosab/Desktop/input/tifgen.tif");
    GeoTiffFormat format = new GeoTiffFormat();
    GridCoverageWriter writer = format.getWriter(out);
    try {
        writer.write(gc, null);
        writer.dispose();
    } catch (IllegalArgumentException | IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

}

}

Update 2:

import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.media.jai.RasterFactory;

import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridFormatFinder;
import org.geotools.factory.Hints;
import org.geotools.gce.geotiff.GeoTiffFormat;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.coverage.grid.GridCoverageWriter;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

public class Test2 {

public static void print(Object o) {
    System.out.println(o);
}

public static void main(String[] args)
        throws MismatchedDimensionException, NoSuchAuthorityCodeException, FactoryException, IOException {
    File out = new File("/home/mosab/Desktop/input/1.tif");
    BufferedImage img = ImageIO.read(out);

    // ColorModel colorModel = img.getColorModel(
    WritableRaster raster = img.getRaster();

    int w = img.getWidth();
    int h = img.getHeight();
    print("width = " + w);
    print("heigh = " + h);
    int numBands = raster.getNumBands();

    WritableRaster writableRaster = RasterFactory.createBandedRaster(java.awt.image.DataBuffer.TYPE_INT, w, h, 3,
            null);
    //as I said pixels are created manually but I used here pixels from an image to check the approach
    int[] data = new int[3];
    for (int i = 0; i < w; i++) {
        for (int j = 0; j < h; j++) {
            for (int k = 0; k < numBands; k++) {
                data[k] = raster.getSample(i, j, k);
            }
            writableRaster.setPixel(i, j, data);
        }
    }

    GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
    CoordinateReferenceSystem crs = CRS.decode("EPSG:27700");
    int llx = 500000;
    int lly = 105000;
    ReferencedEnvelope referencedEnvelope = new ReferencedEnvelope(llx, llx + (w * 10), lly, lly + (h * 10), crs);
    GridCoverage2D gc = factory.create("name", writableRaster, referencedEnvelope);

    AbstractGridFormat format = GridFormatFinder.findFormat(out);
    Hints hints = null;
    if (format instanceof GeoTiffFormat) {
        hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
    }

    File out1 = new File("/home/mosab/Desktop/input/tifgen.tif");
    GridCoverageWriter writer = format.getWriter(out1);
    try {
        writer.write(gc, null);
        writer.dispose();
    } catch (IllegalArgumentException | IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

}

Notice: I used int type and an array of size 3 because the original image has RGB bands.

Mosab Shaheen
  • 1,114
  • 10
  • 25
  • if you have such special demands you might consider having your own file format. – Piglet Jan 19 '17 at 09:12
  • I would work on getting it to work with 3 bands before trying 4 – Ian Turton Jan 19 '17 at 16:10
  • @Piglet it can be done using the TIff/Geotiff format, because it is intended for that. And by the way I mentioned earlier more than 4 bands, because in case of 4 some software will consider it ARGB i.e. +aplha . – Mosab Shaheen Jan 21 '17 at 09:48
  • @iant The problem here I am not able to find a type for BufferedImage to support more than 4 bands – Mosab Shaheen Jan 21 '17 at 09:50
  • that is why you need to use a writable raster which can have as many as you need. GeoTiff is technically limited to INT_MAX. – Ian Turton Jan 21 '17 at 11:19
  • It looks like you are picking up a worldimage writer not a geotiff writer (because 1.tif isn't a geotiff) - Set formats = GridFormatFinder.getAvailableFormats(); Hints hints = null; AbstractGridFormat format = null; Iterator itr = formats.iterator(); while (itr.hasNext()) { GridFormatFactorySpi fspi = itr.next(); AbstractGridFormat f = fspi.createFormat(); if (f instanceof GeoTiffFormat) { hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE); format = f; } } – Ian Turton Jan 23 '17 at 11:58
  • @iant Actually in the code, you are right it is showing WorldImage but using "gdalinfo" command-line tool it is showing "Driver: GTiff/GeoTIFF". However, the code snippet you provided made the code run without exceptions, but the generated image is only black color( through the whole image, in all the bands, and in many Image Reader Software like: Imag Viewer(Ubuntu), Picasa, Photoshop, Paint (windows & ubuntu), even QGIS & Google Drive) but only one software was able to read it as the original image which is IrfanView. – Mosab Shaheen Jan 24 '17 at 09:25
  • @iant Continued... This is the generated image: https://drive.google.com/file/d/0ByKaCojxzNa9TzN0V0VfV01YNDA/view?usp=sharing This is weird as the original image is readable by all software and the generated image is not readable by any software except one. Does that mean the geotools library is not saving in proper format? Even in the second image "tif.tif" (which I uploaded on google drive) generated exactly the same problem i.e. only black color through all Image Reader Software (including QGIS) except IfranView. So why is this happening? Are we missing something? – Mosab Shaheen Jan 24 '17 at 09:37
  • @iant Continued... This is the generated image of the second image i.e. "tif.tif" https://drive.google.com/file/d/0ByKaCojxzNa9WF9GTHItYnJkbGs/view?usp=sharing – Mosab Shaheen Jan 24 '17 at 09:45

1 Answers1

2

You need to create a WritableRaster using the other factory method which allows you to set the required data type and number of bands.

WritableRaster writableRaster = RasterFactory.createBandedRaster
        (java.awt.image.DataBuffer.TYPE_DOUBLE,width,height,4,null);
    double[] data = new double[4];
    double[] dest = new double[3];
    for(int i=0;i<width;i++) {
      for(int j=0;j<height;j++) {
//basically anything you like to create the bands
        GridCoordinates2D coord = new GridCoordinates2D(i, j);

//here I just grab the values of my base image and add them together
        cov.evaluate(coord, dest);

        data[0]=dest[0];
        data[1] = dest[1];
        data[2] = dest[2];
        data[3] = (dest[0]+dest[1]+dest[2]);
        // write them to the new raster
        writableRaster.setPixel(i, j, data);
      }
      float perc = 100.0f*i/width;
      if(i%100==0) {
        System.out.println("done "+perc);
      }
    }
    //Wrap the raster as a Coverage
    GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
    GridCoverage2D gc = factory.create("name", writableRaster, cov.getEnvelope());
    //write it out
    File out = new File(outFile);
    GridCoverageWriter writer = format.getWriter(out);
    try {
      writer.write(gc , null);
      writer.dispose();
    } catch (IllegalArgumentException | IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

UPDATE

If you take the following code:

public void makeTestRaster() throws MismatchedDimensionException, NoSuchAuthorityCodeException, FactoryException {
    int width = 1000;
    int height = 1000;

    WritableRaster writableRaster = RasterFactory.createBandedRaster(java.awt.image.DataBuffer.TYPE_DOUBLE, width,
        height, 4, null);
    double[] data = new double[4];

    for (int i = 0; i < width; i++) {
      for (int j = 0; j < height; j++) {
        data[0] = i * 100.0;
        data[1] = j * 100.0;
        data[2] = (width - i) * 100.0;
        data[3] = (height - j) * 100.0;
        System.out.println(i + "," + j + ":" + data[0] + " " + data[1] + " " + data[2] + " " + data[3] + " ");
        writableRaster.setPixel(i, j, data);
      }
      float perc = 100.0f * i / width;
      if (i % 100 == 0) {
        System.out.println("done " + perc);
      }
    }
    File out = new File("test.tif");
    GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
    CoordinateReferenceSystem crs = CRS.decode("EPSG:27700");
    int llx = 500000;
    int lly = 105000;
    ReferencedEnvelope referencedEnvelope = new ReferencedEnvelope(llx, llx + (width * 10), lly, lly + (height * 10),
        crs);
    GridCoverage2D gc = factory.create("name", writableRaster, referencedEnvelope);
    AbstractGridFormat format = GridFormatFinder.findFormat(out);
    Hints hints = null;
    if (format instanceof GeoTiffFormat) {
      hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
    }

    GridCoverageWriter writer = format.getWriter(out);
    try {
      writer.write(gc, null);
      writer.dispose();
    } catch (IllegalArgumentException | IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

It creates a 10 KM square raster (near my house). It has 4 bands which could represent anything you like, running gdalinfo on it gives the following information:

gdalinfo test.tif
Driver: GTiff/GeoTIFF
Files: test.tif
       test.tif.aux.xml
Size is 1000, 1000
Coordinate System is:
PROJCS["OSGB 1936 / British National Grid",
    GEOGCS["OSGB 1936",
        DATUM["OSGB_1936",
            SPHEROID["Airy 1830",6377563.396,299.3249646,
                AUTHORITY["EPSG","7001"]],
            TOWGS84[446.448,-125.157,542.06,0.15,0.247,0.842,-20.489],
            AUTHORITY["EPSG","6277"]],
        PRIMEM["Greenwich",0,
            AUTHORITY["EPSG","8901"]],
        UNIT["degree",0.0174532925199433,
            AUTHORITY["EPSG","9122"]],
        AUTHORITY["EPSG","4277"]],
    PROJECTION["Transverse_Mercator"],
    PARAMETER["latitude_of_origin",49],
    PARAMETER["central_meridian",-2],
    PARAMETER["scale_factor",0.9996012717],
    PARAMETER["false_easting",400000],
    PARAMETER["false_northing",-100000],
    UNIT["metre",1,
        AUTHORITY["EPSG","9001"]],
    AXIS["Easting",EAST],
    AXIS["Northing",NORTH],
    AUTHORITY["EPSG","27700"]]
Origin = (500000.000000000000000,115000.000000000000000)
Pixel Size = (10.000000000000000,-10.000000000000000)
Metadata:
  AREA_OR_POINT=Area
  TIFFTAG_RESOLUTIONUNIT=1 (unitless)
  TIFFTAG_XRESOLUTION=1
  TIFFTAG_YRESOLUTION=1
Image Structure Metadata:
  INTERLEAVE=PIXEL
Corner Coordinates:
Upper Left  (  500000.000,  115000.000) (  0d34'37.20"W, 50d55'30.82"N)
Lower Left  (  500000.000,  105000.000) (  0d34'47.05"W, 50d50' 7.16"N)
Upper Right (  510000.000,  115000.000) (  0d26' 5.12"W, 50d55'24.27"N)
Lower Right (  510000.000,  105000.000) (  0d26'15.95"W, 50d50' 0.62"N)
Center      (  505000.000,  110000.000) (  0d30'26.33"W, 50d52'45.79"N)
Band 1 Block=1000x8 Type=Float64, ColorInterp=Gray
  Min=0.000 Max=99900.000 
  Minimum=0.000, Maximum=99900.000, Mean=49950.000, StdDev=28867.499
  Metadata:
    STATISTICS_MAXIMUM=99900
    STATISTICS_MEAN=49950
    STATISTICS_MINIMUM=0
    STATISTICS_STDDEV=28867.499025721
Band 2 Block=1000x8 Type=Float64, ColorInterp=Undefined
  Min=0.000 Max=97500.000 
  Minimum=0.000, Maximum=97500.000, Mean=48750.000, StdDev=30378.926
  Metadata:
    STATISTICS_MAXIMUM=97500
    STATISTICS_MEAN=48750
    STATISTICS_MINIMUM=0
    STATISTICS_STDDEV=30378.926358031
Band 3 Block=1000x8 Type=Float64, ColorInterp=Undefined
  Min=0.000 Max=97402500.000 
  Minimum=0.000, Maximum=97402500.000, Mean=24350625.000, StdDev=22476916.605
  Metadata:
    STATISTICS_MAXIMUM=97402500
    STATISTICS_MEAN=24350625
    STATISTICS_MINIMUM=0
    STATISTICS_STDDEV=22476916.605084
Band 4 Block=1000x8 Type=Float64, ColorInterp=Undefined
  Min=2500.000 Max=100000.000 
  Minimum=2500.000, Maximum=100000.000, Mean=51250.000, StdDev=30378.926
  Metadata:
    STATISTICS_MAXIMUM=100000
    STATISTICS_MEAN=51249.999999999
    STATISTICS_MINIMUM=2500
    STATISTICS_STDDEV=30378.926358031

This clearly shows the 4 bands. Finally if you import it in to QGIS to view you are offered the chance to assign any of the 4 bands to the red, green & blue bands.

enter image description here

This produces a variety of images which are normally called false colour or you can opt for a grayscale of a single band if you are looking at physical measurements that don't happen to make sense when combined.

Ian Turton
  • 10,018
  • 1
  • 28
  • 47
  • Dear, in the code above there is the "cov" variable and you used it two times, first time in the origional image you took the pixels from: cov.evaluate(coord, dest); and second time in the generated image: GridCoverage2D gc = factory.create("name", writableRaster, cov.getEnvelope()); – Mosab Shaheen Jan 21 '17 at 14:34
  • but as I said before I don't want to depend on the original image I want to set the pixels "manually" as if there is no original image before. So I need the code to either create a separate "cov" variable, not depending on the original one, or trying different approach. So Could you rewrite the code to reflect that. – Mosab Shaheen Jan 21 '17 at 14:35
  • you can set your pixels any way you want, I happened to copy mine so I could check the output image was correct. You presumably know what the envelope of your output is so you can use that. – Ian Turton Jan 21 '17 at 14:37
  • Thanks I am trying it is giving me a blank transparent background only. Could you check the complete code I added above in the "Edit Post" – Mosab Shaheen Jan 21 '17 at 14:55
  • The code is ready to execute just run it and tell me please why it is giving only a blank transparent background( or if you code post the full code of yours to try it) – Mosab Shaheen Jan 21 '17 at 14:57
  • Your tiff will not be interpreted as rgba just because it has 4 bands – Ian Turton Jan 21 '17 at 17:52
  • The provided code above generated an image that's not readable by Image reader software except for QGIS which shows an image containing a range of colors. Anyway that's one good step to be able to store double data in a tiff file even if it is not readable by other software. However I am trying this approach on a Geotiff file that has 3 bands (RGB) and I am trying to generate the same file to check if this approach is giving the correct results, but now the generated image is both not readable and in QGIS not showing the original image(only showing only black color – Mosab Shaheen Jan 23 '17 at 10:10
  • GeoTiffs are rarely readable by image software like gimp etc. – Ian Turton Jan 23 '17 at 10:11
  • Continued... or an exception is thrown during generation of the image). I think this means the provided code has something not working. I want you to kindly check my new updated code above on either of these images https://drive.google.com/file/d/0ByKaCojxzNa9QkFrOTg0ellCUzQ/view?usp=sharing OR https://drive.google.com/file/d/0ByKaCojxzNa9MWxPTUJjZURHR1E/view?usp=sharing I prefer the first image (1.tif) as it is more clear to check the generated image. and to ensure that the generated image is the same as the original one. – Mosab Shaheen Jan 23 '17 at 10:14
  • Ok, kindly check my newly updated code on the images I provided to see that it is not giving the original image. The code I provided is ready to execute just run it, check the output image and kindly do the correction to the code to generate the original image – Mosab Shaheen Jan 23 '17 at 11:29