diff --git a/doc/source/_themes/sphinx_rtd_theme b/doc/source/_themes/sphinx_rtd_theme new file mode 120000 index 0000000..3f934cd --- /dev/null +++ b/doc/source/_themes/sphinx_rtd_theme @@ -0,0 +1 @@ +sphinx_rtd_theme_git/sphinx_rtd_theme/ \ No newline at end of file diff --git a/doc/source/_themes/sphinx_rtd_theme_git b/doc/source/_themes/sphinx_rtd_theme_git new file mode 160000 index 0000000..eef98b3 --- /dev/null +++ b/doc/source/_themes/sphinx_rtd_theme_git @@ -0,0 +1 @@ +Subproject commit eef98b316b947a9d8854add13cba702f41f00c14 diff --git a/doc/source/builtins.rst b/doc/source/builtins.rst new file mode 100644 index 0000000..770e4ad --- /dev/null +++ b/doc/source/builtins.rst @@ -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: diff --git a/doc/source/conf.py b/doc/source/conf.py index f228e67..23cb7d4 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -63,9 +63,9 @@ author = u'Sofus Rose' # built documents. # # The short X.Y version. -version = u'0.0.1' +version = u'0.2.1' # 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 # 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 # 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 # further. For a list of options available for each theme, see the # documentation. # -# html_theme_options = {} +html_theme_options = { + "collapse_navigation" : False +} # 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. # " v documentation" by default. diff --git a/doc/source/imageio.rst b/doc/source/imageio.rst new file mode 100644 index 0000000..5e2990a --- /dev/null +++ b/doc/source/imageio.rst @@ -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: diff --git a/doc/source/index.rst b/doc/source/index.rst index e88360c..33ea6a4 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -6,12 +6,17 @@ Welcome to openlut's documentation! =================================== -Contents: + +Table of Contents: .. toctree:: - :maxdepth: 2 - + :maxdepth: 4 + intro + imageio + transforms + builtins + Indices and tables ================== @@ -20,3 +25,12 @@ Indices and tables * :ref:`modindex` * :ref:`search` +Full Docs +========= + +No nice explanations here - just all the docs in a list. + +.. toctree:: + :maxdepth: 3 + + modules diff --git a/doc/source/intro.rst b/doc/source/intro.rst new file mode 100644 index 0000000..92b64be --- /dev/null +++ b/doc/source/intro.rst @@ -0,0 +1,4 @@ +Introduction to openlut +====================== + +Hello! TBD diff --git a/doc/source/modules.rst b/doc/source/modules.rst index fb8bc43..f2dd044 100644 --- a/doc/source/modules.rst +++ b/doc/source/modules.rst @@ -1,4 +1,4 @@ -openlut +Full Documentation ======= .. toctree:: diff --git a/doc/source/openlut.rst b/doc/source/openlut.rst index d7837c3..a106afb 100644 --- a/doc/source/openlut.rst +++ b/doc/source/openlut.rst @@ -8,66 +8,6 @@ Subpackages 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 --------------- @@ -75,3 +15,11 @@ Module contents :members: :undoc-members: :show-inheritance: + +C++ Extension: olOpt +-------------- + +.. automodule:: openlut.lib.olOpt + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/transforms.rst b/doc/source/transforms.rst new file mode 100644 index 0000000..447ea0c --- /dev/null +++ b/doc/source/transforms.rst @@ -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: + + diff --git a/doc/upload.sh b/doc/upload.sh new file mode 100755 index 0000000..fbfb39e --- /dev/null +++ b/doc/upload.sh @@ -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 diff --git a/interp.py b/interp.py index 550afde..2f47b6b 100644 --- a/interp.py +++ b/interp.py @@ -1,6 +1,7 @@ from functools import reduce from imp import reload +import numpy as np import openlut as ol from openlut.lib.files import Log diff --git a/openlut/ColMap.py b/openlut/ColMap.py index 46ecdc1..efdaa70 100644 --- a/openlut/ColMap.py +++ b/openlut/ColMap.py @@ -1,14 +1,9 @@ import sys, os, os.path +from functools import reduce + 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.image import wand.display @@ -24,38 +19,134 @@ from . import gamma from .LUT import LUT from .Viewer import Viewer +from .lib import olOpt as olo + class ColMap : - def __init__(self, resX, resY, depth = 16) : - self.depth = depth - self.rgbArr = + ''' + The ColMap class stores an image in its 32 bit float internal working space. + + :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 - def fromArray(imgArr, depth = 16) : - self.depth = depth + def fromArray(imgArr) : + ''' + 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 - def fromIntArray(imgArr) : - 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) : + def fromBinary(binData, fmt, width=None, height=None) : ''' - Applies a Transform object by running its apply method. - ''' - #~ return transform.apply(self) - return ColMap.fromArray(transform.sample(self.asarray())) + Construct a ColMap from an image in binary form. See :py:func:`~ColMap.toBinary` for the inverse. + + * This won't work for greyscale data - it's assumed to be RGB. + + :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 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 : @@ -69,16 +160,32 @@ class ColMap : #Fallback to opening using Wand. return ColMap.openWand(path) - #Vendor-specific open methods. - - #~ def openSci(path) : - #~ return ColMap.fromIntArray(si.io.imread(path)[:,:,:3]) - +#Operations - returns new ColMaps. + def apply(self, transform) : + ''' + Apply an image transformation, in the form of a subclass of :py:class:`~Transform`. + + You can apply LUTs, gamma functions, matrices - simply insert an instance of :py:class:`~LUT`, + :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 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: #Quick inverse sRGB transform, to undo what Wand did - but not for exr's, which are linear bastards. 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. - return ColMap.fromIntArray(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() + return ColMap.fromArray(np.fromstring(img.make_blob("RGB"), dtype='uint{}'.format(img.depth)).reshape(img.height, img.width, 3)) 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 - at http://docs.wand-py.org/en/0.4.3/wand/image.html#wand.image.COMPRESSION_TYPES . + :param str path: The path to save the image file at. The extension specified determines the output format. + :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 : saveFunction = { "exr" : self.saveWand, @@ -126,9 +233,24 @@ class ColMap : #Fallback to saving using Wand. 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 i = data.asWandImg(depth) @@ -142,16 +264,17 @@ class ColMap : 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 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 @@ -163,38 +286,101 @@ class ColMap : Viewer.run(img, xRes, yRes, title = os.path.basename(path)) 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! 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') + +#Data Output Types + def asWandImg(self, depth = None) : + ''' + Output this ColMap as a Wand image. + + :param depth: You may override the ColMap's depth if you wish. + :type depth: int or None + :return: The Wand Image. + :rtype: wand.image - wand.display.display(wandImg) + See http://docs.wand-py.org/en/0.4.4/index.html for Wand docs. + ''' - wandImg.transform_colorspace('rgb') #This transforms it back to linearity. + 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.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. 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) : """ - Returns the base float array. + Returns the internal np.float32 image array directly. + + :return: The internal numpy array. + :rtype: np.array """ return self.rgbArr - def asIntArray(self, depth = 16, us = True) : - u = 'u' if us else '' + def asIntArray(self, depth = None, us = True) : + """ + 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)) - #Overloads +#Overloads 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')])) diff --git a/openlut/LUT.py b/openlut/LUT.py index fbf4577..91754ef 100644 --- a/openlut/LUT.py +++ b/openlut/LUT.py @@ -69,29 +69,27 @@ class LUT(Transform) : ''' 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) : q.put( (cpu, splev(spSeq, splrep(ID, array))) ) #Spline Interpolation. Pretty quick, considering. 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: - 1D LUT: in1, in2, in3 --> out1, out2, out3 - *Min 1 argument. + Latest Performance: + apply(ol.LUT): 0.026462205679908948,, (avg. 100 Trials) *sRGB LUT - 3D LUT: inR, inG, inB --> outR, outG, outB - *Min 3 arguments, len(arguments) % 3 must equal 0. - - Returns a numpy array with identical shape to the input array. + :return: Returns a numpy array with identical shape to the input array. ''' fSeq = np.array(fSeq) 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) + else : #~ return np.interp(spSeq, self.ID, self.array) #non-threaded way. out = [] @@ -109,6 +107,7 @@ class LUT(Transform) : elif self.dims == 3 : print("3D LUT Not Implemented!") +#LUT Functions def resized(self, newSize) : ''' Return the LUT, resized to newSize. diff --git a/openlut/lib/olOpt.cpp b/openlut/lib/olOpt.cpp index 0750067..b452ae1 100644 --- a/openlut/lib/olOpt.cpp +++ b/openlut/lib/olOpt.cpp @@ -144,6 +144,35 @@ py::array_t matr(py::array_t img, py::array_t mat) { } } +//grey_to_rgb takes a flattened greyscale image array and outputs a flattened numpy image array. +py::array_t grey_to_rgb(py::array_t 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(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") ); + 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", &lut1dlin, "Apply any 1D LUT to a flattened numpy image array; vectorized & parallel.",