Shady.Linearization Sub-module

This module contains several utility functions related to linearization.

Some of these (ScreenNonlinearity, Linearize, ApplyLUT) are only useful in special circumstances where we want to re-create “offline” what the shader is doing for us on every frame.

Others (LoadLUT, SaveLUT) are for general management of look-up table arrays.

Others (BitStealingLUT, ReportBitStealingStats) are useful for creating and examining a specific type of look-up table that employs a bit-stealing technique (after Tyler 1997).

Note that everything exported from this module is also available in the top-level Shady.* namespace.

Shady.Linearization.ApplyLUT(image, lut, noiseAmplitude=0, DACbits=8)

Translate an array of image pixel values via a look-up table lut.

This allows us to emulate, on the CPU in Python, the look-up operation performed automatically by the GPU in the shader on every frame if a look-up table has been configured via the Stimulus.lut property.

Parameters:
  • image (numpy.array) – Source pixel values. If the array data type is floating-point, pixel values are assumed to refer to ideal luminances in the range 0 to 1, and are clipped to this range before scaling and rounding according to the size of the lut. If the array is of some integer type, the values are assumed to be direct indices into the look-up table.

    Note that, if the image has more than one color channel (i.e. it has a third dimension with extent > 1) then only the first channel (red) will be used.

  • lut (numpy.array) – Look-up table array, e.g. as output by LoadLUT() or BitStealingLUT().

  • noiseAmplitude (float) – specifies the amplitude of an optional random signal that can be added to image pixel values before lookup. A negative value indicates a uniform distribution (in the range [noiseAmplitude, -noiseAmplitude]) whereas a positive value is interpreted as the standard deviation of a Gaussian noise distribution.

  • DACbits (int) – The number of bits per digital-analog converter in the graphics card for which the look-up table is intended. Floating-point image pixel values will be scaled accordingly.

Returns:

An array of integer DAC values post-lookup. First two dimensions match those of the input image. Extent in the third dimension will match that of lut.

Shady.Linearization.BitStealingLUT(maxDACDeparture=2, Cmax=3.0, nbits=16, gamma='sRGB', cache_dir=None, DACbits=8)

Create an RGB look-up table that (a) linearizes pixel intensity values according to the specified gamma profile, and (b) increases dynamic range using a “bit-stealing” approach (after Tyler 1997).

Parameters:
  • maxDACDeparture (int) – Red, green and blue DAC values will be considered only up to +/- this value relative to the R==G==B line

  • nbits (int) – The look-up table will have 2 ** nbits entries. It doesn’t hurt to specify a high number here, like 16—however, note that, depending on the values of maxDACDeparture and Cmax you may not get that many distinct or evenly-spaced luminance levels. The actual effective precision can be investigated using ReportBitStealingStats()

  • Cmax (float) – [R,G,B] triples will not be considered if the corresponding chroma, in percent (i.e. the third column of RGB_to_YLCHab() output) exceeds this.

  • gamma (float) – screen non-linearity parameter (see ScreenNonlinearity())

  • cache_dir (str, None) – optional directory in which to look for a cached copy of the resulting LUT (or save one, after creation, if the appropriately-named file was not found there).

    DACbits (int):

    The number of bits per digital-analog converter in the graphics card for which the look-up table is intended. LUT values will be scaled accordingly.

Returns:

numpy array of shape [2**nbits, 1, 3] with the appropriate integer type (usually uint8), containing integer RGB triplets.

Shady.Linearization.Linearize(Y, gamma='sRGB')

Maps ideal luminance Y (in the domain 0 to 1) to normalized DAC values x (in the range 0 to 1, which corresponds to DAC values 0 to 255 if we assume standard 8-bit DACs) given the screen non-linearity parameter gamma.

Generally, gamma is numeric and strictly positive, in which case the relationship is x = Y ** (1/gamma). A special case is gamma='sRGB', which is also used if you pass a gamma value of 0 or less: this uses a slightly different piecewise equation, very close to the gamma=2.2 curve (even though the exponent used in it is actually 2.4).

This allows us to emulate, on the CPU and in Python, the linearization operation performed automatically by the GPU in the fragment shader on every frame according to the value of of the Stimulus.gamma property.

Inverse of ScreenNonlinearity()

Shady.Linearization.LoadLUT(source, DACbits=8)

Load or prepare a look-up table array.

Parameters:
  • source (str, numpy.ndarray) – Usually this is a string denoting a filename. The file may be in numpy format - either a npy file in which the the lookup-table has been written with numpy.save, or a npz file into which the look-up table array has been saved with numpy.savez as either the sole variable or a variable called lut. If the third-party pillow package is installed, the file may alternatively be a png image file in which look-up table entries have been saved as R,G,B pixel values in column- first order.

    The source may also be a numpy.ndarray already.

  • DACbits (int) – This is the number of bits per DAC in the graphics card for which the look-up table is intended. In this function it is used to verify that the lookup-table entries are in range and to cast the output as the appropriate numeric data-type.

Returns:

Whether source is a filename or an array already, in either case, this function ensures that the returned result is a 3-dimensional numpy array, of the appropriate integer type, with extent 3 (RGB) or 4 (RGBA) in its third dimension.

See also

Shady.Linearization.ReportBitStealingStats(lut=None, image=None, gamma='sRGB', DACbits=8)

Prints to the console certain statistics about the look-up table lut, if supplied, and optionally also any given image that has been through the look-up process (i.e. an output of ApplyLUT()).

Make sure that the gamma and DACbits arguments match the values that were used for creating lut.

Shady.Linearization.SaveLUT(filename, lut, luminance=(), DACbits=8)

Save a look-up table array, and optionally also the corresponding sequence of luminance values, in a file.

Parameters:
  • filename (str) – name of the file to save. Determines the file format, and should end in npy, npz or png

  • lut (numpy.array) – look-up table array, n-by-3 or n-by-1-by-3 as per the output of LoadLUT() or BitStealingLUT(), where n is the number of entries. If the format is npz the array will be saved under the variable name lut.

  • luminance (numpy.array, list) – sequence of n ideal luminance values (i.e. luminance values in the range 0 to 1) corresponding to each of the look-up table entries. Only saved (under the variable name luminance) if the file format is npz

  • DACbits (int) – This is the number of bits per DAC in the graphics card for which the look-up table is intended. In this function it is used to verify that the lookup-table entries are in range and to cast the output as the appropriate numeric data-type.

Returns:

  • the filename

  • the look-up table array in standardized format

  • the sequence of luminance values

Return type:

a tuple consisting of

See also

Shady.Linearization.ScreenNonlinearity(x, gamma='sRGB')

Maps normalized DAC values x (in the domain 0 to 1, which corresponds to DAC values 0 to 255 if we assume standard 8-bit DACs) to ideal luminance Y (in the range 0 to 1), given the screen non-linearity parameter gamma.

Generally, gamma is numeric and strictly positive, in which case the relationship is Y = x ** gamma. A special case is gamma='sRGB', which is also used if you pass a gamma value of 0 or less: this uses a slightly different piecewise equation, very close to the gamma=2.2 curve (even though the exponent used in it is actually 2.4).

Inverse of Linearize()