Quick Start Guide#

This section will explain how to use decoder and encoder APIs in a few quick steps. The API details will be covered in the next section.

Note

Link to nvTIFF Samples: NVIDIA/CUDALibrarySamples

Please note that since the decoding and encoding of TIFF images are two fundamentally different problems the APIs for decoding and encoding are also different and independent.

nvTIFF Decode#

The library reads the file from disk and loads the image data to device memory.

  1. Create instances of the following -

nvtiffStream_t - is used to parse the bitstream and store the bitstream metadata

nvtiffDecoder_t - is used to store the work buffers required for decode

cudaStream_t stream;
cudaStreamCreate(&stream);

nvtiffStream_t nvtiff_stream;
nvtiffDecoder_t nvtiff_decoder;
nvtiffDecoderCreateSimple(&nvtiff_decoder, stream);
  1. Use the nvtiffStreamOpenFromFile API to open and parse the TIFF file from disk.

// fname is the TIFF file name
nvtiffStatus_t status = nvtiffStreamOpenFromFile(fname, &nvtiff_stream);
if (status != NVTIFF_STATUS_SUCCESS) {
    // report the error and stop: nvtiff_stream is not valid to use on failure
    return status;
}
  1. Extract the tiff file meta data.

// Images are addressed by IFD offset, not a dense index. Enumerate the offsets on the main
// chain starting from the header's first IFD and following the chain to the NVTIFF_NO_IMAGE
// sentinel.
nvtiffStreamHeader_t header = {};
nvtiffStreamGetHeader(nvtiff_stream, &header);

std::vector<size_t> ifd_offsets;
for (size_t off = header.first_ifd_offset; off != NVTIFF_NO_IMAGE; ) {
    ifd_offsets.push_back(off);
    if (nvtiffStreamGetNextIFDOffset(nvtiff_stream, off, &off) != NVTIFF_STATUS_SUCCESS) break;
}

std::vector<nvtiffImageInfo_t> image_info(ifd_offsets.size());
for (size_t i = 0; i < ifd_offsets.size(); i++) {
    nvtiffStreamGetImageInfo(nvtiff_stream, ifd_offsets[i], &image_info[i]);
}
  1. Allocate decode output on device.

// Allocate one device buffer per image and describe each as a one-plane nvtiffImage_t. The
// descriptor array has one element per region to be decoded; here we decode every image, so it
// has one element per enumerated offset.
const uint32_t num_images = (uint32_t)ifd_offsets.size();
std::vector<void*> image_out(num_images, nullptr);
std::vector<size_t> pitches(num_images, 0);
std::vector<nvtiffImage_t> images(num_images);
for (uint32_t i = 0; i < num_images; i++) {
    const size_t image_size = (size_t)image_info[i].image_width *
        image_info[i].image_height * (image_info[i].bits_per_pixel / 8);
    CHECK_CUDA(cudaMalloc(&image_out[i], image_size));

    images[i].plane_data        = &image_out[i];
    images[i].plane_pitch_bytes = &pitches[i]; // 0 lets the library use a tightly packed pitch
    images[i].num_planes        = 1;
}
  1. Create decode parameters and call nvtiffDecode. A request is one or more regions configured on a nvtiffDecodeParams_t; a freshly created params object decodes the first full image. Use nvtiffDecodeParamsSetRegions to target a specific IFD offset (and optionally an ROI). A decoder runs one decode at a time, so synchronize before reusing it for the next image.

 nvtiffDecodeParams_t params;
 nvtiffDecodeParamsCreate(&params);

 for (uint32_t i = 0; i < num_images; i++) {
     // One full-image region at this IFD offset (width == height == 0 means the whole image).
     nvtiffDecodeRegion_t region = {};
     region.ifd_offset = ifd_offsets[i];
     nvtiffDecodeParamsSetRegions(params, &region, 1);

     // Optional: dry-run the request (and validate the output descriptor) before submitting work.
     if (nvtiffDecodeCheckSupported(nvtiff_stream, nvtiff_decoder, params, &images[i]) != NVTIFF_STATUS_SUCCESS) {
         continue; // image/region is not decodable (or not batch-compatible); skip it
     }

     nvtiffStatus_t status = nvtiffDecode(nvtiff_stream, nvtiff_decoder, params, &images[i], stream);
     cudaStreamSynchronize(stream);
     // cudaStreamSynchronize is required after every nvtiffDecode call, even if status is not
     // NVTIFF_STATUS_SUCCESS: the decoder may have submitted work before returning an error.
     if (status != NVTIFF_STATUS_SUCCESS) {
         // report the error and stop issuing work on this decoder
         break;
     }
 }

 nvtiffDecodeParamsDestroy(params);

