Spaceknow Image (SKI)

SKI is a Spaceknow’s proprietary binary file format used for aerial and satellite images. Most visual data inside SK platform are handled in this file format.

SKI is a file-format and a python library. SKI supports multiple bands (i.e. channels). For example each band can represent a single part of electromagnetic spectrum (e.g. near-infrared). A band is a 2D matrix of integers. Each band in a single SKI can have a different resolution (number of rows and columns) and bit-depth. 8, 16, 32 and 64 bits per pixel per band are supported.

TODO: a visual example of an image with two bands, with different resolutions and different bit depths

Binary

SKI is gzipped tar archive with band files, info and meta JSON files.

Bands

Bands are stored into separate files with names xxxxx.skb where x is index of the band (e.g. 00000.skb). First two bytes of the file are an unsigned integer whose value is equal to bit-depth of the band. Next four bytes represent a number of columns and rows. The rest of the file is row-major ordered data of the band.

Pixel values could be reconstructed with the following formula:

\(p_{r, c} = (p_{r - 1, c} + b_{r, c}) \mod 2^k\),

where \(p_{r, c}\) is the pixel value at the rth row and cth column, \(b_{r, c}\) is the value from a stored matrix at the rth row and cth column and k is the bit depth of the band (maximum value + 1). -1th row is defined as a row full of 0s, therefore, the encoded values of the first row are equal to real pixel values.

When the binary is constructed and inverse formula is used.

The following is an hexadecimal example of a band with 8 bits per pixel, one column and two rows. Value in the first row is 250 and 200 in second.

00 08 00 00 00 01 00 00 00 02 FA CE

Info

Every SKI contains info.json file which is UTF-8 encoded JSON serialized data with information about the SKI. It contains a list of band names (band can have multiple names) and SKI version.

Info file has this format:

{
    "bands": [
        {
            "names": ["{band-name}"]
        }
    ],
    "version": "{SKI version}"
}

Example of an RGB image:

{
    "bands": [
        {
            "names": ["r", "red"]
        },
        {
            "names": ["g", "green"]
        },
        {
            "names": ["b", "blue"]
        }
    ],
    "version": "7"
}

Meta

An optional file meta.json may be present in the SKI archive. This file contains arbitrary JSON data set by the creator of the SKI. The file is UTF-8 encoded JSON serialized data.

Auxiliary Files

An arbitrary set of auxiliary files may be present in the aux/ sub-folder of an SKI archive. This is a universal way to transfer additional data from SKI producers to consumers.

No assumptions are made on the content of these files, it can be e.g. JSON, XML, binary file etc.

Usage

SKI library has several tools for loading, storing and manipulating SKI images.

To load a SKI image do the following

from sk.tools.ski.image import Ski

ski_image = Ski.load('~/an_image.ski')

Create an image from 2D numpy arrays - band_a and band_b

bands = [band_a, band_b]
band_names = [['a', 'band_a'], ['b', 'band_b']]
meta = {'company': 'Spaceknow, Inc.'}
ski_image = Ski(bands, band_names, meta=None)
ski_image has two bands constructed from numpy arrays. Both bands have two
names.

To store the image to ~/another_image.ski, do

ski_image.save('~/another_image.ski')

To get band_b as a 2D numpy array

band_b_numpy = ski_image.get_by_name('band_b')

SKI library can combine multiple bands into a single 3D numpy array. It is possible only if all affected bands are of the same resolution and bit depth. The following code will combine band_a and band_b into a numpy array with indexing [band][row][column]

band_row_column = ski_image.get_by_names_3d(['band_a', 'band_b'])

It is possible to stack multiple copies of the same band into a 3D numpy array

band_a_twice = ski_image.get_by_names_3d(['band_a', 'band_a'])

Sometimes it is more useful to get numpy array in a different axis order [row][column][band] (ala PIL)

row_column_band = ski_image.get_by_names_3d_band_last(['a', 'b'])

To test whether a SKI image contains a band do the following

has_band_c = ski_image.has_band('c')

Get the number of bands in the SKI

num_bands = ski_image.get_num_bands()

Best Practices

Use SKI library and do not use internal properties of SKI file format as they may be subject to change.

Always access bands with their names instead of indexes. Do not rely on the band order.

When creating a SKI image, add meta so you will be able to find what is inside.