Astro tools, Software

Converting DSLR RAW images into scientific FITS format. Part 2: working with LIBRAW

In the previous article I’m started to describe my Raw2Fits converter. It was a brief note about GTK user interface development.
Now I wanna talk about LibRaw library. This is a great library for reading RAW files obtained from digital photo cameras (CRW/CR2, NEF, RAF, DNG, and others).

Take a look at the LibRaw project web page and official documentation.

I highly recommend to always use latest stable release from this site instead of precompiled version from Linux distros.

Getting and installing library

Download and build latest STABLE version of the LibRaw from the project downloads page.

For example: LibRaw-0.18.11.tar.gz as for 15 May 2018.

# Download archive
$ wget https://www.libraw.org/data/LibRaw-0.18.11.tar.gz

# Extract
$ tar -xvf LibRaw-0.18.11.tar.gz 

# Remove downloaded file
$ rm LibRaw-0.18.11.tar.gz

# Go to extracted directory
$ cd LibRaw-0.18.11

# Run configure script
$ ./configure

After compilation this library will be installed in /usr/local/lib. If you want to use standard /usr/lib run configure script as following:

./configure --prefix=/usr

But don’t forget to remove previous/repository installation of the library!

Now build and install:

$ make
$ sudo make install

This steps should be done without any errors. In case of problems check out the error messages and fix your environment, you can lack some required package, for example.

To use LibRaw, add the following parameters to the compiler call (when building your own projects):

  • Path to include-files: -I/usr/local/include
  • Path to libraries: -L/usr/local/lib
  • Library: -lraw (ordinary version) or -lraw_r (thread-safe version).

If you configured library for another path (/usr/lib) – correct this params to your own.

Library usage

There are two API’s available – C++ and pure C. They are they are quite similar by concepts and functions/methods naming. Of course C++ version uses objects and methods but C only structs and function calls.

In my project I’m using C version of the API so in following examples we will use this language. It very easy to switch to the C++ api after learning basic concept of this library.

Everything we need is available only in one header file so don’t forget to include library in the beginning of your source file.

#include <libraw/libraw.h>

Library is accessed through libraw_data_t structure which holds all library internals structures during program execution.

This structure must be initialized before usage:

libraw_data_t *rawdata = libraw_init(0);

And closed in the end:

libraw_close(rawdata);

Initialization method returns NULL in case of error, pointer to the structure in all other cases. Some LibRaw methods may return integer error codes. You can convert this codes to the human readable strings using libraw_strerror() method.

Now we ready to load our RAW image from the file (picture.cr2 for example).

int err = libraw_open_file(rawdata, "picture.cr2");

if (err != LIBRAW_SUCCESS) {
    printf("libraw_open_file failed! Error: %s\n", libraw_strerror(err));
    libraw_close(rawdata);
}

Here you can see proper errors checking and usage of the libraw_strerror().

libraw_open_file is very fast since that this methods only access file without any data loading and decoding.

After successful opening of the file we can unpack RAW data to get access to the image meta tags and actual image data.

err = libraw_unpack(rawdata);

if (err != LIBRAW_SUCCESS) {
    printf("libraw_unpack! Error: %s\n", libraw_strerror(err));
    libraw_close(rawdata);
}

This operation may requires some time depending on the file size and your computer resources.

We can get some visualisation of the unpacking progress by setting progress handlers.

int decoder_progress_callback(void *arg, enum LibRaw_progress p,int iteration, int expected)
{
    printf("%s, step %i/%i\n", libraw_strprogress(p), iteration + 1, expected);
    return 0;
}

void *arg;
libraw_set_progress_handler(rawdata, &decoder_progress_callback, arg);

Arg is a pointer to the custom data, you can put here everything you want to use in decoder_progress_callback.

This callback may terminate current image processing by returning of non-zero value. In such case all processing will be cancelled immediately.

decoder_progress_callback must be called before actual data unpacking and decoding.

Unpacked images must be released in the end of the processing.

libraw_recycle(rawdata);
Accesing meta information

At this point we can get some useful information from the our RAW file: camera model, lenses, exposure and other params. Note that depending on camera vendor/model some of the data may not be available.

