Lots of documentation! It's hosted!

master
Sofus Albert Høgsbro Rose 2017-01-25 23:13:57 -05:00
parent 892d511525
commit e0d49376cd
Signed by: so-rose
GPG Key ID: 3D01BE95F3EFFEB9
15 changed files with 444 additions and 157 deletions

View File

@ -0,0 +1 @@
sphinx_rtd_theme_git/sphinx_rtd_theme/

@ -0,0 +1 @@
Subproject commit eef98b316b947a9d8854add13cba702f41f00c14

View File

@ -0,0 +1,32 @@
Builtin Resources
=================
openlut.gamma module
--------------------
.. automodule:: openlut.gamma
:members:
:undoc-members:
:show-inheritance:
openlut.gamut module
--------------------
.. automodule:: openlut.gamut
:members:
:undoc-members:
:show-inheritance:
openlut.lib.olOpt module
------------------------
olOpt is the reason openlut is snappy! It contains the lower-level, fast functions
that drive the rest of openlut.
.. automodule:: openlut.lib.olOpt
:members:
:undoc-members:
:show-inheritance:

View File

@ -63,9 +63,9 @@ author = u'Sofus Rose'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = u'0.0.1' version = u'0.2.1'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = u'0.0.1' release = u'0.2.1'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@ -125,16 +125,18 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
# #
html_theme = 'alabaster' html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
# #
# html_theme_options = {} html_theme_options = {
"collapse_navigation" : False
}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = [] html_theme_path = ["_themes",]
# The name for this set of Sphinx documents. # The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default. # "<project> v<release> documentation" by default.

View File

@ -0,0 +1,12 @@
Image Input/Output
=================
All image IO happens via the ColMap module.
openlut.ColMap module
---------------------
.. automodule:: openlut.ColMap
:members:
:undoc-members:
:show-inheritance:

View File

@ -6,11 +6,16 @@
Welcome to openlut's documentation! Welcome to openlut's documentation!
=================================== ===================================
Contents:
Table of Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 4
intro
imageio
transforms
builtins
Indices and tables Indices and tables
@ -20,3 +25,12 @@ Indices and tables
* :ref:`modindex` * :ref:`modindex`
* :ref:`search` * :ref:`search`
Full Docs
=========
No nice explanations here - just all the docs in a list.
.. toctree::
:maxdepth: 3
modules

View File

@ -0,0 +1,4 @@
Introduction to openlut
======================
Hello! TBD

View File

@ -1,4 +1,4 @@
openlut Full Documentation
======= =======
.. toctree:: .. toctree::

View File

@ -8,66 +8,6 @@ Subpackages
openlut.lib openlut.lib
Submodules
----------
openlut.ColMap module
---------------------
.. automodule:: openlut.ColMap
:members:
:undoc-members:
:show-inheritance:
openlut.ColMat module
---------------------
.. automodule:: openlut.ColMat
:members:
:undoc-members:
:show-inheritance:
openlut.Func module
-------------------
.. automodule:: openlut.Func
:members:
:undoc-members:
:show-inheritance:
openlut.LUT module
------------------
.. automodule:: openlut.LUT
:members:
:undoc-members:
:show-inheritance:
openlut.Transform module
------------------------
.. automodule:: openlut.Transform
:members:
:undoc-members:
:show-inheritance:
openlut.gamma module
--------------------
.. automodule:: openlut.gamma
:members:
:undoc-members:
:show-inheritance:
openlut.gamut module
--------------------
.. automodule:: openlut.gamut
:members:
:undoc-members:
:show-inheritance:
Module contents Module contents
--------------- ---------------
@ -75,3 +15,11 @@ Module contents
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
C++ Extension: olOpt
--------------
.. automodule:: openlut.lib.olOpt
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,40 @@
Transforms
=================
Doing image transforms in openlut uses the :py:func:`~ColMap.apply` method to apply Transform objects. A Transform
object is any subclass of the Transform listed below. Examples include LUT, Func, and ColMat.
openlut.Transform module
------------------------
.. automodule:: openlut.Transform
:members:
:undoc-members:
:show-inheritance:
openlut.LUT module
------------------
.. automodule:: openlut.LUT
:members:
:undoc-members:
:show-inheritance:
openlut.Func module
-------------------
.. automodule:: openlut.Func
:members:
:undoc-members:
:show-inheritance:
openlut.ColMat module
---------------------
.. automodule:: openlut.ColMat
:members:
:undoc-members:
:show-inheritance:

12
doc/upload.sh 100755
View File

@ -0,0 +1,12 @@
#!/bin/bash
REMOTE=sofus@wakingnexus
rsync -avzP build/html/* $REMOTE:~/openlut/
#ssh $REMOTE 'bash -s' << 'ENDSSH'
#
#cd /var/www/openlut
#chown -R www-data:www-data *
#
#ENDSSH

View File

@ -1,6 +1,7 @@
from functools import reduce from functools import reduce
from imp import reload from imp import reload
import numpy as np
import openlut as ol import openlut as ol
from openlut.lib.files import Log from openlut.lib.files import Log

View File

@ -1,14 +1,9 @@
import sys, os, os.path import sys, os, os.path
from functools import reduce
import numpy as np import numpy as np
#~ import skimage as si
#~ import skimage.io
#~ si.io.use_plugin('freeimage')
#~ from PIL import Image
#~ import tifffile as tff
import wand import wand
import wand.image import wand.image
import wand.display import wand.display
@ -24,38 +19,134 @@ from . import gamma
from .LUT import LUT from .LUT import LUT
from .Viewer import Viewer from .Viewer import Viewer
from .lib import olOpt as olo
class ColMap : class ColMap :
def __init__(self, resX, resY, depth = 16) : '''
self.depth = depth The ColMap class stores an image in its 32 bit float internal working space.
self.rgbArr =
:var DEPTHS: A dictionary of depths in relation to the Depths dictionary.
ColMaps are initialized by default with 0's; a black image. You can use
`open` to load a path, :py:func:`~fromArray` to load from a numpy array, or :py:func:`~fromBinary` to load from
a binary representation (useful in pipes).
:param shape: The numpy-style shape of the empty image. Specify width, then height.
:type shape: tuple[int, int] or tuple[int, int, int]
:param depth: The integer depth used for int format's input and output. Set to DEPTHS['full'] by default.
:type depth: int or None
:return: An empty ColMap, holding a black image of specified shape.
:raises ValueError: When trying to use unsupported bit depth.
:raises ValueError: When using invalid image shape.
'''
DEPTHS = { 'default' : None,
'comp' : 8,
'half' : 16,
'full' : 32,
'double' : 64
}
#Constructors
def __init__(self, shape, depth = None) :
if depth not in ColMap.DEPTHS.values :
raise ValueError('Bit depth not supported! Supported bit depths: {}'.format(', '.join(ColMap.DEPTHS.values)))
if len(shape) not in (2, 3) :
raise ValueError('Please use a valid numpy image array shape!')
self.depth = depth if depth is None else ColMap.DEPTHS['full'] #This represents the real precision of data.
self.rgbArr = np.zeros((shape[0], shape[1], 3), dtype=np.float32)
@staticmethod @staticmethod
def fromArray(imgArr, depth = 16) : def fromArray(imgArr) :
self.depth = depth '''
Initialize a ColMap from a numpy array of either float or int type (containing an image).
self.rgbArr = np.array(rgbArr, dtype=np.float32) #Enforce 32 bit floats. Save memory. See :py:class:`~ColMap` initialization for a lower-level constructor.
:param imgArr: The numpy image array. Must have shape (width, height, 3)
:param depth: The integer depth used for int format's input and output. None will use highest available.
:type depth: int or None
:return: A ColMap containing the image represented in imgArr.
:raises ValueError: When trying to use unsupported array data type
'''
#Infer bitDepth from array to create new array, nArr, which we'll use to make our ColMap.
if issubclass(imgArr.dtype.type, np.integer) : #If it's an integer.
bitDepth = int(''.join([i for i in str(imgArr.dtype) if i.isdigit()]))
nArr = np.divide(imgArr.astype(np.float32), 2 ** bitDepth - 1)
elif issubclass(imgArr.dtype.type, np.floating) : #It it's a float.
#If we're dealing with an np.float16 array, we can't exactly start giving 32 bit output.
if int(''.join([i for i in str(imgArr.dtype) if i.isdigit()])) == 16 :
bitDepth = 16
else :
bitDepth = None
nArr = np.array(imgArr, dtype=np.float32)
else :
raise ValueError('The input image array uses an invalid data type {}! Please use any np.int or np.float variant!'.format(imgArr.dtype.type))
#We're taking over the creation of img.rgbArr, so we need to do different error checking of our own.
if len(nArr.shape) not in (2, 3) :
raise ValueError('Please use a valid numpy image array shape!')
elif len(nArr.shape) == 2 :
#If we're dealing with a greyscale image, then we need to convert it to RGB using an optimized C++ function.
nArr = olo.grey_to_rgb(nArr.reshape(reduce(lambda a, b: a*b, nArr.shape))).reshape((nArr.shape[0], nArr.shape[1], 3))
img = ColMap(nArr.shape, depth=bitDepth)
img.rgbArr = nArr
return img
@staticmethod @staticmethod
def fromIntArray(imgArr) : def fromBinary(binData, fmt, width=None, height=None) :
bitDepth = int(''.join([i for i in str(imgArr.dtype) if i.isdigit()]))
self.depth = bitDepth
return ColMap(np.divide(imgArr.astype(np.float32), 2 ** bitDepth - 1))
#Operations - returns new ColMaps.
def apply(self, transform) :
''' '''
Applies a Transform object by running its apply method. Construct a ColMap from an image in binary form. See :py:func:`~ColMap.toBinary` for the inverse.
'''
#~ return transform.apply(self) * This won't work for greyscale data - it's assumed to be RGB.
return ColMap.fromArray(transform.sample(self.asarray()))
:param bin binData: The binary data blob to open.
:param str fmt: Wand needs to know what image format the binary data being thrown at it is in! See https://www.imagemagick.org/script/formats.php .
:param str width: You may specify a specific width if you're having problems.
:param str height: You may specify a specific height if you're having problems.
:return: The image, as a ColMat.
:rtype: :py:class:`~ColMap`
This is great for pipes, where you're receiving binary data through stdin.
* Set binData to `sys.stdin.buffer.read()` in a script to pipe data into it!
**NOTE: Uses Wand's "blob" functionality, and as such incurs Wand's limitations.**
'''
with wand.image.Image(blob=binData, format=fmt, width=width, height=height) as img:
return ColMap.fromArray(np.fromstring(img.make_blob("RGB"), dtype='uint{}'.format(img.depth)).reshape(img.height, img.width, 3))
#IO Functions
@staticmethod @staticmethod
def open(path) : def open(path) :
''' '''
Opens 8 and 16 bit images of many formats. Construct a ColMap from an image on the disk.
:param str path: The image path to open.
:return: The image, as a ColMat.
:rtype: :py:class:`~ColMap`
ColMap currently uses ImageMagick to open a wide range of formats, including:
* **EXR**: The industry standard for HDR, wide-gamut, linear-encoded images.
* **DPX**: An older production format.
* **PNG**: Can store 16-bit images well. Usually quite slow.
* *Any other IM-supported formats...* See https://www.imagemagick.org/script/formats.php
''' '''
try : try :
@ -69,16 +160,32 @@ class ColMap :
#Fallback to opening using Wand. #Fallback to opening using Wand.
return ColMap.openWand(path) return ColMap.openWand(path)
#Vendor-specific open methods. #Operations - returns new ColMaps.
def apply(self, transform) :
'''
Apply an image transformation, in the form of a subclass of :py:class:`~Transform`.
#~ def openSci(path) : You can apply LUTs, gamma functions, matrices - simply insert an instance of :py:class:`~LUT`,
#~ return ColMap.fromIntArray(si.io.imread(path)[:,:,:3]) :py:class:`~Func`, :py:class:`~ColMat`, or any other :py:class:`~Transform` object to apply it
to the image!
:param transform: An image transform.
:type transform: :py:class:`~Transform`
:return: A transformed ColMap.
'''
return ColMap.fromArray(transform.sample(self.asarray()))
#Vendor-specific open methods.
@staticmethod @staticmethod
def openWand(path) : def openWand(path) :
''' '''
Open a file using the Wand ImageMagick binding. Vendor-specific :py:func:`~ColMap.open` function. See :py:func:`~ColMap.open`
:param str path: The image path to open.
:return: The image, as a ColMat.
:rtype: :py:class:`~ColMap`
''' '''
with wand.image.Image(filename=path) as img: with wand.image.Image(filename=path) as img:
#Quick inverse sRGB transform, to undo what Wand did - but not for exr's, which are linear bastards. #Quick inverse sRGB transform, to undo what Wand did - but not for exr's, which are linear bastards.
if img.format != 'EXR' : if img.format != 'EXR' :
@ -87,32 +194,32 @@ class ColMap :
img.colorspace = 'srgb' if img.format == 'DPX' else 'rgb' #Fix for IM's dpx bug. img.colorspace = 'srgb' if img.format == 'DPX' else 'rgb' #Fix for IM's dpx bug.
return ColMap.fromIntArray(np.fromstring(img.make_blob("RGB"), dtype='uint{}'.format(img.depth)).reshape(img.height, img.width, 3)) return ColMap.fromArray(np.fromstring(img.make_blob("RGB"), dtype='uint{}'.format(img.depth)).reshape(img.height, img.width, 3))
@staticmethod
def fromBinary(binData, fmt, width=None, height=None) :
'''
Using the Wand blob functionality, creates a ColMap from binary data. Set binData to sys.stdin.buffer.read() to activate piping!
'''
with wand.image.Image(blob=binData, format=fmt, width=width, height=height) as img:
return ColMap.fromIntArray(np.fromstring(img.make_blob("RGB"), dtype='uint{}'.format(img.depth)).reshape(img.height, img.width, 3))
def toBinary(self, fmt, depth=16) :
'''
Using Wand blob functionality
'''
with self.asWandImg(depth) as img :
img.format = fmt
return img.make_blob()
def save(self, path, compress = None, depth = None) : def save(self, path, compress = None, depth = None) :
''' '''
Save the image. The filetype will be inferred from the path, and the appropriate backend will be used. Save a ColMap to an image file on the disk.
Compression scheme will be applied based on the backend compatiblity. Wand compression types can be used: Browse then :param str path: The path to save the image file at. The extension specified determines the output format.
at http://docs.wand-py.org/en/0.4.3/wand/image.html#wand.image.COMPRESSION_TYPES . :param compress: Compression options passed to the vendor. Currently broken.
:type compress: str or None
:param depth: You may override the ColMap's depth if you wish.
:type depth: int or None
ColMap currently uses ImageMagick to save a wide range of formats, including:
* **EXR**: The industry standard for HDR, wide-gamut, linear-encoded images.
* **DPX**: An older production format.
* **PNG**: Can store 16-bit images well. Usually quite slow.
* *Any other IM-supported formats...* See https://www.imagemagick.org/script/formats.php
**NOTE: EXRs are only saveable as 16-bit integer, with no compression options. This is an IM/Wand library limitation.**
''' '''
if depth is None: depth = 16
if depth not in ColMap.DEPTHS.values :
raise ValueError('Bit depth not supported! Supported bit depths: {}'.format(', '.join(ColMap.DEPTHS.values)))
try : try :
saveFunction = { saveFunction = {
"exr" : self.saveWand, "exr" : self.saveWand,
@ -126,9 +233,24 @@ class ColMap :
#Fallback to saving using Wand. #Fallback to saving using Wand.
self.saveWand(path, compress, depth) self.saveWand(path, compress, depth)
#Vendor-specific save methods
def saveWand(self, path, compress = None, depth = 16) : #Vendor-specific save methods
def saveWand(self, path, compress = None, depth = None) :
'''
Vendor-specific :py:func:`~ColMap.save` function. See :py:func:`~ColMap.save`
:param str path: The image path to save to.
:param compress: Compression options passed to Wand. Currently broken.
:param depth: You may override the ColMap's depth if you wish.
:type depth: int or None
**NOTE: EXRs are only saveable as 16-bit integer, with no compression options. This is an IM/Wand library limitation.**
'''
if depth not in ColMap.DEPTHS.values :
raise ValueError('Bit depth not supported! Supported bit depths: {}'.format(', '.join(ColMap.DEPTHS.values)))
data = self.apply(LUT.lutFunc(gamma.sRGB)) if path[path.rfind('.')+1:] == 'dpx' else self data = self.apply(LUT.lutFunc(gamma.sRGB)) if path[path.rfind('.')+1:] == 'dpx' else self
i = data.asWandImg(depth) i = data.asWandImg(depth)
@ -142,16 +264,17 @@ class ColMap :
i.save(filename=path) i.save(filename=path)
#~ def saveSci(self, path, compress = None, depth = 16) :
#~ if compress is not None: raise ValueError('Scipy Backend cannot compress the output image!')
#~ si.io.imsave(path, self.asIntArray())
#Display Functions #Display Functions
@staticmethod @staticmethod
def display(path, width = 1000) : def display(path, width = 1000) :
''' '''
Shows an image at a path without making a ColMap. Display an image at a path on the disk, using the builtin OpenGL Viewer.
:param width: The desired width of the viewer; the height is automatically gleaned from the aspect ratio.
For the viewer source code, see :py:class:`~Viewer`.
''' '''
img = ColMap.open(path).rgbArr img = ColMap.open(path).rgbArr
@ -163,38 +286,101 @@ class ColMap :
Viewer.run(img, xRes, yRes, title = os.path.basename(path)) Viewer.run(img, xRes, yRes, title = os.path.basename(path))
def show(self, width = 1000) : def show(self, width = 1000) :
'''
Display this ColMap using the builtin OpenGL Viewer.
:param width: The desired width of the viewer; the height is automatically gleaned from the aspect ratio.
For the viewer source code, see :py:class:`~Viewer`.
'''
#Use my custom OpenGL viewer! #Use my custom OpenGL viewer!
Viewer.run(self.rgbArr, width, int(width * self.rgbArr.shape[0]/self.rgbArr.shape[1])) Viewer.run(self.rgbArr, width, int(width * self.rgbArr.shape[0]/self.rgbArr.shape[1]))
@staticmethod
def wandShow(wandImg) :
#Do a quick sRGB transform for viewing. Must be in 'rgb' colorspace for this to take effect.
wandImg.transform_colorspace('srgb')
wand.display.display(wandImg) #Data Output Types
def asWandImg(self, depth = None) :
'''
Output this ColMap as a Wand image.
wandImg.transform_colorspace('rgb') #This transforms it back to linearity. :param depth: You may override the ColMap's depth if you wish.
:type depth: int or None
:return: The Wand Image.
:rtype: wand.image
See http://docs.wand-py.org/en/0.4.4/index.html for Wand docs.
'''
if depth not in ColMap.DEPTHS.values :
raise ValueError('Bit depth not supported! Supported bit depths: {}'.format(', '.join(ColMap.DEPTHS.values)))
if depth is None :
d = ColMap.DEPTHS['half'] if self.depth >= ColMap.DEPTHS['half'] else self.depth #Highest is half - 16.
else :
d = depth
#Data Form Functions
def asWandImg(self, depth = 16) :
#~ i = wand.image.Image(blob=self.asarray().tostring(), width=np.shape(self.rgbArr)[1], height=np.shape(self.rgbArr)[0], format='RGB') #Float Array #~ i = wand.image.Image(blob=self.asarray().tostring(), width=np.shape(self.rgbArr)[1], height=np.shape(self.rgbArr)[0], format='RGB') #Float Array
i = wand.image.Image(blob=self.asIntArray(depth).tostring(), width=np.shape(self.rgbArr)[1], height=np.shape(self.rgbArr)[0], format='RGB') i = wand.image.Image(blob=self.asIntArray(d).tostring(), width=np.shape(self.rgbArr)[1], height=np.shape(self.rgbArr)[0], format='RGB')
i.colorspace = 'rgb' #Specify, to Wand, that this image is to be treated as raw, linear, data. i.colorspace = 'rgb' #Specify, to Wand, that this image is to be treated as raw, linear, data.
return i return i
def toBinary(self, fmt, depth=None) :
'''
Output this ColMap in binary form. See :py:func:`~ColMap.fromBinary` for the inverse.
:param str fmt: Wand needs to know what format to output! See https://www.imagemagick.org/script/formats.php .
:param depth: You may override the ColMap's bit depth if you wish.
:type depth: int or None
:return: The image, as a ColMat.
:rtype: :py:class:`~ColMap`
This is great for pipes, where you're sending binary data through stdout.
* Use the return value as the argument of sys.stdout.write() to pipe the image to other applications!
**NOTE: Uses Wand's "blob" functionality, and as such incurs Wand's limitations.**
'''
if depth not in ColMap.DEPTHS.values :
raise ValueError('Bit depth not supported! Supported bit depths: {}'.format(', '.join(ColMap.DEPTHS.values)))
with self.asWandImg(d) as img :
img.format = fmt
return img.make_blob()
def asarray(self) : def asarray(self) :
""" """
Returns the base float array. Returns the internal np.float32 image array directly.
:return: The internal numpy array.
:rtype: np.array
""" """
return self.rgbArr return self.rgbArr
def asIntArray(self, depth = 16, us = True) : def asIntArray(self, depth = None, us = True) :
u = 'u' if us else '' """
Returns the internal image array as an int array.
:param depth: You may override the ColMap's bit depth if you wish.
:type depth: int or None
:param bool us: True will output unsigned ints, False will output signed ints.
:return: The internal numpy array.
:rtype: np.array
"""
if depth not in ColMap.DEPTHS.values :
raise ValueError('Bit depth not supported! Supported bit depths: {}'.format(', '.join(ColMap.DEPTHS.values)))
if depth is None :
d = self.depth #No limits here.
else :
d = depth
u = 'u' if us else '' #Unsigned or no?
return np.multiply(self.rgbArr.clip(0, 1), 2.0 ** depth - 1).astype("{0}int{1}".format(u, depth)) return np.multiply(self.rgbArr.clip(0, 1), 2.0 ** depth - 1).astype("{0}int{1}".format(u, depth))
#Overloads #Overloads
def __repr__(self) : def __repr__(self) :
return 'ColMap.fromArray( \n\trgbArr = {0}\n)'.format('\n\t\t'.join([line.strip() for line in repr(self.rgbArr).split('\n')])) return 'ColMap.fromArray( \n\trgbArr = {0}\n)'.format('\n\t\t'.join([line.strip() for line in repr(self.rgbArr).split('\n')]))

View File

@ -69,29 +69,27 @@ class LUT(Transform) :
''' '''
return LUT.lutArray(splev(np.linspace(0, 1, num=len(idArr)), splrep(idArr, mapArr))) return LUT.lutArray(splev(np.linspace(0, 1, num=len(idArr)), splrep(idArr, mapArr)))
#LUT Functions. #Transform Functions.
def _splInterp(q, cpu, spSeq, ID, array) : def _splInterp(q, cpu, spSeq, ID, array) :
q.put( (cpu, splev(spSeq, splrep(ID, array))) ) #Spline Interpolation. Pretty quick, considering. q.put( (cpu, splev(spSeq, splrep(ID, array))) ) #Spline Interpolation. Pretty quick, considering.
def sample(self, fSeq, spl=True) : def sample(self, fSeq, spl=True) :
''' '''
Sample the LUT using a flat float sequence (ideally a numpy array; (0..1) ). Apply the 1D LUT to the numpy image array, using fast C++ math.
Each n (dimensions) clump of arguments will be used to sample the LUT. So: Latest Performance:
1D LUT: in1, in2, in3 --> out1, out2, out3 apply(ol.LUT): 0.026462205679908948,, (avg. 100 Trials) *sRGB LUT
*Min 1 argument.
3D LUT: inR, inG, inB --> outR, outG, outB :return: Returns a numpy array with identical shape to the input array.
*Min 3 arguments, len(arguments) % 3 must equal 0.
Returns a numpy array with identical shape to the input array.
''' '''
fSeq = np.array(fSeq) fSeq = np.array(fSeq)
if self.dims == 1 : if self.dims == 1 :
#If scipy isn't loaded, we can't use spline interpolation!
if (not MOD_SCIPY) or self.size > 25 : # Auto-adapts all but the smallest LUTs to use the faster linear interpolation. #Scipy must be loaded & the LUT must be rediculously small before spline interpolation sets in.
if (not MOD_SCIPY) or self.size > 25 :
return olo.lut1dlin(fSeq.reshape(reduce(lambda a, b: a*b, fSeq.shape)), self.array, self.range[0], self.range[1]).reshape(fSeq.shape) return olo.lut1dlin(fSeq.reshape(reduce(lambda a, b: a*b, fSeq.shape)), self.array, self.range[0], self.range[1]).reshape(fSeq.shape)
else : else :
#~ return np.interp(spSeq, self.ID, self.array) #non-threaded way. #~ return np.interp(spSeq, self.ID, self.array) #non-threaded way.
out = [] out = []
@ -109,6 +107,7 @@ class LUT(Transform) :
elif self.dims == 3 : elif self.dims == 3 :
print("3D LUT Not Implemented!") print("3D LUT Not Implemented!")
#LUT Functions
def resized(self, newSize) : def resized(self, newSize) :
''' '''
Return the LUT, resized to newSize. Return the LUT, resized to newSize.

View File

@ -144,6 +144,35 @@ py::array_t<float> matr(py::array_t<float> img, py::array_t<float> mat) {
} }
} }
//grey_to_rgb takes a flattened greyscale image array and outputs a flattened numpy image array.
py::array_t<float> grey_to_rgb(py::array_t<float> arr) {
py::buffer_info bufIn = arr.request();
//To use with an image, MAKE SURE to flatten the 3D array to a 1D array, then back out to a 3D array after.
if (bufIn.ndim == 1) {
//Make numpy allocate the buffer.
auto result = py::array_t<float>(bufIn.size * 3); //Size is multiplied by 3 - we're outputting RGB!
//Get the pointers that we can manipulate from C++.
auto bufOut = result.request();
float *ptrIn = (float *) bufIn.ptr,
*ptrOut = (float *) bufOut.ptr;
//The reason for all this bullshit as opposed to vectorizing is this pragma!!!
#pragma omp parallel for
for (size_t i = 0; i < bufOut.shape[0]; i+=3) {
float val = ptrIn[(i+1)/3 - 1]; //Little bit of indexing math to get the value; remember we're skipping by threes.
ptrOut[i] = val;
ptrOut[i + 1] = val;
ptrOut[i + 2] = val;
}
return result;
}
}
@ -164,6 +193,12 @@ PYBIND11_PLUGIN(olOpt) {
py::arg("mat") py::arg("mat")
); );
mod.def( "grey_to_rgb",
&grey_to_rgb,
"Takes a flattened 2D greyscale image array and outputs a flattened 3D numpy image array.",
py::arg("arr")
);
mod.def( "lut1dlin", mod.def( "lut1dlin",
&lut1dlin, &lut1dlin,
"Apply any 1D LUT to a flattened numpy image array; vectorized & parallel.", "Apply any 1D LUT to a flattened numpy image array; vectorized & parallel.",