Multiple regions can also be decoded together in a single :code:`nvtiffDecode` call by passing an
array of regions to :code:`nvtiffDecodeParamsSetRegions` and an :code:`nvtiffImage_t` array with
one descriptor per region. A multi-region request decodes as one batch when the regions are
compatible; otherwise it is rejected with :code:`NVTIFF_STATUS_BATCH_INCOMPATIBLE`.
  1. Once all images are decoded, release the nvTIFF library resources by calling the corresponding destroy APIs.

nvTIFF Encode#

Set image info#

Other image info parameters can be set, but the following are absolutely necessary.

nvtiffImageInfo_t info = {};
info.image_width = 1920;
info.image_height = 1080;
info.samples_per_pixel = 3;
info.bits_per_sample[0] = info.bits_per_sample[1] = info.bits_per_sample[2] = 8;
info.photometric_int = NVTIFF_PHOTOMETRIC_UNKNOWN;

Basic encode flow#

// Create encoder
nvtiffEncoder_t encoder;
nvtiffEncoderCreate(&encoder, nullptr, nullptr, stream);
// Create params
nvtiffEncodeParams_t params;
nvtiffEncodeParamsCreate(&params);
// Prepare image info (see section nvTIFF Decode above)
nvtiffStream_t tiff_stream;
nvtiffStreamOpenFromFile("input.tiff", &tiff_stream);
nvtiffImageInfo_t img_info = {};
nvtiffStreamGetImageInfo(tiff_stream, 0, &img_info);
img_info.compression = NVTIFF_COMPRESSION_LZW;
nvtiffEncodeParamsSetImageInfo(params, &img_info);

// Optional: choose strip or tile geometry. By default, JPEG uses one full-height strip;
// None and LZW keep the existing approximately 8 KiB strip default.
nvtiffImageGeometry_t geometry = {};
geometry.type = NVTIFF_IMAGE_STRIPED;
geometry.strile_width = img_info.image_width;
geometry.strile_height = 10;
nvtiffEncodeParamsSetImageGeometry(params, &geometry);

// To write tiled output instead, use positive tile dimensions that are multiples of 16.
// geometry.type = NVTIFF_IMAGE_TILED;
// geometry.strile_width = 256;
// geometry.strile_height = 256;
// nvtiffEncodeParamsSetImageGeometry(params, &geometry);

nvtiffEncodeParamsSetInputs(params, images, num_images);
// Encode
nvtiffEncode(encoder, &params, 1, stream);
nvtiffEncodeFinalize(encoder, &params, 1, stream);
// Write output
nvtiffWriteTiffFile(encoder, &params, 1, "outFile.tif", stream);
// Clean up
nvtiffEncodeParamsDestroy(params, stream);
nvtiffEncoderDestroy(encoder, stream);

JPEG encode options#

To encode JPEG-compressed TIFF output, set img_info.compression to NVTIFF_COMPRESSION_JPEG before calling nvtiffEncodeParamsSetImageInfo(). JPEG encoding requires nvJPEG and supports unsigned 8-bit grayscale or RGB input.

JPEG options are optional. If nvtiffEncodeParamsSetJpegOptions() is not called, nvTIFF uses quality 90, optimized Huffman disabled, and 4:2:0 chroma subsampling for RGB input. To override those defaults:

nvtiffImageInfo_t img_info = {};
img_info.image_width = width;
img_info.image_height = height;
img_info.samples_per_pixel = 3;
img_info.bits_per_sample[0] = 8;
img_info.bits_per_sample[1] = 8;
img_info.bits_per_sample[2] = 8;
img_info.bits_per_pixel = 24;
img_info.sample_format[0] = NVTIFF_SAMPLEFORMAT_UINT;
img_info.sample_format[1] = NVTIFF_SAMPLEFORMAT_UINT;
img_info.sample_format[2] = NVTIFF_SAMPLEFORMAT_UINT;
img_info.photometric_int = NVTIFF_PHOTOMETRIC_RGB;
img_info.planar_config = NVTIFF_PLANARCONFIG_CONTIG;
img_info.compression = NVTIFF_COMPRESSION_JPEG;
nvtiffEncodeParamsSetImageInfo(params, &img_info);

