Skip to content

WriteNITF20C

anna-dodd edited this page Jun 3, 2015 · 3 revisions

Writing a NITF file from scratch, in C


When dealing with NITF, an increasingly more common need is the ability to construct one from scratch, and write it to disk. This article details the steps you should follow in order to do this yourself, in C. This article is written mainly to support 2.0 development efforts, but should apply to the 1.5 release as well. There is one work-around you'll need to add if you are using this example with 1.5 development.

First off, we want to create a Record, which is the container of the entire NITF "model".
    ...
    /* first, we need to create a new Record */
    nitf_Error error;
    nitf_Record *record = nitf_Record_construct(NITF_VER_21, &error);
    if (!record)
    {
        nitf_Error_print(&error, stderr, "While constructing record...");
        goto CATCH_ERROR;
    }
    ...


In addition, we'll want to populate the FileHeader with some custom field data.
    nitf_FileHeader *header = record->header;
   
    /* populate some fields */
    if (!nitf_Field_setString(header->fileHeader, "NITF", &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setString(header->fileVersion, "02.10", &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setUint32(header->complianceLevel, 3, &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setString(header->systemType, "BF01", &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setString(header->originStationID, "SF.net", &error))
        goto CATCH_ERROR;
    /* fake the date */
    if (!nitf_Field_setString(header->fileDateTime, "20080812000000", &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setString(header->fileTitle, title, &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setString(header->classification, "U", &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setUint32(header->encrypted, 0, &error))
        goto CATCH_ERROR;
    ...


There are other fields to set, (as well as TRE Extension segments), but we'll keep it simple for this example. Also, we could just stop here. A Record with just a FileHeader is a valid NITF... but it doesn't really mean much.
In our next step, let's add an image to the NITF.

Next, let's add a new ImageSegment to the Record; we'll also update some fields of the subheader.
    ...
    nitf_ImageSegment* segment = nitf_Record_newImageSegment(record, &error);
    if (!segment)
        goto CATCH_ERROR;

    /* grab the subheader */
    nitf_ImageSubheader *imsubheader = segment->subheader;

    /* populate some fields */
    if (!nitf_Field_setString(imsubheader->filePartType, "IM", &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setString(imsubheader->imageId, "NITRO-TEST", &error))
        goto CATCH_ERROR;
    /* fake the date */
    if (!nitf_Field_setString(imsubheader->imageDateAndTime, "20080812000000", &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setString(imsubheader->imageSecurityClass, "U", &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setUint32(imsubheader->encrypted, 0, &error))
        goto CATCH_ERROR;
   
    /* for fun, let's add a comment */
    if (!nitf_ImageSubheader_insertImageComment(imsubheader, "NITF generated by NITRO",     0, &error))
        goto CATCH_ERROR;

    /* uncompressed image */
    if (!nitf_Field_setString(imsubheader->imageCompression, "NC", &error))
        goto CATCH_ERROR;
   
    /* some more fields... */
    if (!nitf_Field_setString(imsubheader->imageMagnification, "1.00", &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setUint32(imsubheader->imageDisplayLevel, 1, &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setUint32(imsubheader->imageAttachmentLevel, 0, &error))
        goto CATCH_ERROR;
    if (!nitf_Field_setUint32(imsubheader->imageLocation, 0, &error))
        goto CATCH_ERROR;
    ...


At this point, all we've done is add an ImageSegment to the record, along with fairly uncomplicated subheader data. It is now that we need to decide what we are going to put into this segment.
In the full example in subversion, I just use an image from a static memory buffer. You can also read your image
data from file.

So, for this example, assume you have a 3-band RGB image already in memory. The memory is assumed to be in band-interleaved by pixel (so, the data is organized as such: B1P1, B2P1, B3P1, etc.).

Let's construct the band information, and tell the ImageSubheader how we want the pixel information handled.
    ...
    /* somewhere, define what the band representations are.. for this example, RGB */
    static const char* RGB[] = {"R", "G", "B"};
   
    ...
   
    /* create a BandInfo buffer - one for each band */
    bands = (nitf_BandInfo **) NITF_MALLOC(sizeof(nitf_BandInfo *) * 3);
    if (bands == NULL)
    {
        nitf_Error_init(&error, NITF_STRERROR(NITF_ERRNO),
                        NITF_CTXT, NITF_ERR_MEMORY);
        goto CATCH_ERROR;
    }

    /* you may not want the number of band hard-coded like this example */
    for (i = 0; i < 3; ++i)
    {
        bands[i] = nitf_BandInfo_construct(&error);
        if (!bands[i])
            goto CATCH_ERROR;

        if (!nitf_BandInfo_init(bands[i],
                                RGB[i],   /* The band representation, Nth band */
                                " ",      /* The band subcategory */
                                "N",      /* The band filter condition */
                                "   ",    /* The band standard image filter code */
                                0,        /* The number of look-up tables */
                                0,        /* The number of entries/LUT */
                                NULL,     /* The look-up tables */
                                &error))
            goto CATCH_ERROR;
    }

    /* set the pixel information */
    if (!nitf_ImageSubheader_setPixelInformation(header,    /* header */
                                                 "INT",     /* Pixel value type */
                                                 8,         /* Number of bits/pixel */
                                                 8,         /* Actual number of bits/pixel */
                                                 "R",       /* Pixel justification */
                                                 "RGB",     /* Image representation */
                                                 "VIS",     /* Image category */
                                                 3,         /* Number of bands */
                                                 bands,     /* Band information object list */
                                                 &error))
        goto CATCH_ERROR;
    ....


Note: There was a known bug in 1.5 that has been fixed for 2.0. There is a work-around that you will need to do if developing with version 1.5. For 1.5 users, before calling nitf_ImageSubheader_setPixelInformation, call:
nitf_Field_setUint32(header->numImageBands, 1, &error);


Getting back to our example, the next step is to set the blocking info. For this example, we'll just create one block, and interleave the data by pixel.

How to set the blocking info.
    ...
    /* set the blocking info */
    if (!nitf_ImageSubheader_setBlocking(header,
                                         NITRO_IMAGE.height, /*!< The number of rows */
                                         NITRO_IMAGE.width,  /*!< The number of columns */
                                         NITRO_IMAGE.height, /*!< The number of rows/block */
                                         NITRO_IMAGE.width,  /*!< The number of columns/block */
                                         "P",                /*!< Image mode */
                                         &error))
        goto CATCH_ERROR;
    ...


Okay, so now you have your ImageSegment all setup how you want it written out. The only thing you have to do now is attach the actual image data and then write it! Let's go ahead and do that.

Set up a Writer and output IOHandle.
    ...
    /* create the IOHandle */
    out = nitf_IOHandle_create(filename, NITF_ACCESS_WRITEONLY,
                               NITF_CREATE, &error);

    /* make sure it was ok */
    if (NITF_INVALID_HANDLE(out))
        goto CATCH_ERROR;

    writer = nitf_Writer_construct(&error);
    if (!writer)
        goto CATCH_ERROR;
   
    /* prepare the writer to write this record */
    if (!nitf_Writer_prepare(writer, record, out, &error))
        goto CATCH_ERROR;
    ...


Now, for each ImageSegment you have added to the Record, you'll need to create a new ImageWriter. In this example, we just added 1 image, so this should be easy enough.
In addition, since this is a 3-band image, we will have to setup 3 BandSources.

Adding an ImageWriter and attaching the image data.
     ...
     /* get a new ImageWriter for the 1st image (index 0) */
    imageWriter = nitf_Writer_newImageWriter(writer, 0, &error);
    if (!imageWriter)
        goto CATCH_ERROR;

    /* create an ImageSource for our embedded image */
    imageSource = nitf_ImageSource_construct(&error);
    if (!imageSource)
        goto CATCH_ERROR;

    /* make one bandSource per band */
    for (i = 0; i < 3; ++i)
    {
        nitf_BandSource *bandSource = nitf_MemorySource_construct(
                NITRO_IMAGE.data, NITRO_IMAGE.width * NITRO_IMAGE.height,
                i, 1, 2, &error);
        if (!bandSource)
            goto CATCH_ERROR;

        /* attach the band to the image */
        if (!nitf_ImageSource_addBand(imageSource, bandSource, &error))
            goto CATCH_ERROR;
    }
     ...


Finally, all you need to do is attach the ImageSource to the ImageWriter, and then write it!

    ...
    /* attach the ImageSource to the writer */
    if (!nitf_ImageWriter_attachSource(imageWriter, imageSource, &error))
        goto CATCH_ERROR;

    /* finally, write it! */
    if (!nitf_Writer_write(writer, &error))
    {
        nitf_Error_print(&error, stderr, "Error writing up write");
        goto CATCH_ERROR;
    }

    /* cleanup */
    nitf_IOHandle_close(out);
    nitf_Writer_destruct(&writer);
    ...


As always, make sure to do proper cleanup along the way. The only objects that need to be cleaned up by you is the top level Record object, and the "Actor" objects (Reader, Writer).

The full source for this example can be found here: https://github.com/mdaus/nitro

Thanks!
The NITRO Team
Clone this wiki locally