There are few data structures available in libraw_data_t:

  • libraw_iparams_t – The structure describes the main image parameters retrieved from the RAW file.
    Most useful fields of this structure:

char make[64] – Camera manufacturer.
char model[64] – Camera model.
char software[64] – Softwary name/version
unsigned raw_count – Number of RAW images in file

  • libraw_image_sizes_t sizes -The structure describes the geometrical parameters of the image.
    Fields of this structure:ushort raw_height, raw_width – Full size of RAW image (including the frame) in pixels
    ushort height, width – Size of visible (“meaningful”) part of the image (without the frame)
    ushort top_margin, left_margin – Coordinates of the top left corner of the frame (the second corner is calculated from the full size of the image and size of its visible part)
    ushort iheight, iwidth – Size of the output image (may differ from height/width for cameras that require image rotation or have non-square pixels)
    double pixel_aspect – Pixel width/height ratio
    int flip – Image orientation (0 if does not require rotation; 3 if requires 180-deg rotation; 5 if 90 deg counterclockwise, 6 if 90 deg clockwise)
  • libraw_lensinfo_t lens – The structure describes lens used for the shot. Read the official documentation about this huge strusture.
  • libraw_imgother_t other – Data structure for information purposes.
    Fields:float iso_speed – ISO sensitivity
    float shutter – Shutter speed
    float aperture – Aperture
    float focal_len – Focal length
    time_t timestamp – Date of shooting
    unsigned shot_order – Serial number of image
    unsigned gpsdata[32] – GPS data (unparsed block, to write to output as is)
    libraw_gps_info_t parsed_gps – Parsed GPS-data: longtitude/lattitude/altitude and time stamp
    char desc[512] – Image description
    char artist[64] – Author of image
    float FlashEC – Flash exposure compensation

Reading meta information.

// Camera & user information
printf("Camera vendor: %s\n", rawdata->idata.make);
printf("Camera model: %s\n", rawdata->idata.model);
printf("Camera owner: %s\n", rawdata->other.artist);

// Date of shooting
char time_buf[25];
struct tm *utc_tm = gmtime(&rawdata->other.timestamp);
strftime(time_buf, 25, "%Y-%m-%dT%H:%M:%S", utc_tm);
printf("Date of shooting: %s\n", time_buf);

// Shooting params
printf("Exposure: %d\n", rawdata->other.shutter);
printf("Aperture: %d\n", rawdata->other.aperture);
Getting image

You can get raw image buffer from the library to do all post processing by yourself. But in this case you have to deal with Bayer RGB filters.

Bayer color filter array is a popular format for digital acquisition of color images. The pattern of the color filters is shown below. Half of the total number of pixels are green (G), while a quarter of the total number is assigned to both red (R) and blue (B).

In order to obtain this color information, the color image sensor is covered with either a red, a green, or a blue filter, in a repeating pattern. This pattern, or sequence, of filters can vary, but the widely adopted β€œBayer” pattern, which was invented at Kodak, is a repeating 2×2 arrangement.

Note that different cameras may have different pattern of this filter and even non-square pixels. You should know all the information about your camera sensor if you want to process this image manually.

To convert an image from the bayer format to an RGB per pixel format, we need to interpolate the two missing color values in each pixel. Several standard interpolation methods (nearest neighbor, linear, cubic, cubic spline, etc.) is exists.

Getting raw buffer from the LibRaw you actually getting three buffers: RED, GREEN and BLUE. All buffers have same sizes (size of the image) but with “holes” in the place of other colors pixels.

Now you can apply some Bayer Interpolation algorithm to mix this image matrices get proper RGB image. You still have to do with three separate buffers for colors, but now every point this buffers is filled with proper mixed value, without any “holes”. Also at this point can be applied several corrections to the image: white balance, brightness and so on.

Likely LibRaw can do for us everything of this! πŸ™‚

This is separate subset of API for simulating dcraw functionality.

Getting RGB image is required few simple steps.

err = libraw_raw2image(rawdata);

if (err != LIBRAW_SUCCESS) {
    printf("libraw_raw2image failed! Error: %s", libraw_strerror(err));
    libraw_recycle(rawdata);
    libraw_close(rawdata);
}