nvtiffJpegEncodeOptions_t jpeg_options = {};
jpeg_options.quality = 90; // 0 also selects the default quality.
jpeg_options.optimized_huffman = 0;
jpeg_options.chroma_subsampling = NVTIFF_JPEG_CHROMA_SUBSAMPLING_420;
nvtiffEncodeParamsSetJpegOptions(params, &jpeg_options);

nvtiffEncodeParamsSetInputs(params, images, num_images);
nvtiffEncode(encoder, &params, 1, stream);
nvtiffEncodeFinalize(encoder, &params, 1, stream);

The JPEG option setter may be called before or after nvtiffEncodeParamsSetImageInfo(). Zero-initialized options are valid and preserve the JPEG defaults. For RGB input, the available chroma subsampling values are NVTIFF_JPEG_CHROMA_SUBSAMPLING_444, NVTIFF_JPEG_CHROMA_SUBSAMPLING_422, and NVTIFF_JPEG_CHROMA_SUBSAMPLING_420. Grayscale JPEG always uses a single luminance channel.

By default, JPEG output uses one full-height strip, so the whole image is encoded as a single JPEG bitstream. None and LZW output keep the existing approximately 8 KiB strip default. If tiled or smaller striped JPEG output is required, call nvtiffEncodeParamsSetImageGeometry() with explicit strip or tile dimensions.

Write TIFF stream to a buffer#

nvtiffEncodeParamsSetImageInfo(params, &info);
nvtiffEncodeParamsSetInputs(params, imageOut_d, num_images);
nvtiffEncode(encoder, &params, 1, stream);
size_t metadata_size = 0, bitstream_size = 0;
nvtiffEncodeFinalize(encoder, &params, 1, stream);
// Get bitstream size to allocate a buffer, then write to that buffer
nvtiffGetBitstreamSize(encoder, &params, 1, &metadata_size, &bitstream_size));
std::vector<unsigned char> buf(metadata_size + bitstream_size);
nvtiffWriteTiffBuffer(encoder, &params, 1, buf.data(), buf.size(), stream);

Decode then encode#

nvtiffStream_t tiff_stream;
nvtiffStreamOpenFromFile(fname, &tiff_stream);
nvtiffDecoder_t decoder;
nvtiffDecoderCreate(&decoder, nullptr, nullptr, stream);

// Decode the first image.
nvtiffImageInfo_t info;
nvtiffStreamGetImageInfo(tiff_stream, 0, &info);

uint8_t* imageOut_d = nullptr;
const size_t imageSize = (size_t)info.image_height * info.image_width * (info.bits_per_pixel / 8);
cudaMalloc(&imageOut_d, imageSize);

void* plane  = imageOut_d;
size_t pitch = 0;
nvtiffImage_t image = {};
image.plane_data        = &plane;
image.plane_pitch_bytes = &pitch;
image.num_planes        = 1;

nvtiffDecodeParams_t decode_params;
nvtiffDecodeParamsCreate(&decode_params); // fresh params: first full image
nvtiffStatus_t decode_status = nvtiffDecode(tiff_stream, decoder, decode_params, &image, stream);
cudaStreamSynchronize(stream);
if (decode_status != NVTIFF_STATUS_SUCCESS) {
    // report the error
}

// Re-encode it.
nvtiffEncoder_t encoder;
nvtiffEncoderCreate(&encoder, nullptr, nullptr, stream);
nvtiffEncodeParams_t params;
nvtiffEncodeParamsCreate(&params);
nvtiffEncodeParamsSetImageInfo(params, &info);
nvtiffEncodeParamsSetInputs(params, &imageOut_d, 1);
nvtiffEncode(encoder, &params, 1, stream);

Geotiff#

