Verified Commit e0d49376 authored by Sofus Albert Høgsbro Rose's avatar Sofus Albert Høgsbro Rose
Browse files

Lots of documentation! It's hosted!

parent 892d5115
sphinx_rtd_theme_git/sphinx_rtd_theme/
\ No newline at end of file
sphinx_rtd_theme_git @ eef98b31
Subproject commit eef98b316b947a9d8854add13cba702f41f00c14
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:
......@@ -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.
# "<project> v<release> documentation" by default.
......
Image Input/Output
=================
All image IO happens via the ColMap module.
openlut.ColMap module
---------------------
.. automodule:: openlut.ColMap
:members:
:undoc-members:
:show-inheritance:
......@@ -6,12 +6,17 @@
Welcome to openlut's documentation!
===================================
Contents:
.. toctree::
:maxdepth: 2
Table of Contents:
.. toctree::
: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
Introduction to openlut
======================
Hello! TBD
openlut
Full Documentation
=======
.. toctree::
......
......@@ -8,70 +8,18 @@ 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
--------------------
Module contents
---------------
.. automodule:: openlut.gamut
.. automodule:: openlut
:members:
:undoc-members:
:show-inheritance:
C++ Extension: olOpt
--------------
Module contents
---------------
.. automodule:: openlut
.. automodule:: openlut.lib.olOpt
:members:
:undoc-members:
:show-inheritance:
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:
#!/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
from functools import reduce
from imp import reload
import numpy as np
import openlut as ol
from openlut.lib.files import Log
......
import sys, os, os.path
import numpy as np
#~ import skimage as si
#~ import skimage.io
#~ si.io.use_plugin('freeimage')
from functools import reduce
#~ from PIL import Image
#~ import tifffile as tff
import numpy as np
import wand
import wand.image
......@@ -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)))
@staticmethod
def fromArray(imgArr, depth = 16) :
self.depth = depth
if len(shape) not in (2, 3) :
raise ValueError('Please use a valid numpy image array shape!')
self.rgbArr = np.array(rgbArr, dtype=np.float32) #Enforce 32 bit floats. Save memory.
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 fromIntArray(imgArr) :
bitDepth = int(''.join([i for i in str(imgArr.dtype) if i.isdigit()]))
def fromArray(imgArr) :
'''
Initialize a ColMap from a numpy array of either float or int type (containing an image).
self.depth = bitDepth
See :py:class:`~ColMap` initialization for a lower-level constructor.
return ColMap(np.divide(imgArr.astype(np.float32), 2 ** bitDepth - 1))
: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
#Operations - returns new ColMaps.
def apply(self, transform) :
: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 fromBinary(binData, fmt, width=None, height=None) :
'''
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.
* 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.**
'''
#~ return transform.apply(self)
return ColMap.fromArray(transform.sample(self.asarray()))
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.
: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:
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 .
* **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