err = libraw_dcraw_process(rawdata);

if (err != LIBRAW_SUCCESS) {
    printf("libraw_dcraw_process failed! Error: %s", libraw_strerror(err));
    libraw_free_image(rawdata);
    libraw_recycle(rawdata);
    libraw_close(rawdata);
}

libraw_processed_image_t *proc_img = libraw_dcraw_make_mem_image(rawdata, &err);

libraw_free_image(rawdata);

if (!proc_img) {
    printf("libraw_dcraw_make_mem_image failed! Error: %s", libraw_strerror(err));
    libraw_recycle(rawdata);
    libraw_close(rawdata);
}

// At this point we got processed image in proc_img
// This image can be accessed by proc_img->data field

//////

printf("Image decoded, size = %ix%i, bits = %i, colors = %i\n",
        proc_img->width, proc_img->height, proc_img->bits, proc_img->colors);

libraw_recycle(rawdata);
libraw_close(rawdata);

libraw_dcraw_clear_mem(proc_img);

In this example you can find new data structure – libraw_processed_image_t. This structure is holding processed image.

RGB image can be accessed by proc_img->data field.

This field is just a byte array holding every pixels of the image in the following pattern: RGBRGBRGBRGB…. where every R, G and B is a pixel luminosity value after debayering process.

So to read every image pixel RGB value you can do something like this:

int i, k = 0;
long rgb[3];

for (i = 0; i < proc_img->width * proc_img->height; i++) {
    rgb[0] = proc_img->data[k];
    rgb[1] = proc_img->data[k + 1];
    rgb[2] = proc_img->data[k + 2];

    /////

    k += 3;
}

Here on every cycle iteration we get red value in rgb[0], green value in rgb[1] and blue value in rgb[2].

Now we can do with this values everything we want, copy to separate buffers for future processing or mix up to grayscale.

Conversion to grayscale is also very simple:

long gray_pixel = (rgb[0] + rgb[1] + rgb[2]) / 3;

In my Raw2Fits converter I’m using this pixel values to store image in to FITS file. In the next article I will describe this in details. But you can discover this source file.

Complete example

In this article I can show you another example. Let’s convert RAW image in to standard JPEG!

We need one more library – libjpeg.

Following example contains all code from the above and usage of the libjpeg to store compressed RAW image.

#include <stdio.h>
#include <time.h>
#include <libraw/libraw.h>
#include <jpeglib.h>

int decoder_progress_callback(void *arg, enum LibRaw_progress p,int iteration, int expected)
{
    printf("%s, step %i/%i\n", libraw_strprogress(p), iteration + 1, expected);
    return 0; 
}