const char* citation = "Generated by nvtiff|";
nvtiffEncodeParamsSetGeoKeyASCII(params, NVTIFF_GEOKEY_GT_CITATION, citation,
strlen(citation) + 1));
nvtiffEncodeParamsSetGeoKeySHORT(params, NVTIFF_GEOKEY_GT_MODEL_TYPE, 2, 1);
nvtiffEncodeParamsSetGeoKeySHORT(params, NVTIFF_GEOKEY_GEODETIC_CRS, 4326, 1);
double geotransform[6] = { ... };
double pixelScale[3] = {geotransform[1], std::abs(geotransform[5]), 0.0};
nvtiffEncodeParamsSetTag(params, NVTIFF_TAG_MODEL_PIXEL_SCALE, NVTIFF_TAG_TYPE_DOUBLE, pixelScale, 3);
double tiePoint[6] = {0.0, 0.0, 0.0, geotransform[0], geotransform[3], 0.0};
nvtiffEncodeParamsSetTag(params, NVTIFF_TAG_MODEL_TIE_POINT, NVTIFF_TAG_TYPE_DOUBLE, tiePoint, 6);

Tiff Decode / Encode Demo example#

The binary nvtiff_example provides a complete and detailed usage example for the encoding and decoding capabilities of the nvTIFF library.

Usage:
nvTiff_example [options] -f|--file <TIFF_FILE>

General options:

    -d DEVICE_ID
    --device DEVICE_ID
            Specifies the GPU to use for images decoding/encoding.
            Default: device 0 is used.

    -v
    --verbose
            Prints some information about the decoded TIFF file.

    -h
    --help
            Prints this help

Decoding options:

    -f TIFF_FILE
    --file TIFF_FILE
            Specifies the TIFF file to decode. The code supports both single and multi-image
            tiff files with the following limitations:
              * color space must be either Grayscale (PhotometricInterp.=1) or RGB (=2)
              * image data compressed with LZW (Compression=5) or uncompressed
              * pixel components stored in "chunky" format (RGB..., PlanarConfiguration=1)
                for RGB images
              * image data must be organized in Strips, not Tiles
              * pixels of RGB images must be represented with at most 4 components
              * each component must be represented exactly with:
              * 8 bits for LZW compressed images
              * 8, 16 or 32 bits for uncompressed images
              * all images in the file must have the same properties

    -b BEG_FRM
    --frame-beg BEG_FRM
            Specifies the image id in the input TIFF file to start decoding from.  The image
            id must be a value between 0 and the total number of images in the file minus 1.
            Values less than 0 are clamped to 0.
            Default: 0

    -e END_FRM
    --frame-end END_FRM
            Specifies the image id in the input TIFF file to stop  decoding  at  (included).
            The image id must be a value between 0 and the total number  of  images  in  the
            file minus 1.  Values greater than num_images-1  are  clamped  to  num_images-1.
            Default:  num_images-1.

    -m
    --memtype TYPE
            Specifies the type of memory used to hold  the  TIFF  file  content:  pinned  or
            pageable.  Pinned memory is used if 'p' is specified. Pageable memory is used if
            'r' is specified.  In case of pinned memory,  file  content  is  not  copied  to
            device memory before the decoding process (with a resulting performance  impact)
            unless the option -c is also specified (see below).
            Defualt: r (pageable)

    -c
    --copyh2d
            Specifies to copy the file data to device memory in case the -m option specifies
            to use pinned memory.  In case of pageable memory this  option  has  no  effect.
            Default: off.

    --decode-out NUM_OUT
            Enables the writing of selected images from the decoded  input  TIFF  file  into
            separate BMP files for inspection.  If no argument is  passed,  only  the  first
            image is written to disk,  otherwise  the  first  NUM_OUT  images  are  written.
            Output files are named outImage_0.bmp, outImage_1.bmp...
            Defualt: disabled.

Encoding options:

    -E
    --encode
            This option enables the encoding of the raster images obtained by  decoding  the
            input TIFF file. By default, each image is written as one strip, compressed with
            LZW and, optionally, written into an output TIFF file.
            Default: disabled.

    --encode-out
            Enables the writing of the compressed  images  to  an  output  TIFF  file named
            outFile.tif.
            This option is ignored if -E is not specified.
            Defualt: disabled.