int main(int argc, char* argv[])
{
    if (argc != 3) {
        printf("Usage: ./raw_test in.raw (cr2, nef, etc) out.jpg\n");
        return -1;
    }

    libraw_data_t *rawdata = libraw_init(0);

    int err = libraw_open_file(rawdata, argv[1]);

    if (err != LIBRAW_SUCCESS) { 
        printf("libraw_open_file failed! Error: %s\n", libraw_strerror(err));
        libraw_close(rawdata);
        return -1;
    }

    libraw_set_progress_handler(rawdata, &decoder_progress_callback, NULL);
    
    err = libraw_unpack(rawdata);

    if (err != LIBRAW_SUCCESS) {
        printf("libraw_unpack! Error: %s\n", libraw_strerror(err));
        libraw_close(rawdata);
        return -1;
    }

    // Camera & user information 
    printf("Camera vendor: %s\n", rawdata->idata.make);
    printf("Camera model: %s\n", rawdata->idata.model);
    printf("Camera owner: %s\n", rawdata->other.artist);

    // Date of shooting
    char time_buf[25]; 
    struct tm *utc_tm = gmtime(&rawdata->other.timestamp); 
    strftime(time_buf, 25, "%Y-%m-%dT%H:%M:%S", utc_tm);
    printf("Date of shooting: %s\n", time_buf);

    // Shooting params
    printf("Exposure: %d\n", rawdata->other.shutter);
    printf("Aperture: %d\n", rawdata->other.aperture);

    err = libraw_raw2image(rawdata);

    if (err != LIBRAW_SUCCESS) {
        printf("libraw_raw2image failed! Error: %s", libraw_strerror(err));
        libraw_recycle(rawdata);
        libraw_close(rawdata);
        return -1;
    }

    err = libraw_dcraw_process(rawdata);

    if (err != LIBRAW_SUCCESS) {
        printf("libraw_dcraw_process failed! Error: %s", libraw_strerror(err));
        libraw_free_image(rawdata);
        libraw_recycle(rawdata);
        libraw_close(rawdata);
        return -1;
    }

    libraw_processed_image_t *proc_img = libraw_dcraw_make_mem_image(rawdata, &err);

    libraw_free_image(rawdata);

    if (!proc_img) {
        printf("libraw_dcraw_make_mem_image failed! Error: %s", libraw_strerror(err));
        libraw_recycle(rawdata);
        libraw_close(rawdata);
        return -1;
    }

    printf("Image decoded, size = %ix%i, bits = %i, colors = %i\n", proc_img->width, proc_img->height, proc_img->bits, proc_img->colors);

    libraw_recycle(rawdata);
    libraw_close(rawdata);

    ////
    // Save decoded RAW image as JPEG file

    // open file for writing
    FILE *out_jpeg = fopen(argv[2], "wb");

    if (out_jpeg == NULL) {
        printf("Failed to open %s for writing\n", argv[2]);
    }

    // initialize libjpeg structures
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);

    // set output data stream
    jpeg_stdio_dest(&cinfo, out_jpeg);

    cinfo.image_width = proc_img->width;
    cinfo.image_height = proc_img->height;

    // set image params - 3 components, RGB
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;

    int quality = 85;

    // configure decoder
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, 1);
    jpeg_start_compress(&cinfo, 1);

    JSAMPROW row_pointer;
    int row_stride = cinfo.image_width * 3;

    // write jpeg rows
    while (cinfo.next_scanline < cinfo.image_height) {
        // get data from our proc_img->data buffer
        row_pointer = (JSAMPROW) &proc_img->data[cinfo.next_scanline * row_stride];
        jpeg_write_scanlines(&cinfo, &row_pointer, 1);
    }

    // cleanup
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);

    fclose(out_jpeg);

    ///

    return 0;
}

Build and run:

$ gcc main.c -I/usr/local/include -L/usr/local/lib -lraw -ljpeg -std=c99 -o raw_test

$ ./raw_test RAW_CANON600D.CR2 test.jpg
Reading RAW data, step 1/2
Reading RAW data, step 2/2
Camera vendor: Canon
Camera model: EOS 600D
Camera owner: 
Date of shooting: 2015-03-27T01:57:57
Exposure: 0
Aperture: 0
Scaling colors, step 1/2
Scaling colors, step 2/2
Pre-interpolating, step 1/2
Pre-interpolating, step 2/2
Interpolating, step 1519/3458
Interpolating, step 2025/3458
Converting to RGB, step 1/2
Converting to RGB, step 2/2
Image decoded, size = 5202x3465, bits = 8, colors = 3

And results.

Original RAW file (opened with Darktable):

Generated test.jpg:

Additional image tuning

LibRaw allow us to configure some decoding params. We can disable interpolation, apply autobright and so on.

Let’s apply autobright to see how it works. Add following code before libraw_dcraw_process():

rawdata->params.no_auto_bright = 1;

Run code again and check result. Generated image should be darker and more natural.

Try to disable interpolation with “rawdata->params.no_interpolation = 1” and check the result πŸ™‚

Dealing with different LibRaw versions

Using different version of LibRaw you can found that some functionality is not implemented yet or working not properly. Using special library macroses we can check LibRaw version at compilation time and disable some code.

#if (LIBRAW_COMPILE_CHECK_VERSION_NOTLESS(0,17))
    rawdata->params.no_auto_bright = 1;
#else
    #pragma message ("LibRaw version is to old, unable to use image autobright")
#endif

 

Thanks for reading

Tagged , ,

1 thought on “Converting DSLR RAW images into scientific FITS format. Part 2: working with LIBRAW

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.