Added my OpenGL viewer, olOpt for cool fast C++, and more!
parent
c1be1041b2
commit
f8cd85bbc2
|
@ -0,0 +1,3 @@
|
|||
*.exr filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
|
@ -0,0 +1 @@
|
|||
*.pyc
|
|
@ -0,0 +1,6 @@
|
|||
[distutils]
|
||||
index-servers=pypi
|
||||
|
||||
[pypi]
|
||||
repository = https://upload.pypi.org/legacy/
|
||||
username = so-rose
|
|
@ -0,0 +1 @@
|
|||
recursive-include openlut *.py
|
11
README.md
11
README.md
|
@ -16,10 +16,11 @@ I wanted it to cover this niche simply and consistently, something color managem
|
|||
What About OpenColorIO? Why does this exist?
|
||||
------
|
||||
OpenColorIO is a wonderful library, but seems geared towards managing the complexity of many larger applications in a greater pipeline.
|
||||
openlut is more simple; it doesn't care about the big picture - you simply read in images, transform them, then output them. openlut
|
||||
also focuses greatly on the "how" of these transformations with tools that eg. create or resize LUTs, things missing in OCIO.
|
||||
openlut is more simple; it doesn't care about the big picture - you just do consistent operations on images. openlut also has tools to deal
|
||||
with these building blocks, unlike OCIO - resizing LUTs, etc. .
|
||||
|
||||
Since it's a library, though, it's perfectly feasable (if not easy) to build such a greater pipeline based on openlut's simple color transformations.
|
||||
Indeed, OCIO is just a system these basic operations using LUTs - in somewhat unintuitive ways, in my opinion. You could setup a similar system
|
||||
using openlut's toolkit.
|
||||
|
||||
|
||||
Installation
|
||||
|
@ -29,7 +30,7 @@ I'll put it on pip eventually (when I figure out how!). For now, just download t
|
|||
To run openlut.py, first make sure you have the *Dependencies*. To run the test code at the bottom (make sure openlut is in the same
|
||||
directory as testpath; it needs to load test.exr), you can then run:
|
||||
|
||||
`python3 openlut.py -t`
|
||||
`python3 main.py -t`
|
||||
|
||||
To use in your code, simply `import` the module at the top of your file.
|
||||
|
||||
|
@ -56,7 +57,7 @@ The **Transform** objects themselves have plenty of features - like LUT, with `o
|
|||
input matrices, or automatic spline-based interpolation of very small 1D LUTs - to make them helpful in and of themselves!
|
||||
|
||||
|
||||
The best way to demonstrate from here, I think, is to show some test code: (run python3 openlut.py -t to see it work)
|
||||
The best way to demonstrate this, I think, is to show some test code: (run python3 openlut.py -t to see it work)
|
||||
|
||||
```python
|
||||
#Open any format image. Try it with exr/dpx/anything!
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
#!/usr/bin/env python2
|
||||
from __future__ import print_function
|
||||
|
||||
'''
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Sofus Rose
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
'''
|
||||
|
||||
#Image Dims: X (0-1919), Y (0-1079), C (0-2; RGB)
|
||||
|
||||
import sys, os
|
||||
|
||||
#~ import numpy as np
|
||||
#~ from PIL import Image
|
||||
#~ import tifffile as tff
|
||||
|
||||
#~ import pylut as pl
|
||||
|
||||
SEP=' '
|
||||
SIZE=8 #The .cube resolution is this, squared.
|
||||
BIT_DEPTH=16
|
||||
|
||||
def imgOpen(path) :
|
||||
if path[-4:] == '.tif' or path[-4:] == 'tiff' :
|
||||
return tff.TiffFile(path).asarray()
|
||||
print("Not Supported!")
|
||||
else :
|
||||
return np.asarray(Image.open(path).convert('RGB'))
|
||||
|
||||
def rgbImg(img): return img.transpose(2, 0, 1) #X, Y, C --> C, X, Y
|
||||
def xyImg(rgbImg): return rgbImg.transpose(1, 2, 0) #C, X, Y --> X, Y, C
|
||||
|
||||
|
||||
def prHelp() :
|
||||
print("ml_lhald.py: Generates modified (R/B swapped) HALD files for arbitrary grading, then converts them to .cube using pylut.")
|
||||
print("\tGenerate modified HALD: ./mk_lhald.py gen <OPTIONAL: name, without extension, of png output>")
|
||||
print("\tHALD --> CUBE: ./mk_lhald.py mk <path to HALD> <OPTIONAL: name, without extension, of cube output>\n\n")
|
||||
print("Requires pylut. Install with 'pip2 install pylut'.")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__" :
|
||||
if not sys.argv[1:]: prHelp()
|
||||
|
||||
if sys.argv[1] == "gen" :
|
||||
iPath = "identity" if not sys.argv[2:] else sys.argv[2]
|
||||
os.system('convert hald:{0} {1}.png'.format(SIZE, iPath)) # -separate -swap 0,2 -combine
|
||||
|
||||
print('Go ahead and grade the file "{}.png".'.format(iPath))
|
||||
elif sys.argv[1] == "mk" and sys.argv[1:] :
|
||||
fPath = ".converted_hald.ppm"
|
||||
lPath = "grade" if not sys.argv[3:] else sys.argv[3]
|
||||
os.system('convert -depth {2} {0} -compress none {1}'.format(sys.argv[2], fPath, BIT_DEPTH))
|
||||
|
||||
lines = ' '.join(line.strip() for line in open(fPath, 'r').readlines()[3:]).split(' ')
|
||||
print(lines[:64*3])
|
||||
lines = ['%.6f' % (float(line)/float(2 ** BIT_DEPTH)) for line in lines] #Direct .cube output only.
|
||||
coords = [SEP.join(lines[i:i+3]) for i in range(0, len(lines)-2, 3)]
|
||||
|
||||
identity = ' '.join([line.split(SEP)[0] for line in coords[:SIZE**2]])
|
||||
|
||||
print(lines[:65], '\n\n', coords[:65], len(lines), len(coords))
|
||||
|
||||
#~ with open(lPath + '.3dl', 'w') as f :
|
||||
#~ print(identity, end='\n', file=f)
|
||||
#~ print(*coords, sep='\n', file=f)
|
||||
|
||||
print("Creating", lPath + '.cube')
|
||||
|
||||
#~ lut = pl.LUT.FromNuke3DLFile(lPath + '.3dl')
|
||||
#~ #print(lut.ColorAtInterpolatedLatticePoint(0.00206,0.00227,0.00307))
|
||||
#~ lut.ToCubeFile(lPath + '.cube')
|
||||
|
||||
with open(lPath + '.cube', 'w') as f :
|
||||
print("LUT_3D_SIZE", SIZE ** 2, file=f)
|
||||
print(*coords, sep='\n', file=f)
|
||||
|
||||
os.remove(fPath)
|
||||
#~ os.remove(lPath + '.3dl')
|
||||
else :
|
||||
prHelp()
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " applehelp to make an Apple Help Book"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " epub3 to make an epub3"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
@echo " dummy to check syntax errors of document sources"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
.PHONY: html
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
.PHONY: dirhtml
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
.PHONY: singlehtml
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
.PHONY: pickle
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
.PHONY: json
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
.PHONY: htmlhelp
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
.PHONY: qthelp
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/openlut.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/openlut.qhc"
|
||||
|
||||
.PHONY: applehelp
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
.PHONY: devhelp
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/openlut"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/openlut"
|
||||
@echo "# devhelp"
|
||||
|
||||
.PHONY: epub
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
.PHONY: epub3
|
||||
epub3:
|
||||
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
|
||||
@echo
|
||||
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
|
||||
|
||||
.PHONY: latex
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
.PHONY: latexpdf
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: latexpdfja
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: text
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
.PHONY: man
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
.PHONY: texinfo
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
.PHONY: info
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
.PHONY: gettext
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
.PHONY: changes
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
.PHONY: linkcheck
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
.PHONY: doctest
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
.PHONY: xml
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
.PHONY: pseudoxml
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
|
||||
.PHONY: dummy
|
||||
dummy:
|
||||
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
||||
@echo
|
||||
@echo "Build finished. Dummy builder generates no files."
|
|
@ -0,0 +1,281 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% source
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. epub3 to make an epub3
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
echo. coverage to run coverage check of the documentation if enabled
|
||||
echo. dummy to check syntax errors of document sources
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
REM Check if sphinx-build is available and fallback to Python version if any
|
||||
%SPHINXBUILD% 1>NUL 2>NUL
|
||||
if errorlevel 9009 goto sphinx_python
|
||||
goto sphinx_ok
|
||||
|
||||
:sphinx_python
|
||||
|
||||
set SPHINXBUILD=python -m sphinx.__init__
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:sphinx_ok
|
||||
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\openlut.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\openlut.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub3" (
|
||||
%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "coverage" (
|
||||
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of coverage in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/coverage/python.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dummy" (
|
||||
%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. Dummy builder generates no files.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
|
@ -0,0 +1,344 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# openlut documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Jan 18 22:00:18 2017.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.mathjax',
|
||||
'sphinx.ext.viewcode',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'openlut'
|
||||
copyright = u'2017, Sofus Rose'
|
||||
author = u'Sofus Rose'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'0.0.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'0.0.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#
|
||||
# today = ''
|
||||
#
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
# keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# 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 = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents.
|
||||
# "<project> v<release> documentation" by default.
|
||||
#
|
||||
# html_title = u'openlut v0.0.1'
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (relative to this directory) to use as a favicon of
|
||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#
|
||||
# html_extra_path = []
|
||||
|
||||
# If not None, a 'Last updated on:' timestamp is inserted at every page
|
||||
# bottom, using the given strftime format.
|
||||
# The empty string is equivalent to '%b %d, %Y'.
|
||||
#
|
||||
# html_last_updated_fmt = None
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
|
||||
#
|
||||
# html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# 'ja' uses this config value.
|
||||
# 'zh' user can custom change `jieba` dictionary path.
|
||||
#
|
||||
# html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#
|
||||
# html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'openlutdoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'openlut.tex', u'openlut Documentation',
|
||||
u'Sofus Rose', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#
|
||||
# latex_appendices = []
|
||||
|
||||
# It false, will not define \strong, \code, itleref, \crossref ... but only
|
||||
# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
|
||||
# packages.
|
||||
#
|
||||
# latex_keep_old_macro_names = True
|
||||
|
||||
# If false, no module index is generated.
|
||||
#
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'openlut', u'openlut Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'openlut', u'openlut Documentation',
|
||||
author, 'openlut', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#
|
||||
# texinfo_no_detailmenu = False
|
|
@ -0,0 +1,22 @@
|
|||
.. openlut documentation master file, created by
|
||||
sphinx-quickstart on Wed Jan 18 22:00:18 2017.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to openlut's documentation!
|
||||
===================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
openlut
|
||||
=======
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
openlut
|
|
@ -0,0 +1,22 @@
|
|||
openlut.lib package
|
||||
===================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
openlut.lib.files module
|
||||
------------------------
|
||||
|
||||
.. automodule:: openlut.lib.files
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: openlut.lib
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,77 @@
|
|||
openlut package
|
||||
===============
|
||||
|
||||
Subpackages
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
|
||||
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
|
||||
---------------
|
||||
|
||||
.. automodule:: openlut
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2ad056a1887cd7be2cb2eeddfa2194d092be4fbc3a62e358ae5a15ba0562ba3c
|
||||
size 10596905
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:39123fa7f59cb30f65aabf6801d1ac4642cf829c912de14dcc4a04005bf02cbe
|
||||
size 7789396
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env python3.5
|
||||
|
||||
'''
|
||||
openlut: A package for managing and applying 1D and 3D LUTs.
|
||||
|
||||
Color Management: openlut deals with the raw RGB values, does its work, then puts out images with correct raw RGB values - a no-op.
|
||||
|
||||
Dependencies:
|
||||
-numpy: Like, everything.
|
||||
-wand: Saving/loading images.
|
||||
-scipy - OPTIONAL: For spline interpolation.
|
||||
|
||||
Easily get all deps: sudo pip3 install numpy wand numba scipy
|
||||
|
||||
*Make sure you get the Python 3.X version of these packages!!!
|
||||
|
||||
|
||||
|
||||
LICENCE:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Sofus Rose
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
#~ from lib.files import Log #For Development
|
||||
|
||||
if __name__ == "__main__" :
|
||||
if not sys.argv[1:]: print('Use -t to test!'); exit()
|
||||
|
||||
if sys.argv[1] == '-t' :
|
||||
import tests.suite
|
||||
tests.suite.runTest('img_test', 'testpath')
|
|
@ -0,0 +1,42 @@
|
|||
[editor]
|
||||
line_wrapping=false
|
||||
line_break_column=72
|
||||
auto_continue_multiline=true
|
||||
|
||||
[file_prefs]
|
||||
final_new_line=true
|
||||
ensure_convert_new_lines=false
|
||||
strip_trailing_spaces=false
|
||||
replace_tabs=false
|
||||
|
||||
[indentation]
|
||||
indent_width=4
|
||||
indent_type=1
|
||||
indent_hard_tab_width=8
|
||||
detect_indent=false
|
||||
detect_indent_width=false
|
||||
indent_mode=2
|
||||
|
||||
[project]
|
||||
name=openlut
|
||||
base_path=/home/sofus/subhome/src/openlut
|
||||
description=
|
||||
|
||||
[long line marker]
|
||||
long_line_behaviour=1
|
||||
long_line_column=72
|
||||
|
||||
[files]
|
||||
current_page=0
|
||||
FILE_NAME_0=1995;Python;0;EUTF-8;1;1;0;%2Fhome%2Fsofus%2Fsubhome%2Fsrc%2Fopenlut%2Fopenlut.py;0;4
|
||||
|
||||
[VTE]
|
||||
last_dir=/home/sofus/subhome/src/linemarch
|
||||
|
||||
[prjorg]
|
||||
source_patterns=*.c;*.C;*.cpp;*.cxx;*.c++;*.cc;*.m;
|
||||
header_patterns=*.h;*.H;*.hpp;*.hxx;*.h++;*.hh;
|
||||
ignored_dirs_patterns=.*;CVS;
|
||||
ignored_file_patterns=*.o;*.obj;*.a;*.lib;*.so;*.dll;*.lo;*.la;*.class;*.jar;*.pyc;*.mo;*.gmo;
|
||||
generate_tag_prefs=0
|
||||
external_dirs=
|
755
openlut.py
755
openlut.py
|
@ -1,755 +0,0 @@
|
|||
#!/usr/bin/env python3.5
|
||||
|
||||
'''
|
||||
openlut: A package for managing and applying 1D and 3D LUTs.
|
||||
|
||||
Color Management: openlut deals with the raw RGB values, does its work, then puts out images with correct raw RGB values - a no-op.
|
||||
|
||||
Dependencies:
|
||||
-numpy: Like, everything.
|
||||
-wand: Saving/loading all images.
|
||||
-numba: 38% speedup for matrix math.
|
||||
|
||||
-scipy - OPTIONAL: For spline interpolation.
|
||||
|
||||
Easily get all deps: sudo pip3 install numpy wand numba scipy
|
||||
|
||||
*Make sure you get the Python 3.X version of these packages!!!
|
||||
|
||||
|
||||
|
||||
LICENCE:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Sofus Rose
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
'''
|
||||
|
||||
import sys, os, math, abc, ctypes
|
||||
|
||||
import multiprocessing as mp
|
||||
from functools import reduce
|
||||
import operator as oper
|
||||
|
||||
MOD_SCIPY = False
|
||||
try :
|
||||
from scipy.interpolate import splrep, splev
|
||||
MOD_SCIPY = True
|
||||
except :
|
||||
pass
|
||||
|
||||
import numpy as np
|
||||
import numba
|
||||
|
||||
#~ 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
|
||||
|
||||
from wand.api import library
|
||||
|
||||
#~ library.MagickSetCompressionQuality.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
|
||||
#~ library.MagickSetCompression.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
|
||||
|
||||
#~ COMPRESS_TYPES = dict(zip(wand.image.COMPRESSION_TYPES, tuple(map(ctypes.c_int, range(len(wand.image.COMPRESSION_TYPES))))))
|
||||
|
||||
|
||||
|
||||
#~ from lib.files import Log #For Development
|
||||
|
||||
class Transform :
|
||||
def apply(self, cMap) :
|
||||
"""
|
||||
Applies this transformation to a ColMap.
|
||||
"""
|
||||
return ColMap(self.sample(cMap.asarray()))
|
||||
|
||||
@abc.abstractmethod
|
||||
def sample(self, fSeq) :
|
||||
"""
|
||||
Samples the Transformation.
|
||||
"""
|
||||
|
||||
def spSeq(seq, outLen) :
|
||||
"""
|
||||
Utility function for splitting a sequence into equal parts, for multithreading.
|
||||
"""
|
||||
perfSep = (1/outLen) * len(seq)
|
||||
return list(filter(len, [seq[round(perfSep * i):round(perfSep * (i + 1))] for i in range(len(seq))])) if len(seq) > 1 else seq
|
||||
|
||||
class ColMap :
|
||||
def __init__(self, rgbArr) :
|
||||
self.rgbArr = rgbArr
|
||||
|
||||
def fromIntArray(imgArr) :
|
||||
bitDepth = int(''.join([i for i in str(imgArr.dtype) if i.isdigit()]))
|
||||
return ColMap(np.divide(imgArr.astype(np.float64), 2 ** bitDepth - 1))
|
||||
|
||||
#Operations - returns new ColMaps.
|
||||
def apply(self, transform) :
|
||||
'''
|
||||
Applies a Transform object by running its apply method.
|
||||
'''
|
||||
return transform.apply(self)
|
||||
|
||||
#IO Functions
|
||||
|
||||
def open(path) :
|
||||
'''
|
||||
Opens 8 and 16 bit images of many formats.
|
||||
'''
|
||||
|
||||
try :
|
||||
openFunction = {
|
||||
"exr" : ColMap.openWand,
|
||||
"dpx" : ColMap.openWand,
|
||||
}[path[path.rfind('.') + 1:]]
|
||||
|
||||
return openFunction(path) #Any fancy formats will go here.
|
||||
except :
|
||||
#Fallback to opening using Wand.
|
||||
return ColMap.openWand(path)
|
||||
|
||||
#Vendor-specific open methods.
|
||||
|
||||
#~ def openSci(path) :
|
||||
#~ return ColMap.fromIntArray(si.io.imread(path)[:,:,:3])
|
||||
|
||||
def openWand(path) :
|
||||
'''
|
||||
Open a file using the Wand ImageMagick binding.
|
||||
'''
|
||||
with wand.image.Image(filename=path) as img:
|
||||
#Quick inverse sRGB transform, to undo what Wand did.
|
||||
img.colorspace = 'srgb'
|
||||
img.transform_colorspace('rgb')
|
||||
|
||||
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))
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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 .
|
||||
'''
|
||||
if depth is None: depth = 16
|
||||
try :
|
||||
saveFunction = {
|
||||
"exr" : self.saveWand,
|
||||
"dpx" : self.saveWand,
|
||||
"tif" : self.saveWand,
|
||||
"tiff": self.saveWand
|
||||
}[path[path.rfind('.') + 1:]]
|
||||
|
||||
return saveFunction(path, compress, depth)
|
||||
except :
|
||||
#Fallback to saving using Wand.
|
||||
self.saveWand(path, compress, depth)
|
||||
|
||||
#Vendor-specific save methods
|
||||
|
||||
def saveWand(self, path, compress = None, depth = 16) :
|
||||
data = self.apply(LUT.lutFunc(Gamma.sRGB)) if path[path.rfind('.')+1:] == 'dpx' else self
|
||||
i = data.asWandImg(depth)
|
||||
|
||||
i.colorspace = 'srgb' #Make sure it saves without a colorspace transformation.
|
||||
|
||||
#~ if compress :
|
||||
#~ library.MagickSetCompression(i.wand, 'rle')
|
||||
|
||||
#~ i.compression = 'lzma'
|
||||
#~ i.compression_quality = 80
|
||||
|
||||
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())
|
||||
|
||||
#~ def savePil(self, path, compress = None, depth = 8) :
|
||||
#~ if compress is not None: raise ValueError('Scipy Backend cannot compress the output image!')
|
||||
#~ if depth != 8: raise ValueError('Cannot save non-8 bit image using PIL.')
|
||||
#~ self.asPilImg().save(path)
|
||||
|
||||
|
||||
def show(self) :
|
||||
#~ ColMap.pilShow(self.apply(LUT.lutFunc(Gamma.sRGB)).asPilImg())
|
||||
ColMap.wandShow(self.asWandImg())
|
||||
|
||||
#~ def pilShow(pilImg) :
|
||||
#~ pilImg.show()
|
||||
|
||||
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)
|
||||
|
||||
wandImg.transform_colorspace('rgb') #This transforms it back to linearity.
|
||||
|
||||
|
||||
#Data Form Functions
|
||||
#~ def asPilImg(self) :
|
||||
#~ return Image.fromarray(self.asIntArray(8), mode='RGB')
|
||||
|
||||
def asWandImg(self, depth = 16) :
|
||||
i = wand.image.Image(blob=self.asIntArray(depth).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 asarray(self) :
|
||||
"""
|
||||
Returns the base float array.
|
||||
"""
|
||||
return self.rgbArr
|
||||
|
||||
def asIntArray(self, depth = 16, us = True) :
|
||||
u = 'u' if us else ''
|
||||
return np.multiply(self.rgbArr.clip(0, 1), 2.0 ** depth - 1).astype("{0}int{1}".format(u, depth))
|
||||
|
||||
|
||||
#Overloads
|
||||
def __repr__(self) :
|
||||
return 'ColMap( \n\trgbArr = {0}\n)'.format('\n\t\t'.join([line.strip() for line in repr(self.rgbArr).split('\n')]))
|
||||
|
||||
class LUT(Transform) :
|
||||
def __init__(self, dims = 1, size = 16384, title = "openlut_LUT", array = None, iRange = (0.0, 1.0)) :
|
||||
'''
|
||||
Create an identity LUT with given dimensions (1 or 3), size, and title.
|
||||
'''
|
||||
if array is not None :
|
||||
LUT.lutArray(array, size, dims, title)
|
||||
else :
|
||||
if dims != 1 and dims != 3: raise ValueError("Dimensions must be 1 or 3!")
|
||||
|
||||
self.title = title #The title.
|
||||
self.size = size #The size. 1D LUTs: size numbers. 3D LUTs: size x size x size numbers.
|
||||
self.range = iRange #The input range - creates data or legal LUTs. Should work fine, but untested.
|
||||
self.dims = dims #The dimensions. 1 or 3; others aren't accepted.
|
||||
self.ID = np.linspace(self.range[0], self.range[1], self.size) #Read Only.
|
||||
|
||||
if dims == 1 :
|
||||
self.array = np.linspace(self.range[0], self.range[1], self.size) #Size number of floats.
|
||||
elif dims == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
#~ self.array = np.linspace(self.range[0], self.range[1], self.size**3).reshape(self.size, self.size, self.size) #Should make an identity size x size x size array.
|
||||
|
||||
def lutFunc(func, size = 16384, dims = 1, title="openlut_FuncGen") :
|
||||
'''
|
||||
Creates a LUT from a simple function.
|
||||
'''
|
||||
if dims == 1 :
|
||||
lut = LUT(dims=dims, size=size, title=title)
|
||||
|
||||
vFunc = np.vectorize(func, otypes=[np.float])
|
||||
lut.array = vFunc(lut.array)
|
||||
|
||||
return lut
|
||||
elif dims == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
|
||||
def lutArray(array, title="Array_Generated") :
|
||||
'''
|
||||
Creates a LUT from a float array. Elements must be in range [0, 1].
|
||||
'''
|
||||
if len(np.shape(array)) == 1 :
|
||||
lut = LUT(dims=1, size=len(array), title=title)
|
||||
lut.array = array
|
||||
|
||||
return lut
|
||||
elif len(np.shape(array)) == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
else :
|
||||
raise ValueError("lutArray input must be 1D or 3D!")
|
||||
|
||||
#LUT Functions.
|
||||
def __interp(q, cpu, spSeq, ID, array, spl) :
|
||||
if spl :
|
||||
q.put( (cpu, splev(spSeq, splrep(ID, array))) ) #Spline Interpolation. Pretty quick, considering.
|
||||
else :
|
||||
q.put( (cpu, np.interp(spSeq, ID, array)) )
|
||||
|
||||
def sample(self, fSeq, spl=True) :
|
||||
'''
|
||||
Sample the LUT using a flat float sequence (ideally a numpy array; (0..1) ).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
'''
|
||||
|
||||
fSeq = np.array(fSeq)
|
||||
if self.dims == 1 :
|
||||
#~ return np.interp(spSeq, self.ID, self.array)
|
||||
|
||||
#If scipy isn't loaded, we can't use spline interpolation!
|
||||
if (not MOD_SCIPY) or self.size > 1023: spl = False # Auto-adapts big LUTs to use the faster, more brute-forceish, linear interpolation.
|
||||
#~ spl = True
|
||||
out = []
|
||||
q = mp.Queue()
|
||||
splt = Transform.spSeq(fSeq, mp.cpu_count())
|
||||
for cpu in range(mp.cpu_count()) :
|
||||
p = mp.Process(target=LUT.__interp, args=(q, cpu, splt[cpu], self.ID, self.array, spl))
|
||||
p.start()
|
||||
|
||||
for num in range(len(splt)) :
|
||||
out.append(q.get())
|
||||
|
||||
return np.concatenate([seq[1] for seq in sorted(out, key=lambda seq: seq[0])], axis=0)
|
||||
|
||||
elif self.dims == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
|
||||
def resized(self, newSize) :
|
||||
if newSize == self.size: return self
|
||||
|
||||
fac = newSize / self.size
|
||||
|
||||
useSpl = self.size < newSize #If the new size is lower, we use Linear interpolation. If the new size is higher, we use Spline interpolation.
|
||||
if self.size < 128: useSpl = True #If the current size is too low, use spline regardless.
|
||||
|
||||
if self.dims == 1 :
|
||||
newID = np.linspace(self.range[0], self.range[1], newSize)
|
||||
return LUT.lutArray(self.sample(newID, spl=useSpl), title="Resized to {0}".format(newSize))
|
||||
if self.dims == 3 :
|
||||
print("3D LUT Not Implemented")
|
||||
|
||||
#IO Functions.
|
||||
|
||||
def open(path) :
|
||||
'''
|
||||
Opens any supported file format, located at path.
|
||||
'''
|
||||
openFunction = {
|
||||
"cube" : LUT.openCube,
|
||||
}[path[path.rfind('.') + 1:]]
|
||||
|
||||
return openFunction(path)
|
||||
|
||||
def openCube(path) :
|
||||
'''
|
||||
Opens .cube files. They must be saved with whitespaces. Referenced by open().
|
||||
'''
|
||||
lut = LUT() #Mutable luts are not reccommended for users.
|
||||
|
||||
with open(path, 'r') as f :
|
||||
i = 0
|
||||
for line in f :
|
||||
#~ if not line.strip(): continue
|
||||
sLine = line.strip()
|
||||
if not sLine: continue
|
||||
|
||||
if sLine[0] == '#': continue
|
||||
|
||||
index = sLine[:sLine.find(' ')]
|
||||
data = sLine[sLine.find(' ') + 1:]
|
||||
|
||||
if index == "TITLE": lut.title = data.strip('"'); continue
|
||||
if index == "LUT_1D_SIZE": lut.dims = 1; lut.size = int(data); continue
|
||||
if index == "LUT_3D_SIZE": lut.dims = 3; lut.size = int(data); continue
|
||||
|
||||
if index == "LUT_1D_INPUT_RANGE": lut.range = (float(data[:data.find(' ')]), float(data[data.find(' ') + 1:])); continue
|
||||
|
||||
if lut.dims == 1 and sLine[:sLine.find(' ')] :
|
||||
lut.array[i] = float(sLine[:sLine.find(' ')])
|
||||
i += 1
|
||||
elif lut.dims == 3 :
|
||||
print("3D LUT Not Implemened!")
|
||||
|
||||
return lut
|
||||
|
||||
def save(self, path) :
|
||||
'''
|
||||
Method that saves the LUT in a supported format, based on the path.
|
||||
'''
|
||||
saveFunction = {
|
||||
"cube" : self.saveCube,
|
||||
|
||||
|
||||
}[path[path.rfind('.') + 1:]]
|
||||
|
||||
saveFunction(path)
|
||||
|
||||
def saveCube(self, path) :
|
||||
with open(path, 'w') as f :
|
||||
print('TITLE', '"{}"'.format(self.title), file=f)
|
||||
|
||||
if self.dims == 1 :
|
||||
print('LUT_1D_SIZE', '{}'.format(self.size), file=f)
|
||||
print('LUT_1D_INPUT_RANGE', '{0:.6f} {1:.6f}'.format(*self.range), file=f)
|
||||
print('# Created by openlut.\n', file=f)
|
||||
|
||||
for itm in self.array :
|
||||
entry = '{0:.6f}'.format(itm)
|
||||
print(entry, entry, entry, file=f)
|
||||
elif self.dims == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
|
||||
#Overloaded functions
|
||||
|
||||
def __iter__(self) :
|
||||
if dims == 1 :
|
||||
return iter(self.array)
|
||||
elif dims == 3 :
|
||||
iArr = self.array.reshape(self.dims, self.size / self.dims) #Group into triplets.
|
||||
return iter(iArr)
|
||||
|
||||
def __getitem__(self, key) :
|
||||
return self.sample(key)
|
||||
|
||||
def __repr__(self) :
|
||||
return 'LUT(\tdims = {0},\n\tsize = {1},\n\ttitle = "{2}"\n\tarray = {3}\n)'.format(self.dims, self.size, self.title, '\n\t\t'.join([line.strip() for line in repr(self.array).split('\n')]))
|
||||
|
||||
class Gamma(Transform) :
|
||||
def __init__(self, func) :
|
||||
self.func = func
|
||||
|
||||
#Gamma Methods
|
||||
def __gamma(q, cpu, f, spSeq) :
|
||||
q.put( (cpu, f(spSeq)) )
|
||||
|
||||
def sample(self, fSeq) :
|
||||
fSeq = np.array(fSeq)
|
||||
fVec = np.vectorize(self.func)
|
||||
|
||||
out = []
|
||||
q = mp.Queue()
|
||||
splt = Transform.spSeq(fSeq, mp.cpu_count())
|
||||
for cpu in range(mp.cpu_count()) :
|
||||
p = mp.Process(target=Gamma.__gamma, args=(q, cpu, fVec, splt[cpu]))
|
||||
p.start()
|
||||
|
||||
for num in range(len(splt)) :
|
||||
out.append(q.get())
|
||||
|
||||
return np.concatenate([seq[1] for seq in sorted(out, key=lambda seq: seq[0])], axis=0) if len(fSeq) > 1 else self.func(fSeq[0])
|
||||
|
||||
return fVec(fSeq) if len(fSeq) > 1 else self.func(fSeq[0])
|
||||
|
||||
#Static Gamma Functions (partly adapted from MLRawViewer)
|
||||
|
||||
def lin(x): return x
|
||||
|
||||
def sRGB(x) :
|
||||
'''
|
||||
sRGB formula. Domain must be within [0, 1].
|
||||
'''
|
||||
return ( (1.055) * (x ** (1.0 / 2.4)) ) - 0.055 if x > 0.0031308 else x * 12.92
|
||||
def sRGBinv(x) :
|
||||
'''
|
||||
Inverse sRGB formula. Domain must be within [0, 1].
|
||||
'''
|
||||
return ((x + 0.055) / 1.055) ** 2.4 if x > 0.04045 else x / 12.92
|
||||
|
||||
def Rec709(x) :
|
||||
'''
|
||||
Rec709 formula. Domain must be within [0, 1].
|
||||
'''
|
||||
return 1.099 * (x ** 0.45) - 0.099 if x >= 0.018 else 4.5 * x
|
||||
|
||||
def ReinhardHDR(x) :
|
||||
'''
|
||||
Reinhard Tonemapping formula. Domain must be within [0, 1].
|
||||
'''
|
||||
return x / (1.0 + x)
|
||||
|
||||
def sLog(x) :
|
||||
'''
|
||||
sLog 1 formula. Domain must be within [0, 1]. See https://pro.sony.com/bbsccms/assets/
|
||||
files/mkt/cinema/solutions/slog_manual.pdf .
|
||||
'''
|
||||
return ( 0.432699 * math.log(x + 0.037584, 10.0) + 0.616596) + 0.03
|
||||
|
||||
def sLog2(x) :
|
||||
'''
|
||||
sLog2 formula. Domain must be within [0, 1]. See https://pro.sony.com/bbsccms/assets/files/micro/dmpc/training/S-Log2_Technical_PaperV1_0.pdf .
|
||||
'''
|
||||
return ( 0.432699 * math.log( (155.0 * x) / 219.0 + 0.037584, 10.0) + 0.616596 ) + 0.03
|
||||
|
||||
def sLog3(x) :
|
||||
'''
|
||||
Not yet implemented. See http://community.sony.com/sony/attachments/sony/large-sensor-camera-F5-F55/12359/2/TechnicalSummary_for_S-Gamut3Cine_S-Gamut3_S-Log3_V1_00.pdf .
|
||||
'''
|
||||
return x
|
||||
|
||||
def DanLog(x) :
|
||||
return (10.0 ** ((x - 0.385537) / 0.2471896) - 0.071272) / 3.555556 if x > 0.1496582 else (x - 0.092809) / 5.367655
|
||||
|
||||
def DanLoginv(x) :
|
||||
pass
|
||||
|
||||
|
||||
class TransMat(Transform) :
|
||||
def __init__(self, *mats) :
|
||||
'''
|
||||
Initializes a combined 3x3 Transformation Matrix from any number of input matrices. These may be numpy arrays, matrices,
|
||||
other TransMats, or any combination thereof.
|
||||
'''
|
||||
if len(mats) == 1 :
|
||||
mat = mats[0]
|
||||
|
||||
if isinstance(mat, TransMat) :
|
||||
self.mat = mat.mat #Support a copy constructor.
|
||||
else :
|
||||
self.mat = np.array(mat) #Simply set self.mat with the numpy array version of the mat.
|
||||
elif len(mats) > 1 :
|
||||
self.mat = TransMat.__mats(*[TransMat(mat) for mat in mats]).mat
|
||||
elif not mats :
|
||||
self.mat = np.identity(3)
|
||||
|
||||
def __mats(*inMats) :
|
||||
'''
|
||||
Initialize a combined Transform matrix from several input TransMats.
|
||||
'''
|
||||
return TransMat(reduce(TransMat.__mul__, reversed(inMats))) #Works because multiply is actually non-commutative dot.
|
||||
#This is why we reverse inMats.
|
||||
|
||||
@numba.jit(nopython=True)
|
||||
def __optDot(img, mat, shp, out) :
|
||||
shaped = img.reshape((shp[0] * shp[1], shp[2])) #Flatten to 2D array for iteration over colors.
|
||||
i = 0
|
||||
while i < shp[0] * shp[1] :
|
||||
res = np.dot(mat, shaped[i])
|
||||
out[i] = res
|
||||
i += 1
|
||||
|
||||
def __applMat(q, cpu, shp, mat, img3D) :
|
||||
out = np.zeros((shp[0] * shp[1], shp[2]))
|
||||
TransMat.__optDot(img3D, mat, shp, out)
|
||||
q.put( (cpu, out.reshape(shp)) )
|
||||
|
||||
def sample(self, fSeq) :
|
||||
shp = np.shape(fSeq)
|
||||
if len(shp) == 1 :
|
||||
return self.mat.dot(fSeq)
|
||||
if len(shp) == 3 :
|
||||
cpus = mp.cpu_count()
|
||||
out = []
|
||||
q = mp.Queue()
|
||||
splt = Transform.spSeq(fSeq, cpus)
|
||||
for cpu in range(cpus) :
|
||||
p = mp.Process(target=TransMat.__applMat, args=(q, cpu, np.shape(splt[cpu]), self.mat, splt[cpu]))
|
||||
p.start()
|
||||
|
||||
for num in range(len(splt)) :
|
||||
out.append(q.get())
|
||||
|
||||
return np.concatenate([seq[1] for seq in sorted(out, key=lambda seq: seq[0])], axis=0)
|
||||
|
||||
#~ out = np.zeros((shp[0] * shp[1], shp[2]))
|
||||
#~ TransMat.__optDot(fSeq, self.mat, shp, out)
|
||||
#~ return out.reshape(shp)
|
||||
|
||||
#~ return np.array([self.mat.dot(col) for col in fSeq.reshape(shp[0] * shp[1], shp[2])]).reshape(shp)
|
||||
|
||||
#~ p = mp.Pool(mp.cpu_count())
|
||||
#~ return np.array(list(map(self.mat.dot, fSeq.reshape(shp[0] * shp[1], shp[2])))).reshape(shp)
|
||||
#~ return fSeq.dot(self.mat)
|
||||
|
||||
def inv(obj) :
|
||||
if isinstance(obj, TransMat) : #Works on any TransMat object - including self.
|
||||
return TransMat(np.linalg.inv(obj.mat))
|
||||
else : #Works on raw numpy arrays as well.
|
||||
return np.linalg.inv(obj)
|
||||
|
||||
def transpose(self) :
|
||||
return TransMat(np.transpose(self.mat))
|
||||
|
||||
#Overloading
|
||||
def __mul__(self, other) :
|
||||
'''
|
||||
* implements matrix multiplication.
|
||||
'''
|
||||
if isinstance(other, TransMat) :
|
||||
return TransMat(self.mat.dot(other.mat))
|
||||
elif isinstance(other, float) or isinstance(other, int) :
|
||||
return TransMat(np.multiply(self.mat, other))
|
||||
elif isinstance(other, np.ndarray) or isinstance(other, np.matrixlib.defmatrix.matrix) :
|
||||
return TransMat(self.mat.dot(np.array(other)))
|
||||
else :
|
||||
raise ValueError('Invalid multiplication arguments!')
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __add__(self, other) :
|
||||
if isinstance(other, TransMat) :
|
||||
return TransMat(self.mat + other.mat)
|
||||
elif isinstance(other, np.ndarray) or isinstance(other, np.matrixlib.defmatrix.matrix) :
|
||||
return TransMat(self.mat + np.array(other))
|
||||
else :
|
||||
raise ValueError('Invalid addition arguments!')
|
||||
|
||||
__radd__ = __add__
|
||||
|
||||
def __pow__(self, other) :
|
||||
'''
|
||||
** implements direct multiplication. You usually don't want this.
|
||||
'''
|
||||
if isinstance(other, TransMat) :
|
||||
return TransMat(self.mat * other.mat)
|
||||
elif isinstance(other, float) or isinstance(other, int) :
|
||||
return TransMat(np.multiply(self.mat, other))
|
||||
elif isinstance(other, np.ndarray) or isinstance(other, np.matrixlib.defmatrix.matrix) :
|
||||
return TransMat(self.mat * np.array(other))
|
||||
else :
|
||||
raise ValueError('Invalid multiplication arguments!')
|
||||
|
||||
def __invert__(self) :
|
||||
return self.inv()
|
||||
|
||||
def __len__(self) :
|
||||
return len(self.mat)
|
||||
|
||||
def __getitem__(self, key) :
|
||||
return self.mat[key]
|
||||
|
||||
def __iter__(self) :
|
||||
return iter(self.mat)
|
||||
|
||||
def __repr__(self) :
|
||||
return "\nTransMat (\n{0} )\n".format(str(self.mat))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#Static Transmat Matrices -- all go from ACES to <gamut name>. <gamut name>inv functions go from the gamut to ACES.
|
||||
#Converted (CIECAT02) D65 Illuminant for all.
|
||||
|
||||
XYZ = np.array(
|
||||
[
|
||||
0.93863095, -0.00574192, 0.0175669,
|
||||
0.33809359, 0.7272139, -0.0653075,
|
||||
0.00072312, 0.00081844, 1.08751619
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
XYZinv = np.array(
|
||||
[
|
||||
1.06236611, 0.00840695, -0.01665579,
|
||||
-0.49394137, 1.37110953, 0.09031659,
|
||||
-0.00033467, -0.00103746, 0.91946965
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
sRGB = np.array(
|
||||
[
|
||||
2.52193473, -1.1370239, -0.38491083,
|
||||
-0.27547943, 1.36982898, -0.09434955,
|
||||
-0.01598287, -0.14778923, 1.1637721
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
sRGBinv = np.array(
|
||||
[
|
||||
0.43957568, 0.38391259, 0.17651173,
|
||||
0.08960038, 0.81471415, 0.09568546,
|
||||
0.01741548, 0.10873435, 0.87385017
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
aRGB = np.array(
|
||||
[
|
||||
1.72502307, -0.4228857, -0.30213736,
|
||||
-0.27547943, 1.36982898, -0.09434955,
|
||||
-0.02666425, -0.08532111, 1.11198537
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
aRGBinv = np.array(
|
||||
[
|
||||
0.61468318, 0.20122762, 0.1840892,
|
||||
0.12529321, 0.77491365, 0.09979314,
|
||||
0.02435304, 0.06428329, 0.91136367
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__" :
|
||||
if not sys.argv: print('Use -t to test!')
|
||||
|
||||
if sys.argv[1] == '-t' :
|
||||
print('Open openlut.py and scroll down to the end to see the code that\'s working!')
|
||||
#Open any format image. Try it with exr/dpx/anything!
|
||||
img = ColMap.open('testpath/test.exr') #Opens a test image 'test.exr', creating a ColMap object, automatically using the best image backend available to load the image at the correct bit depth.
|
||||
|
||||
'''
|
||||
Gamma has gamma functions like Gamma.sRGB, called by value like Gamma.sRGB(val). All take one argument, the value (x), and returns the transformed value. Color doesn't matter for gamma.
|
||||
TransMat has matrices, in 3x3 numpy array form. All are relative to ACES, with direction aptly named. So, TransMat.XYZ is a matrix from ACES --> XYZ, while TransMat.XYZinv goes from XYZ --> ACES. All use/are converted to the D65 illuminant, for consistency sake.
|
||||
'''
|
||||
|
||||
#Gamma Functions: sRGB --> Linear.
|
||||
gFunc = Gamma(Gamma.sRGBinv) #A Gamma Transform object using the sRGB-->Linear gamma formula. Apply to ColMaps!
|
||||
gFuncManualsRGB = Gamma(lambda val: ((val + 0.055) / 1.055) ** 2.4 if val > 0.04045 else val / 12.92) #It's generic - specify any gamma function, even inline with a lambda!
|
||||
|
||||
#LUT from Function: sRGB --> Linear
|
||||
oLut = LUT.lutFunc(Gamma.sRGBinv) #A LUT Transform object, created from a gamma function. Size is 16384 by default. LUTs are faster!
|
||||
oLut.save('testpath/sRGB-->Lin.cube') #Saves the LUT to a format inferred from the extension. cube only for now!
|
||||
|
||||
#Opening LUTs from .cube files.
|
||||
lut = LUT.open('testpath/sRGB-->Lin.cube') #Opens the lut we just made into a different LUT object.
|
||||
lut.resized(17).save('testpath/sRGB-->Lin_tiny.cube') #Resizes the LUT, then saves it again to a much smaller file!
|
||||
|
||||
#Matrix Transformations
|
||||
simpleMat = TransMat(TransMat.sRGBinv) #A Matrix Transform (TransMat) object, created from a color transform matrix for gamut transformations! This one is sRGB --> ACES.
|
||||
mat = TransMat(TransMat.sRGBinv, TransMat.XYZ, TransMat.XYZinv, TransMat.aRGB) * TransMat.aRGBinv
|
||||
#Indeed, specify many matrices which auto-multiply into a single one! You can also combine them after, with simple multiplication.
|
||||
|
||||
#Applying and saving.
|
||||
img.apply(gFunc).save('testpath/openlut_gammafunc.png') #save saves an image using the appropriate image backend, based on the extension.
|
||||
img.apply(lut).save('testpath/openlut_lut-lin-16384.png') #apply applies any color transformation object that inherits from Transform - LUT, Gamma, TransMat, etc., or make your own! It's easy ;) .
|
||||
img.apply(lut.resized(17)).save('testpath/openlut_lut-lin-17.png') #Why so small? Because spline interpolation automatically turns on. It's identical to the larger LUT!
|
||||
img.apply(mat).save('testpath/openlut_mat.png') #Applies the gamut transformation.
|
||||
|
||||
#As a proof of concept, here's a long list of transformations that should, in sum, do nothing :) :
|
||||
|
||||
img.apply(lut).apply(LUT.lutFunc(Gamma.sRGB)).apply(mat).apply(~mat).save('testpath/openlut_noop.png') #~mat is the inverse of mat. Easily undo the gamut operation!
|
||||
|
||||
#Format Test: All output images are in Linear ACES.
|
||||
tImg = img.apply(mat)
|
||||
tImg.save('testpath/output.exr')
|
||||
tImg.save('testpath/output.dpx')
|
||||
tImg.save('testpath/output.png')
|
||||
tImg.save('testpath/output.jpg')
|
||||
tImg.save('testpath/output.tif') #All sorts of formats work! Bit depth is 16, unless you say something else.
|
||||
|
||||
#Compression is impossible right now - wand is being difficult.
|
||||
#Keep in mind, values are clipped from 0 to 1 when done. Scary transforms can make this an issue!
|
||||
|
||||
#Color management is simple: openlut doesn't touch your data, unless you tell it to with a Transform. So, the data that goes in, goes out, unless a Transform was applied.
|
|
@ -0,0 +1,191 @@
|
|||
import sys, os, os.path
|
||||
|
||||
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
|
||||
|
||||
from wand.api import library
|
||||
|
||||
#~ library.MagickSetCompressionQuality.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
|
||||
#~ library.MagickSetCompression.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
|
||||
|
||||
#~ COMPRESS_TYPES = dict(zip(wand.image.COMPRESSION_TYPES, tuple(map(ctypes.c_int, range(len(wand.image.COMPRESSION_TYPES))))))
|
||||
|
||||
from . import gamma
|
||||
from .LUT import LUT
|
||||
from .Viewer import Viewer
|
||||
|
||||
class ColMap :
|
||||
def __init__(self, rgbArr) :
|
||||
self.rgbArr = np.array(rgbArr, dtype=np.float32) #Enforce 32 bit floats. Save memory.
|
||||
|
||||
def fromIntArray(imgArr) :
|
||||
bitDepth = int(''.join([i for i in str(imgArr.dtype) if i.isdigit()]))
|
||||
return ColMap(np.divide(imgArr.astype(np.float64), 2 ** bitDepth - 1))
|
||||
|
||||
#Operations - returns new ColMaps.
|
||||
def apply(self, transform) :
|
||||
'''
|
||||
Applies a Transform object by running its apply method.
|
||||
'''
|
||||
#~ return transform.apply(self)
|
||||
return ColMap(transform.sample(self.asarray()))
|
||||
|
||||
#IO Functions
|
||||
@staticmethod
|
||||
def open(path) :
|
||||
'''
|
||||
Opens 8 and 16 bit images of many formats.
|
||||
'''
|
||||
|
||||
try :
|
||||
openFunction = {
|
||||
"exr" : ColMap.openWand,
|
||||
"dpx" : ColMap.openWand,
|
||||
}[path[path.rfind('.') + 1:]]
|
||||
|
||||
return openFunction(path) #Any fancy formats will go here.
|
||||
except :
|
||||
#Fallback to opening using Wand.
|
||||
return ColMap.openWand(path)
|
||||
|
||||
#Vendor-specific open methods.
|
||||
|
||||
#~ def openSci(path) :
|
||||
#~ return ColMap.fromIntArray(si.io.imread(path)[:,:,:3])
|
||||
|
||||
@staticmethod
|
||||
def openWand(path) :
|
||||
'''
|
||||
Open a file using the Wand ImageMagick binding.
|
||||
'''
|
||||
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' :
|
||||
img.colorspace = 'srgb'
|
||||
img.transform_colorspace('rgb')
|
||||
|
||||
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))
|
||||
|
||||
@staticmethod
|
||||
def toBinary(self, fmt, depth=16) :
|
||||
'''
|
||||
Using Wand blob functionality
|
||||
'''
|
||||
with self.asWandImg(depth) as img :
|
||||
img.format = fmt
|
||||
return img.make_blob()
|
||||
|
||||
@staticmethod
|
||||
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.
|
||||
|
||||
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 .
|
||||
'''
|
||||
if depth is None: depth = 16
|
||||
try :
|
||||
saveFunction = {
|
||||
"exr" : self.saveWand,
|
||||
"dpx" : self.saveWand,
|
||||
"tif" : self.saveWand,
|
||||
"tiff": self.saveWand
|
||||
}[path[path.rfind('.') + 1:]]
|
||||
|
||||
return saveFunction(path, compress, depth)
|
||||
except :
|
||||
#Fallback to saving using Wand.
|
||||
self.saveWand(path, compress, depth)
|
||||
|
||||
#Vendor-specific save methods
|
||||
|
||||
def saveWand(self, path, compress = None, depth = 16) :
|
||||
data = self.apply(LUT.lutFunc(gamma.sRGB)) if path[path.rfind('.')+1:] == 'dpx' else self
|
||||
i = data.asWandImg(depth)
|
||||
|
||||
i.colorspace = 'srgb' #Make sure it saves without a colorspace transformation.
|
||||
|
||||
#~ if compress :
|
||||
#~ library.MagickSetCompression(i.wand, 'rle')
|
||||
|
||||
#~ i.compression = 'lzma'
|
||||
#~ i.compression_quality = 80
|
||||
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def display(path, width = 1200) :
|
||||
'''
|
||||
Shows an image at a path without making a ColMap.
|
||||
'''
|
||||
|
||||
img = ColMap.open(path).rgbArr
|
||||
aspectRatio = img.shape[0]/img.shape[1]
|
||||
|
||||
xRes = width
|
||||
yRes = int(xRes * aspectRatio)
|
||||
|
||||
Viewer.run(img, xRes, yRes, title = os.path.basename(path))
|
||||
|
||||
def show(self, width = 1200) :
|
||||
#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')
|
||||
|
||||
wand.display.display(wandImg)
|
||||
|
||||
wandImg.transform_colorspace('rgb') #This transforms it back to linearity.
|
||||
|
||||
|
||||
#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.colorspace = 'rgb' #Specify, to Wand, that this image is to be treated as raw, linear, data.
|
||||
|
||||
return i
|
||||
|
||||
def asarray(self) :
|
||||
"""
|
||||
Returns the base float array.
|
||||
"""
|
||||
return self.rgbArr
|
||||
|
||||
def asIntArray(self, depth = 16, us = True) :
|
||||
u = 'u' if us else ''
|
||||
return np.multiply(self.rgbArr.clip(0, 1), 2.0 ** depth - 1).astype("{0}int{1}".format(u, depth))
|
||||
|
||||
|
||||
#Overloads
|
||||
def __repr__(self) :
|
||||
return 'ColMap( \n\trgbArr = {0}\n)'.format('\n\t\t'.join([line.strip() for line in repr(self.rgbArr).split('\n')]))
|
|
@ -0,0 +1,148 @@
|
|||
import multiprocessing as mp
|
||||
from functools import reduce
|
||||
import operator as oper
|
||||
|
||||
import numpy as np
|
||||
import numba
|
||||
|
||||
from .Transform import Transform
|
||||
|
||||
class ColMat(Transform) :
|
||||
def __init__(self, *mats) :
|
||||
'''
|
||||
Initializes a combined 3x3 Transformation Matrix from any number of input matrices. These may be numpy arrays, matrices,
|
||||
other ColMats, or any combination thereof.
|
||||
'''
|
||||
if len(mats) == 1 :
|
||||
mat = mats[0]
|
||||
|
||||
if isinstance(mat, ColMat) :
|
||||
self.mat = mat.mat #Support a copy constructor.
|
||||
else :
|
||||
self.mat = np.array(mat) #Simply set self.mat with the numpy array version of the mat.
|
||||
elif len(mats) > 1 :
|
||||
self.mat = ColMat.__mats(*[ColMat(mat) for mat in mats]).mat
|
||||
elif not mats :
|
||||
self.mat = np.identity(3)
|
||||
|
||||
def __mats(*inMats) :
|
||||
'''
|
||||
Initialize a combined Transform matrix from several input ColMats.
|
||||
'''
|
||||
return ColMat(reduce(ColMat.__mul__, reversed(inMats))) #Works because multiply is actually non-commutative dot.
|
||||
#This is why we reverse inMats.
|
||||
|
||||
@numba.jit(nopython=True)
|
||||
def __optDot(img, mat, shp, out) :
|
||||
'''
|
||||
Dots the matrix with each tuple of colors in the img.
|
||||
|
||||
img: Numpy array of shape (height, width, 3).
|
||||
mat: The 3x3 numpy array representing the color transform matrix.
|
||||
shp: The shape of the image.
|
||||
out: the output list. Built mutably for numba's sake.
|
||||
'''
|
||||
shaped = img.reshape((shp[0] * shp[1], shp[2])) #Flatten to 2D array for iteration over colors.
|
||||
i = 0
|
||||
while i < shp[0] * shp[1] :
|
||||
res = np.dot(mat, shaped[i])
|
||||
out[i] = res
|
||||
i += 1
|
||||
|
||||
def __applMat(q, cpu, shp, mat, img3D) :
|
||||
out = np.zeros((shp[0] * shp[1], shp[2]))
|
||||
ColMat.__optDot(img3D, mat, shp, out)
|
||||
q.put( (cpu, out.reshape(shp)) )
|
||||
|
||||
def sample(self, fSeq) :
|
||||
shp = np.shape(fSeq)
|
||||
if len(shp) == 1 :
|
||||
return self.mat.dot(fSeq)
|
||||
if len(shp) == 3 :
|
||||
cpus = mp.cpu_count()
|
||||
out = []
|
||||
q = mp.Queue()
|
||||
splt = Transform.spSeq(fSeq, cpus)
|
||||
for cpu in range(cpus) :
|
||||
p = mp.Process(target=ColMat.__applMat, args=(q, cpu, np.shape(splt[cpu]), self.mat, splt[cpu]))
|
||||
p.start()
|
||||
|
||||
for num in range(len(splt)) :
|
||||
out.append(q.get())
|
||||
|
||||
return np.concatenate([seq[1] for seq in sorted(out, key=lambda seq: seq[0])], axis=0)
|
||||
|
||||
#~ out = np.zeros((shp[0] * shp[1], shp[2]))
|
||||
#~ ColMat.__optDot(fSeq, self.mat, shp, out)
|
||||
#~ return out.reshape(shp)
|
||||
|
||||
#~ return np.array([self.mat.dot(col) for col in fSeq.reshape(shp[0] * shp[1], shp[2])]).reshape(shp)
|
||||
|
||||
#~ p = mp.Pool(mp.cpu_count())
|
||||
#~ return np.array(list(map(self.mat.dot, fSeq.reshape(shp[0] * shp[1], shp[2])))).reshape(shp)
|
||||
#~ return fSeq.dot(self.mat)
|
||||
|
||||
def inv(obj) :
|
||||
if isinstance(obj, ColMat) : #Works on any ColMat object - including self.
|
||||
return ColMat(np.linalg.inv(obj.mat))
|
||||
else : #Works on raw numpy arrays as well.
|
||||
return np.linalg.inv(obj)
|
||||
|
||||
def transpose(self) :
|
||||
return ColMat(np.transpose(self.mat))
|
||||
|
||||
#Overloading
|
||||
def __mul__(self, other) :
|
||||
'''
|
||||
* implements matrix multiplication.
|
||||
'''
|
||||
if isinstance(other, ColMat) :
|
||||
return ColMat(self.mat.dot(other.mat))
|
||||
elif isinstance(other, float) or isinstance(other, int) :
|
||||
return ColMat(np.multiply(self.mat, other))
|
||||
elif isinstance(other, np.ndarray) or isinstance(other, np.matrixlib.defmatrix.matrix) :
|
||||
return ColMat(self.mat.dot(np.array(other)))
|
||||
else :
|
||||
raise ValueError('Invalid multiplication arguments!')
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __add__(self, other) :
|
||||
if isinstance(other, ColMat) :
|
||||
return ColMat(self.mat + other.mat)
|
||||
elif isinstance(other, np.ndarray) or isinstance(other, np.matrixlib.defmatrix.matrix) :
|
||||
return ColMat(self.mat + np.array(other))
|
||||
else :
|
||||
raise ValueError('Invalid addition arguments!')
|
||||
|
||||
__radd__ = __add__
|
||||
|
||||
def __pow__(self, other) :
|
||||
'''
|
||||
** implements direct multiplication. You usually don't want this.
|
||||
'''
|
||||
if isinstance(other, ColMat) :
|
||||
return ColMat(self.mat * other.mat)
|
||||
elif isinstance(other, float) or isinstance(other, int) :
|
||||
return ColMat(np.multiply(self.mat, other))
|
||||
elif isinstance(other, np.ndarray) or isinstance(other, np.matrixlib.defmatrix.matrix) :
|
||||
return ColMat(self.mat * np.array(other))
|
||||
else :
|
||||
raise ValueError('Invalid multiplication arguments!')
|
||||
|
||||
def __invert__(self) :
|
||||
return self.inv()
|
||||
|
||||
def __len__(self) :
|
||||
return len(self.mat)
|
||||
|
||||
def __getitem__(self, key) :
|
||||
return self.mat[key]
|
||||
|
||||
def __iter__(self) :
|
||||
return iter(self.mat)
|
||||
|
||||
def __repr__(self) :
|
||||
return "\nColMat (\n{0} )\n".format(str(self.mat))
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import multiprocessing as mp
|
||||
import types
|
||||
from functools import reduce
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .Transform import Transform
|
||||
from .lib import olOpt as olo
|
||||
|
||||
class Func(Transform) :
|
||||
def __init__(self, func) :
|
||||
self.func = func
|
||||
|
||||
#Func Methods
|
||||
def __gamma(q, cpu, f, spSeq) :
|
||||
q.put( (cpu, f(spSeq)) )
|
||||
|
||||
def sample(self, fSeq) :
|
||||
fSeq = np.array(fSeq, dtype=np.float32) #Just some type assurances.
|
||||
|
||||
# Any float-returning C++ functions can be threaded with olo.gam(), but because of GIL, it won't work with Python functions.
|
||||
if isinstance(self.func, types.BuiltinFunctionType) :
|
||||
# \/ Just olo.gam, except fSeq is flattened to a 1D array, processed flat, then shaped back into a 3D array on the fly.
|
||||
return olo.gam(fSeq.reshape(reduce(lambda a, b: a*b, fSeq.shape)), self.func).reshape(fSeq.shape) #OpenMP vectorized C++ motherfuckery!
|
||||
else :
|
||||
#We always have the slow af fallback.
|
||||
fVec = np.vectorize(self.func)
|
||||
|
||||
out = []
|
||||
q = mp.Queue()
|
||||
splt = Transform.spSeq(fSeq, mp.cpu_count())
|
||||
for cpu in range(mp.cpu_count()) :
|
||||
p = mp.Process(target=Func.__gamma, args=(q, cpu, fVec, splt[cpu]))
|
||||
p.start()
|
||||
|
||||
for num in range(len(splt)) :
|
||||
out.append(q.get())
|
||||
|
||||
return np.concatenate([seq[1] for seq in sorted(out, key=lambda seq: seq[0])], axis=0) if len(fSeq) > 1 else self.func(fSeq[0])
|
||||
|
||||
#~ return fVec(fSeq) if len(fSeq) > 1 else self.func(fSeq[0])
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
import multiprocessing as mp
|
||||
from functools import reduce
|
||||
import types
|
||||
|
||||
import numpy as np
|
||||
|
||||
MOD_SCIPY = False
|
||||
try :
|
||||
from scipy.interpolate import splrep, splev
|
||||
MOD_SCIPY = True
|
||||
except :
|
||||
pass
|
||||
|
||||
from .Transform import Transform
|
||||
from .lib import olOpt as olo
|
||||
|
||||
class LUT(Transform) :
|
||||
def __init__(self, dims = 1, size = 16384, title = "openlut_LUT", iRange = (0.0, 1.0)) :
|
||||
'''
|
||||
Create an identity LUT with given dimensions (1 or 3), size, and title.
|
||||
'''
|
||||
if dims != 1 and dims != 3: raise ValueError("Dimensions must be 1 or 3!")
|
||||
|
||||
self.title = title #The title.
|
||||
self.size = size #The size. 1D LUTs: size numbers. 3D LUTs: size x size x size numbers.
|
||||
self.range = iRange #The input range - creates data or legal LUTs. Should work fine, but untested.
|
||||
self.dims = dims #The dimensions. 1 or 3; others aren't accepted.
|
||||
self.ID = np.linspace(self.range[0], self.range[1], self.size, dtype=np.float32) #Read Only.
|
||||
|
||||
if dims == 1 :
|
||||
self.array = np.linspace(self.range[0], self.range[1], self.size, dtype=np.float32) #Size number of floats.
|
||||
elif dims == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
#~ self.array = np.linspace(self.range[0], self.range[1], self.size**3).reshape(self.size, self.size, self.size) #Should make an identity size x size x size array.
|
||||
|
||||
def lutFunc(func, size = 16384, dims = 1, title="openlut_FuncGen", iRange = (0.0, 1.0)) :
|
||||
'''
|
||||
Creates a LUT from a simple function.
|
||||
'''
|
||||
if dims == 1 :
|
||||
lut = LUT(dims=dims, size=size, title=title, iRange=iRange)
|
||||
|
||||
#Use fast function sampling if the function is a C++ function.
|
||||
vFunc = lambda arr: olo.gam(arr, func) if isinstance(func, types.BuiltinFunctionType) else np.vectorize(func, otypes=[np.float32])
|
||||
lut.array = vFunc(lut.array)
|
||||
|
||||
return lut
|
||||
elif dims == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
|
||||
def lutArray(array, title="Array_Generated") :
|
||||
'''
|
||||
Creates a LUT from a float array. Elements must be in range [0, 1].
|
||||
'''
|
||||
if len(np.shape(array)) == 1 :
|
||||
lut = LUT(dims=1, size=len(array), title=title)
|
||||
lut.array = array
|
||||
|
||||
return lut
|
||||
elif len(np.shape(array)) == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
else :
|
||||
raise ValueError("lutArray input must be 1D or 3D!")
|
||||
|
||||
def lutMapping(idArr, mapArr, title="Mapped_Array") :
|
||||
'''
|
||||
Creates a 1D LUT from a nonlinear mapping. Elements must be in range [0, 1].
|
||||
'''
|
||||
return LUT.lutArray(splev(np.linspace(0, 1, num=len(idArr)), splrep(idArr, mapArr)))
|
||||
|
||||
#LUT Functions.
|
||||
def __interp(q, cpu, spSeq, ID, array, spl) :
|
||||
if spl :
|
||||
q.put( (cpu, splev(spSeq, splrep(ID, array))) ) #Spline Interpolation. Pretty quick, considering.
|
||||
else :
|
||||
q.put( (cpu, np.interp(spSeq, ID, array)) )
|
||||
|
||||
def sample(self, fSeq, spl=True) :
|
||||
'''
|
||||
Sample the LUT using a flat float sequence (ideally a numpy array; (0..1) ).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
'''
|
||||
|
||||
fSeq = np.array(fSeq)
|
||||
if self.dims == 1 :
|
||||
#~ return np.interp(spSeq, self.ID, self.array)
|
||||
|
||||
#If scipy isn't loaded, we can't use spline interpolation!
|
||||
if (not MOD_SCIPY) or self.size > 1023: spl = False # Auto-adapts big LUTs to use the faster, more brute-forceish, linear interpolation.
|
||||
out = []
|
||||
q = mp.Queue()
|
||||
splt = Transform.spSeq(fSeq, mp.cpu_count())
|
||||
for cpu in range(mp.cpu_count()) :
|
||||
p = mp.Process(target=LUT.__interp, args=(q, cpu, splt[cpu], self.ID, self.array, spl))
|
||||
p.start()
|
||||
|
||||
for num in range(len(splt)) :
|
||||
out.append(q.get())
|
||||
|
||||
return np.concatenate([seq[1] for seq in sorted(out, key=lambda seq: seq[0])], axis=0)
|
||||
|
||||
elif self.dims == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
|
||||
def resized(self, newSize) :
|
||||
'''
|
||||
Return the LUT, resized to newSize.
|
||||
|
||||
1D LUTs: If the new size is lower, we use Linear interpolation. If the new size is higher, we use Spline interpolation.
|
||||
* If the current size is too low, use spline regardless.
|
||||
'''
|
||||
if newSize == self.size: return self
|
||||
|
||||
fac = newSize / self.size
|
||||
|
||||
useSpl = self.size < newSize #If the new size is lower, we use Linear interpolation. If the new size is higher, we use Spline interpolation.
|
||||
if self.size < 128: useSpl = True #If the current size is too low, use spline regardless.
|
||||
|
||||
if self.dims == 1 :
|
||||
newID = np.linspace(self.range[0], self.range[1], newSize)
|
||||
return LUT.lutArray(self.sample(newID, spl=useSpl), title="Resized to {0}".format(newSize))
|
||||
if self.dims == 3 :
|
||||
print("3D LUT Not Implemented")
|
||||
|
||||
def inverted(self) :
|
||||
'''
|
||||
Return the inverse LUT.
|
||||
'''
|
||||
return LUT.lutArray(splev(np.linspace(self.range[0], self.range[1], num=self.size), splrep(self.array, np.linspace(self.range[0], self.range[1], num=self.size))))
|
||||
|
||||
#IO Functions.
|
||||
|
||||
def open(path) :
|
||||
'''
|
||||
Opens any supported file format, located at path.
|
||||
'''
|
||||
openFunction = {
|
||||
"cube" : LUT.openCube,
|
||||
}[path[path.rfind('.') + 1:]]
|
||||
|
||||
return openFunction(path)
|
||||
|
||||
def openCube(path) :
|
||||
'''
|
||||
Opens .cube files. They must be saved with whitespaces. Referenced by open().
|
||||
'''
|
||||
lut = LUT() #Mutable luts are not reccommended for users.
|
||||
|
||||
with open(path, 'r') as f :
|
||||
i = 0
|
||||
for line in f :
|
||||
#~ if not line.strip(): continue
|
||||
sLine = line.strip()
|
||||
if not sLine: continue
|
||||
|
||||
if sLine[0] == '#': continue
|
||||
|
||||
index = sLine[:sLine.find(' ')]
|
||||
data = sLine[sLine.find(' ') + 1:]
|
||||
|
||||
if index == "TITLE": lut.title = data.strip('"'); continue
|
||||
if index == "LUT_1D_SIZE": lut.dims = 1; lut.size = int(data); continue
|
||||
if index == "LUT_3D_SIZE": lut.dims = 3; lut.size = int(data); continue
|
||||
|
||||
if index == "LUT_1D_INPUT_RANGE": lut.range = (float(data[:data.find(' ')]), float(data[data.find(' ') + 1:])); continue
|
||||
|
||||
if lut.dims == 1 and sLine[:sLine.find(' ')] :
|
||||
lut.array[i] = float(sLine[:sLine.find(' ')])
|
||||
i += 1
|
||||
elif lut.dims == 3 :
|
||||
print("3D LUT Not Implemened!")
|
||||
|
||||
return lut
|
||||
|
||||
def save(self, path) :
|
||||
'''
|
||||
Method that saves the LUT in a supported format, based on the path.
|
||||
'''
|
||||
saveFunction = {
|
||||
"cube" : self.saveCube,
|
||||
|
||||
|
||||
}[path[path.rfind('.') + 1:]]
|
||||
|
||||
saveFunction(path)
|
||||
|
||||
def saveCube(self, path) :
|
||||
with open(path, 'w') as f :
|
||||
print('TITLE', '"{}"'.format(self.title), file=f)
|
||||
|
||||
if self.dims == 1 :
|
||||
print('LUT_1D_SIZE', '{}'.format(self.size), file=f)
|
||||
print('LUT_1D_INPUT_RANGE', '{0:.6f} {1:.6f}'.format(*self.range), file=f)
|
||||
print('# Created by openlut.\n', file=f)
|
||||
|
||||
for itm in self.array :
|
||||
entry = '{0:.6f}'.format(itm)
|
||||
print(entry, entry, entry, file=f)
|
||||
elif self.dims == 3 :
|
||||
print("3D LUT Not Implemented!")
|
||||
|
||||
#Overloaded functions
|
||||
|
||||
def __iter__(self) :
|
||||
if dims == 1 :
|
||||
return iter(self.array)
|
||||
elif dims == 3 :
|
||||
iArr = self.array.reshape(self.dims, self.size / self.dims) #Group into triplets.
|
||||
return iter(iArr)
|
||||
|
||||
def __getitem__(self, key) :
|
||||
return self.sample(key)
|
||||
|
||||
def __repr__(self) :
|
||||
return 'LUT(\tdims = {0},\n\tsize = {1},\n\ttitle = "{2}"\n\tarray = {3}\n)'.format(self.dims, self.size, self.title, '\n\t\t'.join([line.strip() for line in repr(self.array).split('\n')]))
|
|
@ -0,0 +1,17 @@
|
|||
import abc
|
||||
|
||||
import numpy as np
|
||||
|
||||
class Transform :
|
||||
def spSeq(seq, outLen) :
|
||||
"""
|
||||
Utility function for splitting a sequence into equal parts, for multithreading.
|
||||
"""
|
||||
perfSep = (1/outLen) * len(seq)
|
||||
return list(filter(len, [seq[round(perfSep * i):round(perfSep * (i + 1))] for i in range(len(seq))])) if len(seq) > 1 else seq
|
||||
|
||||
@abc.abstractmethod
|
||||
def sample(self, fSeq) :
|
||||
"""
|
||||
Samples the Transformation.
|
||||
"""
|
|
@ -0,0 +1,135 @@
|
|||
import pygame
|
||||
from pygame.locals import *
|
||||
from OpenGL.GL import *
|
||||
from OpenGL.GLU import *
|
||||
|
||||
import sys, os, os.path
|
||||
|
||||
class Viewer :
|
||||
def __init__(self, res, title="OpenLUT Image Viewer") :
|
||||
self.res = res
|
||||
|
||||
pygame.init()
|
||||
pygame.display.set_caption(title)
|
||||
pygame.display.set_mode(res, DOUBLEBUF|OPENGL)
|
||||
|
||||
self.initGL()
|
||||
|
||||
def initGL(self) :
|
||||
'''
|
||||
Initialize OpenGL.
|
||||
'''
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
glLoadIdentity()
|
||||
glOrtho(0, self.res[0], self.res[1], 0, 0, 100)
|
||||
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
|
||||
#~ glClearColor(0, 0, 0, 0)
|
||||
#~ glClearDepth(0)
|
||||
#~ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
#~ def resizeWindow(self, newRes) :
|
||||
#~ self.res = newRes
|
||||
#~ pygame.display.set_mode(self.res, RESIZABLE|DOUBLEBUF|OPENGL)
|
||||
##~ glLoadIdentity()
|
||||
##~ glOrtho(0, self.res[0], self.res[1], 0, 0, 100)
|
||||
|
||||
##~ glMatrixMode(GL_MODELVIEW)
|
||||
|
||||
def drawQuad(self) :
|
||||
'''
|
||||
Draws an image to the screen.
|
||||
'''
|
||||
glBegin(GL_QUADS)
|
||||
|
||||
glTexCoord2i(0, 0)
|
||||
glVertex2i(0, 0)
|
||||
|
||||
glTexCoord2i(0, 1)
|
||||
glVertex2i(0, self.res[1])
|
||||
|
||||
glTexCoord2i(1, 1)
|
||||
glVertex2i(self.res[0], self.res[1])
|
||||
|
||||
glTexCoord2i(1, 0)
|
||||
glVertex2i(self.res[0], 0)
|
||||
|
||||
glEnd()
|
||||
|
||||
def bindTex(self, img) :
|
||||
'''
|
||||
Binds the image contained the numpy float array img to a 2D texture on the GPU.
|
||||
'''
|
||||
id = glGenTextures(1)
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
|
||||
glBindTexture(GL_TEXTURE_2D, id)
|
||||
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.shape[1], img.shape[0], 0, GL_RGB, GL_FLOAT, img)
|
||||
|
||||
def display(self) :
|
||||
'''
|
||||
Repaints the window.
|
||||
'''
|
||||
|
||||
#Clears the "canvas"
|
||||
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
|
||||
#Maybe do them here.
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
self.drawQuad()
|
||||
|
||||
#Updates the display.
|
||||
pygame.display.flip()
|
||||
|
||||
def close() :
|
||||
#~ print()
|
||||
pygame.quit()
|
||||
|
||||
def run(img, xRes, yRes, title = "OpenLUT Image Viewer") :
|
||||
'''
|
||||
img is an rgb array.
|
||||
'''
|
||||
v = Viewer((xRes, yRes), title)
|
||||
v.bindTex(img)
|
||||
|
||||
FPS = None
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
while True :
|
||||
for event in pygame.event.get() :
|
||||
if event.type == pygame.QUIT: Viewer.close(); break
|
||||
|
||||
#~ if event.type == pygame.VIDEORESIZE :
|
||||
#~ v.resizeWindow((event.w, event.h))
|
||||
|
||||
if event.type == pygame.KEYDOWN :
|
||||
try :
|
||||
{
|
||||
|
||||
}[event.key]()
|
||||
except KeyError as key :
|
||||
if str(key) == "27": Viewer.close(); break #Need to catch ESC to close the window.
|
||||
print("Key not mapped!")
|
||||
else :
|
||||
#This else will only run if the event loop is completed.
|
||||
v.display()
|
||||
|
||||
#Smooth playback at FPS.
|
||||
if FPS: clock.tick(FPS)
|
||||
else: clock.tick()
|
||||
#~ print("\r", clock.get_fps(), end="", sep="")
|
||||
|
||||
continue
|
||||
|
||||
break #This break will only run if the event loop is broken out of.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#Set it up so that the users don't see the files containing the classes.
|
||||
from .Transform import Transform
|
||||
from .ColMap import ColMap
|
||||
from .LUT import LUT
|
||||
from .Func import Func
|
||||
from .ColMat import ColMat
|
||||
from .Viewer import Viewer
|
||||
|
||||
__all__ = ['ColMap', 'Transform', 'LUT', 'Func', 'ColMat', 'Viewer', 'gamma', 'gamut']
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .lib import olOpt as olo
|
||||
|
||||
#Static Gamma Functions, borrowed from olo.
|
||||
#inv goes from space to lin.
|
||||
|
||||
lin = olo.lin
|
||||
sRGB = olo.sRGB
|
||||
sRGBinv = olo.sRGBinv
|
||||
Rec709 = olo.Rec709
|
||||
ReinhardHDR = olo.ReinhardHDR
|
||||
sLog = olo.sLog
|
||||
sLog2 = olo.sLog2
|
||||
DanLog = olo.DanLog
|
||||
|
||||
class PGamma :
|
||||
'''
|
||||
Static class containing python versions of the C++ gamma functions.
|
||||
'''
|
||||
|
||||
def lin(x): return x
|
||||
|
||||
def sRGB(x) :
|
||||
'''
|
||||
sRGB formula. Domain must be within [0, 1].
|
||||
'''
|
||||
return ( (1.055) * (x ** (1.0 / 2.4)) ) - 0.055 if x > 0.0031308 else x * 12.92
|
||||
def sRGBinv(x) :
|
||||
'''
|
||||
Inverse sRGB formula. Domain must be within [0, 1].
|
||||
'''
|
||||
return ((x + 0.055) / 1.055) ** 2.4 if x > 0.04045 else x / 12.92
|
||||
|
||||
def Rec709(x) :
|
||||
'''
|
||||
Rec709 formula. Domain must be within [0, 1].
|
||||
'''
|
||||
return 1.099 * (x ** 0.45) - 0.099 if x >= 0.018 else 4.5 * x
|
||||
|
||||
def ReinhardHDR(x) :
|
||||
'''
|
||||
Reinhard Tonemapping formula. Domain must be within [0, 1].
|
||||
'''
|
||||
return x / (1.0 + x)
|
||||
|
||||
def sLog(x) :
|
||||
'''
|
||||
sLog 1 formula. Domain must be within [0, 1]. See https://pro.sony.com/bbsccms/assets/
|
||||
files/mkt/cinema/solutions/slog_manual.pdf .
|
||||
'''
|
||||
return ( 0.432699 * math.log(x + 0.037584, 10.0) + 0.616596) + 0.03
|
||||
|
||||
def sLog2(x) :
|
||||
'''
|
||||
sLog2 formula. Domain must be within [0, 1]. See https://pro.sony.com/bbsccms/assets/files/micro/dmpc/training/S-Log2_Technical_PaperV1_0.pdf .
|
||||
'''
|
||||
return ( 0.432699 * math.log( (155.0 * x) / 219.0 + 0.037584, 10.0) + 0.616596 ) + 0.03
|
||||
|
||||
def sLog3(x) :
|
||||
'''
|
||||
Not yet implemented. See http://community.sony.com/sony/attachments/sony/large-sensor-camera-F5-F55/12359/2/TechnicalSummary_for_S-Gamut3Cine_S-Gamut3_S-Log3_V1_00.pdf .
|
||||
'''
|
||||
return x
|
||||
|
||||
def DanLog(x) :
|
||||
return (10.0 ** ((x - 0.385537) / 0.2471896) - 0.071272) / 3.555556 if x > 0.1496582 else (x - 0.092809) / 5.367655
|
|
@ -0,0 +1,71 @@
|
|||
import numpy as np
|
||||
|
||||
#Static Matrices -- all go from ACES to <gamut name>. <gamut name>inv matrices go from the gamut to ACES.
|
||||
#Converted (CIECAT02) D65 Illuminant for all.
|
||||
|
||||
XYZ = np.array(
|
||||
[
|
||||
0.93863095, -0.00574192, 0.0175669,
|
||||
0.33809359, 0.7272139, -0.0653075,
|
||||
0.00072312, 0.00081844, 1.08751619
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
XYZinv = np.array(
|
||||
[
|
||||
1.06236611, 0.00840695, -0.01665579,
|
||||
-0.49394137, 1.37110953, 0.09031659,
|
||||
-0.00033467, -0.00103746, 0.91946965
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
|
||||
sRGB = np.array(
|
||||
[
|
||||
2.52193473, -1.1370239, -0.38491083,
|
||||
-0.27547943, 1.36982898, -0.09434955,
|
||||
-0.01598287, -0.14778923, 1.1637721
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
sRGBinv = np.array(
|
||||
[
|
||||
0.43957568, 0.38391259, 0.17651173,
|
||||
0.08960038, 0.81471415, 0.09568546,
|
||||
0.01741548, 0.10873435, 0.87385017
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
|
||||
Rec709 = np.array(
|
||||
[
|
||||
2.52193473, -1.1370239, -0.38491083,
|
||||
-0.27547943, 1.36982898, -0.09434955,
|
||||
-0.01598287, -0.14778923, 1.1637721
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
Rec709inv = np.array(
|
||||
[
|
||||
0.43957568, 0.38391259, 0.17651173,
|
||||
0.08960038, 0.81471415, 0.09568546,
|
||||
0.01741548, 0.10873435, 0.87385017
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
|
||||
aRGB = np.array(
|
||||
[
|
||||
1.72502307, -0.4228857, -0.30213736,
|
||||
-0.27547943, 1.36982898, -0.09434955,
|
||||
-0.02666425, -0.08532111, 1.11198537
|
||||
]
|
||||
).reshape(3, 3)
|
||||
|
||||
aRGBinv = np.array(
|
||||
[
|
||||
0.61468318, 0.20122762, 0.1840892,
|
||||
0.12529321, 0.77491365, 0.09979314,
|
||||
0.02435304, 0.06428329, 0.91136367
|
||||
]
|
||||
).reshape(3, 3)
|
|
@ -0,0 +1,37 @@
|
|||
#Macros
|
||||
SHELL=/bin/sh
|
||||
|
||||
FILES = olOpt.cpp
|
||||
#~ LIBS = openimageio
|
||||
|
||||
CXXFLAGS = -O3 -shared -Wall -std=gnu++14 -fopenmp -fPIC
|
||||
PYTHONFLAGS = $(shell python3-config --cflags) $(shell python3-config --ldflags)
|
||||
CXX = g++
|
||||
|
||||
LIBS =
|
||||
|
||||
DATE = $(shell date)
|
||||
|
||||
#Main Rules
|
||||
|
||||
all: olOpt
|
||||
|
||||
clean:
|
||||
-rm -f *.so
|
||||
|
||||
#~ clean-deps:
|
||||
#~ -rm -rf libs
|
||||
|
||||
#~ clean-all: clean clean-deps
|
||||
|
||||
install:
|
||||
@echo "Not Yet Implemented"
|
||||
|
||||
debug:
|
||||
|
||||
|
||||
#Executables
|
||||
|
||||
olOpt: ${FILES}
|
||||
${CXX} -o $@.so ${FILES} ${CXXFLAGS} ${LIBS} ${PYTHONFLAGS}
|
||||
@echo "Successfully compiled at" ${DATE}
|
|
@ -0,0 +1,587 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
'''
|
||||
Copyright 2016 Sofus Rose
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
'''
|
||||
|
||||
import sys, os, time
|
||||
import multiprocessing as mp
|
||||
|
||||
class Files :
|
||||
"""
|
||||
The Files object is an immutable sequence of files, which supports writing simultaneously to all the files.
|
||||
"""
|
||||
def __init__(self, *files) :
|
||||
seq=[]
|
||||
for f in files:
|
||||
if isinstance(f, Files): seq += f.files
|
||||
elif 'write' in dir(f): seq.append(f)
|
||||
else: raise TypeError('Wrong Input Type: ' + repr(f))
|
||||
|
||||
self.files = tuple(seq) #Immutable tuple of file-like objects,
|
||||
|
||||
|
||||
|
||||
def write(self, inStr, exclInd=[]) :
|
||||
"""
|
||||
Writes inStr all file-like objects stored within the Files object. You may exclude certain entries with a sequence of indices.
|
||||
"""
|
||||
for f in enumerate(self.files) :
|
||||
if f[0] in exclInd: continue
|
||||
f[1].write(inStr)
|
||||
|
||||
|
||||
|
||||
|
||||
def __add__(self, o, commut=False) :
|
||||
"""
|
||||
Implements merging with Files objects and appending file-like objects. Returns new Files object.
|
||||
"""
|
||||
if isinstance(o, Files) :
|
||||
this, other = self.files, o.files
|
||||
elif 'write' in dir(o) :
|
||||
this, other = self.files, [o]
|
||||
else :
|
||||
return None
|
||||
|
||||
if commut: this, other = other, this
|
||||
|
||||
return Files(*this, *other) #this and other must be unpackable.
|
||||
|
||||
def __radd__(self, o) :
|
||||
"""
|
||||
Commutative addition.
|
||||
"""
|
||||
return self.__add__(o, commut=True) #Use the add operator. It's commutative!!
|
||||
|
||||
def __bool__(self) :
|
||||
"""
|
||||
False if empty.
|
||||
"""
|
||||
return bool(self.files)
|
||||
|
||||
|
||||
|
||||
def __getitem__(self, index) :
|
||||
"""
|
||||
Supports slicing and indexing.
|
||||
"""
|
||||
if isinstance(index, slice) :
|
||||
return Files(self.files[index])
|
||||
else :
|
||||
return self.files[index]
|
||||
|
||||
def __len__(self) :
|
||||
"""
|
||||
Number of files in the Files object.
|
||||
"""
|
||||
return len(self.files)
|
||||
|
||||
def __repr__(self) :
|
||||
return 'Files(' + ', '.join("'{}'".format(n.name) for n in self.files) + ')'
|
||||
|
||||
def __iter__(self) :
|
||||
"""
|
||||
Iterates through the file-like objects.
|
||||
"""
|
||||
return iter(self.files)
|
||||
|
||||
class ColLib :
|
||||
"""
|
||||
Simple hashmap to colors.. Make sure to activate colors in ~/.bashrc or enable ansi.sys!
|
||||
"""
|
||||
cols = { 'HEADER' : '\033[97m',
|
||||
'OKBLUE' : '\033[94m',
|
||||
'OKGREEN' : '\033[92m',
|
||||
'WARNING' : '\033[93m',
|
||||
'FAIL' : '\033[91m',
|
||||
'CRIT' : '\033[31m',
|
||||
'DEBUG' : '\033[35m',
|
||||
|
||||
'ENDC' : '\033[0m',
|
||||
|
||||
'BOLD' : '\033[1m',
|
||||
'ITALIC' : '\033[3m',
|
||||
'UNDERLINE' : '\033[4m'
|
||||
}
|
||||
|
||||
|
||||
|
||||
debug = { 'info' : ('[INFO]', 'OKGREEN'),
|
||||
'error' : ('[ERROR]', 'FAIL'),
|
||||
'crit' : ('[CRIT]', 'CRIT'),
|
||||
'warn' : ('[WARNING]', 'WARNING'),
|
||||
'debug' : ('[DEBUG]', 'DEBUG'),
|
||||
'run' : ('[RUN]', 'OKBLUE')
|
||||
}
|
||||
|
||||
|
||||
|
||||
def colString(color, string) :
|
||||
"""
|
||||
Returns a colored string.
|
||||
"""
|
||||
return '{}{}{}'.format(cols[color], string, cols['ENDC'])
|
||||
|
||||
def dbgString(signal, string) :
|
||||
"""
|
||||
"""
|
||||
return '{}{}{}'.format(debug[signal])
|
||||
|
||||
|
||||
|
||||
def printCol(color, colored, *output, **settings) :
|
||||
"""
|
||||
Simple print clone where the first printed parameter is colored.
|
||||
"""
|
||||
print(cols[color] + str(colored) + cols['ENDC'], *output, **settings)
|
||||
|
||||
def printDbg(signal, *output, **settings) :
|
||||
"""
|
||||
Pass in simple debug signals to print the corresponding entry.
|
||||
"""
|
||||
printCol(debug[signal][1], debug[signal][0] + ' ' + colored, *output, **settings)
|
||||
|
||||
class Log(ColLib) :
|
||||
"""
|
||||
Logging object, an instance of which is passed throughout afarm. **It has + changes state**, as the sole exception to the
|
||||
'no globals' paradigm. You may pass in any file-like object, the only criteria being that it has a 'write' method. stdout is
|
||||
used by default.
|
||||
"""
|
||||
def __init__(self, *file, verb=3, useCol=True, startTime=None) :
|
||||
if not file: file = [sys.stdout]
|
||||
if startTime is None: startTime = time.perf_counter()
|
||||
|
||||
self.verb = verb #From 0 to 3. 0: CRITICAL 1: ERRORS 2: WARNINGS 3: DEBUG. Info all.
|
||||
self.file = Files(*file)
|
||||
|
||||
self.log = [] #Log list. Format: (verb, time in ms, debug_text, text)
|
||||
self.sTimes = dict() #Dict of start times for various runs.
|
||||
|
||||
self._useCol = useCol #Whether or not to use colored output.
|
||||
self._attrLock = mp.Lock() #The log access lock.
|
||||
self._startTime = startTime #Global instance time. begins when the instance is created.
|
||||
|
||||
|
||||
|
||||
def getLogTime(self) :
|
||||
"""
|
||||
Gets the current logging time in seconds, from the time of instantiation of the Log object.
|
||||
"""
|
||||
return time.perf_counter() - self._startTime
|
||||
|
||||
def startTime(self, run) :
|
||||
"""
|
||||
Starts the timer for the specified run. Can use any immutable object to mark the run.
|
||||
"""
|
||||
self.sTimes[run] = self.getLogTime()
|
||||
|
||||
def getTime(self, run) :
|
||||
"""
|
||||
Gets the time since startTime for the specified run (an immutable object).
|
||||
"""
|
||||
if run in self.sTimes :
|
||||
return self.getLogTime() - self.sTimes[run]
|
||||
else :
|
||||
raise ValueError('Run wasn\'t found!!')
|
||||
|
||||
def compItem(self, state, time, *text) :
|
||||
"""
|
||||
Returns a displayable log item as a string, formatted with or without color.
|
||||
"""
|
||||
decor = { 'info' : '',
|
||||
'error' : '',
|
||||
'crit' : ColLib.cols['BOLD'],
|
||||
'warn' : '',
|
||||
'debug' : ColLib.cols['BOLD'],
|
||||
'run' : ColLib.cols['BOLD']
|
||||
}[state]
|
||||
|
||||
timeCol = { 'info' : ColLib.cols['HEADER'],
|
||||
'error' : ColLib.cols['WARNING'],
|
||||
'crit' : ColLib.cols['FAIL'],
|
||||
'warn' : ColLib.cols['HEADER'],
|
||||
'debug' : ColLib.cols['DEBUG'] + ColLib.cols['BOLD'],
|
||||
'run' : ColLib.cols['OKGREEN']
|
||||
}[state]
|
||||
|
||||
if self._useCol :
|
||||
return '{3}{5}{0}{4[ENDC]}\t{6}{1:.10f}{4[ENDC]}: {2}'.format( ColLib.debug[state][0],
|
||||
time,
|
||||
''.join(text),
|
||||
ColLib.cols[ColLib.debug[state][1]],
|
||||
ColLib.cols,
|
||||
decor,
|
||||
timeCol
|
||||
)
|
||||
else :
|
||||
return '{0} {1:.10f}: {2}'.format(ColLib.debug[state][0], time, ''.join(text))
|
||||
|
||||
|
||||
|
||||
def write(self, *text, verb=2, state='info') :
|
||||
"""
|
||||
Adds an entry to the log file, as well as to the internal structure.
|
||||
|
||||
Possible state values:
|
||||
*'info': To give information.
|
||||
*'error': When things go wrong.
|
||||
*'crit': When things go very wrong.
|
||||
*'warn': To let the user know that something weird is up.
|
||||
*'run': To report on an intensive process.
|
||||
*'debug': For debugging purposes. Keep it at verbosity 3.
|
||||
|
||||
Possible verbosity values, and suggested usage:
|
||||
*0: User-oriented, general info about important happenings.
|
||||
*1: Helpful info about what is running/happening, even to the user.
|
||||
*2: Deeper info about the programs functionality, for fixing problems.
|
||||
*3: Developer oriented debugging.
|
||||
"""
|
||||
text = [str(t).strip() for t in text if str(t).strip()]
|
||||
if not text: return #Empty write's are no good.
|
||||
curTime = self.getLogTime()
|
||||
with self._attrLock :
|
||||
self.log.append( { 'verb' : verb,
|
||||
'time' : curTime,
|
||||
'state' : state,
|
||||
'text' : ' '.join(str(t) for t in text)
|
||||
}
|
||||
)
|
||||
|
||||
if self.verb >= verb :
|
||||
with self._attrLock :
|
||||
print(self.compItem(state, curTime, ' '.join(text)), file=self.file)
|
||||
|
||||
def read(self, verb=None) :
|
||||
"""
|
||||
Reads the internal logging data structure, optionally overriding verbosity.
|
||||
"""
|
||||
if not verb: verb = self.verb
|
||||
with self._attrLock :
|
||||
return '\n'.join([self.compItem(l['state'], l['time'], l['text']) for l in self.log if verb >= l['verb']])
|
||||
|
||||
def reset(self, startTime=None) :
|
||||
return Log(self.file, verb=self.verb, useCol=self._useCol, startTime=startTime)
|
||||
|
||||
|
||||
|
||||
def getFiles(self) :
|
||||
"""
|
||||
Get the list of files to dump Log output to.
|
||||
"""
|
||||
return self.file
|
||||
|
||||
def setFiles(self, *files) :
|
||||
"""
|
||||
Set a new list of files to dump Log output to.
|
||||
"""
|
||||
with self._attrLock :
|
||||
self.file = Files(*files)
|
||||
|
||||
def addFiles(self, *files) :
|
||||
"""
|
||||
Add a list of files to dump Log output to.
|
||||
"""
|
||||
with self._attrLock :
|
||||
self.file += Files(*files)
|
||||
|
||||
|
||||
|
||||
def setVerb(self, newVerb) :
|
||||
"""
|
||||
Call to set verbosity.
|
||||
"""
|
||||
with self._attrLock :
|
||||
self.verb = newVerb
|
||||
self.write('Verbosity set to', str(newVerb) + '.', verb=0, state='info')
|
||||
|
||||
def setCol(self, newUseCol) :
|
||||
"""
|
||||
Call to change color output.
|
||||
"""
|
||||
with self._attrLock :
|
||||
self._useCol = newUseCol
|
||||
self.write('Color Output set to', self._useCol, verb=0, state='info')
|
||||
|
||||
|
||||
|
||||
def __call__(self, verb, state, *text) :
|
||||
"""
|
||||
Identical to Log.write(), except it requires the verbosity level to be specified.
|
||||
"""
|
||||
self.write(verb=verb, state=state, *text)
|
||||
|
||||
def __repr__(self) :
|
||||
return ( 'Log(' +
|
||||
(', '.join("'{}'".format(f.name) for f in self.file.files) + ', ' if self.file else '') +
|
||||
'verb={}, useCol={}, startTime={:.3f})'.format(self.verb, self._useCol, self._startTime)
|
||||
)
|
||||
|
||||
def __str__(self) :
|
||||
return self.read()
|
||||
|
||||
|
||||
|
||||
def __add__(self, o, commut=False) :
|
||||
"""
|
||||
Merges a Log object with another Log object, a Files object, or a file-like object.
|
||||
*For Log object addition, the minimum startTime attibute is used to initialize the merged startTime.
|
||||
*The Files objects of both Log objects are merged.
|
||||
"""
|
||||
|
||||
if isinstance(o, Log) :
|
||||
l = self.reset(min(self.getLogTime(), o.getLogTime())) #Max of self and other time.
|
||||
l.log = self.log + o.log
|
||||
l.log.sort(key=lambda item: item['time']) #Make sure to sort the internal log by time.
|
||||
l.addFiles(o.file)
|
||||
return l
|
||||
elif isinstance(o, Files) :
|
||||
l = self.reset(self.getLogTime())
|
||||
l.log = self.log
|
||||
l.setFiles(self.getFiles(), *o.files)
|
||||
return l
|
||||
elif 'write' in dir(o) :
|
||||
l = self.reset(self.getLogTime())
|
||||
l.file = o + self.file if commut else self.file + o
|
||||
return l
|
||||
else :
|
||||
return None
|
||||
|
||||
def __radd__(self, o) :
|
||||
return self.__add__(o, commut=True) #Use the add operator. It's commutative!!
|
||||
|
||||
|
||||
def __bool__(self) :
|
||||
"""
|
||||
False if log is empty.
|
||||
"""
|
||||
return bool(self.log)
|
||||
|
||||
def __getitem__(self, i) :
|
||||
"""
|
||||
Supports slicing and indexing, from recent (0) to oldest (end).
|
||||
"""
|
||||
if isinstance(i, slice) :
|
||||
l = self.reset(self.getLogTime())
|
||||
for ind, itm in enumerate(self.log) :
|
||||
if ind in list(range(i.start if i.start else 0, i.stop, i.step if i.step else 1)): l.log.append(itm)
|
||||
return list(l)
|
||||
else :
|
||||
l = self.log[::-1][i]
|
||||
return self.compItem(l['state'], l['time'], l['text'], noCol = not self._useCol)
|
||||
|
||||
def __len__(self) :
|
||||
"""
|
||||
Amount of items in the log.
|
||||
"""
|
||||
return len(self.log)
|
||||
|
||||
def __iter__(self) :
|
||||
"""
|
||||
Iterator never colors output.
|
||||
"""
|
||||
return iter(self.compItem(l['state'], l['time'], l['text'], noCol = True) for l in self.log)
|
||||
|
||||
class LogFile() :
|
||||
"""
|
||||
Similar to a normal file, except it splits into several files. On the frontend, however, it acts as if it were a single file.
|
||||
|
||||
*Writes to 'path'.log.
|
||||
*When maxLen is exceeded, lines are pushed into 'path'.0.log, then 'path'.1.log, etc. .
|
||||
"""
|
||||
def __init__(self, path, maxLen=1000, trunc=False) : #Make maxLen 1000 later.
|
||||
"""
|
||||
Constructor accepts a path (extension will be rewritten to '.log'), a maximum length, and will optionally truncate
|
||||
any previous logfiles.
|
||||
"""
|
||||
self.path = os.path.splitext(path)[0] + '.log'
|
||||
self.bPath = os.path.splitext(self.path)[0]
|
||||
|
||||
self.maxLen = maxLen
|
||||
self.name = '<{0}.log, {0}.0...n.log>'.format(self.bPath)
|
||||
|
||||
self.lines = 0
|
||||
self.fileNum = 0
|
||||
|
||||
#If the logfile already exists, it's read + rewritten using the current maxLen.
|
||||
if os.path.exists(self.path) :
|
||||
if trunc: self.truncate(); return
|
||||
|
||||
inLines = open(self.path, 'r').readlines()[::-1]
|
||||
os.remove(os.path.abspath(self.path)) #Remove the old path.log.
|
||||
|
||||
i = 0
|
||||
while os.path.exists('{0}.{1}.log'.format(self.bPath, i)) :
|
||||
inLines += open('{0}.{1}.log'.format(self.bPath, i), 'r').readlines()[::-1]
|
||||
os.remove(os.path.abspath('{0}.{1}.log'.format(self.bPath, i)))
|
||||
i += 1
|
||||
|
||||
self.write(''.join(reversed(inLines)))
|
||||
|
||||
|
||||
def write(self, *inStr) :
|
||||
apnd = list(filter(bool, ''.join(inStr).strip().split('\n')))
|
||||
|
||||
if not apnd: return #Nothing to append = don't even try!
|
||||
if not os.path.exists(self.path): open(self.path, 'w').close() #Make sure path.log exists.
|
||||
|
||||
#Empty apnd line by line.
|
||||
while len(apnd) > 0 :
|
||||
toWrite = self.maxLen * (self.fileNum + 1) - self.lines #Lines needed to fill up path.log
|
||||
if toWrite == 0 : #Time to make new files.
|
||||
|
||||
#Rename upwards. path.n.log -> path.(n+1).log, etc. . path.log becomes path.0.log.
|
||||
for i in reversed(range(self.fileNum)) :
|
||||
os.rename('{0}.{1}.log'.format(self.bPath, i), '{0}.{1}.log'.format(self.bPath, i+1))
|
||||
os.rename('{0}.log'.format(self.bPath), '{0}.0.log'.format(self.bPath))
|
||||
|
||||
#Make new path.log.
|
||||
open(self.path, 'w').close() #Just create the file.
|
||||
|
||||
#Number of files just increased.
|
||||
self.fileNum += 1
|
||||
else : #Fill up path.log.
|
||||
print(apnd.pop(0), file=open(self.path, 'a'))
|
||||
|
||||
#Number of written lines just increasd.
|
||||
self.lines += 1
|
||||
|
||||
def read(self) :
|
||||
collec = []
|
||||
|
||||
for i in reversed(range(self.fileNum)) :
|
||||
collec += open('{0}.{1}.log'.format(self.bPath, i), 'r').readlines()
|
||||
|
||||
collec += open(self.path, 'r').readlines()
|
||||
|
||||
return ''.join(collec)
|
||||
|
||||
def truncate(self) :
|
||||
"""
|
||||
Deletes all associated files + resets the instance.
|
||||
"""
|
||||
i = 0
|
||||
|
||||
os.remove(os.path.abspath(self.path)) #Remove the old path.log.
|
||||
while os.path.exists('{0}.{1}.log'.format(self.bPath, i)) : #Remove all log files.
|
||||
os.remove(os.path.abspath('{0}.{1}.log'.format(self.bPath, i)))
|
||||
i += 1
|
||||
|
||||
self.lines = 0
|
||||
self.fileNum = 0
|
||||
|
||||
def readlines(self) :
|
||||
return self.read().split('\n')
|
||||
|
||||
def isatty(self) :
|
||||
"""
|
||||
Always returns false, as a LogFile is never associated with a tty.
|
||||
"""
|
||||
return False
|
||||
|
||||
def __iter__(self) :
|
||||
return (line for line in self.readlines())
|
||||
|
||||
def __repr__(self) :
|
||||
return 'LogFile({0}, maxLen={1})'.format(self.path, self.maxLen)
|
||||
|
||||
def coolTest() :
|
||||
l = Log(Files(LogFile('first', 10, True), LogFile('second', 20, True)), LogFile('third', 30, True), sys.stdout)
|
||||
l(0, 'info', 'Big Failure Oh NO!')
|
||||
l(0, 'error', 'Big Failure Oh NO!')
|
||||
l(0, 'crit', 'Big Failure Oh NO!')
|
||||
l(0, 'warn', 'Big Failure Oh NO!')
|
||||
l(0, 'debug', 'Big Failure Oh NO!')
|
||||
l(0, 'run', 'Big Failure Oh NO!')
|
||||
|
||||
print('We got ourselves a log file here kids.')
|
||||
|
||||
for i in range(50) :
|
||||
l(0, 'run', 'This is the', i, 'run today!')
|
||||
|
||||
print(l.getLogTime())
|
||||
|
||||
def logFileTest() :
|
||||
l = LogFile('hi.log', maxLen = 3, trunc=True)
|
||||
|
||||
print('\n', repr(l), sep='')
|
||||
print('hi', 'world', file=l, sep='\n')
|
||||
|
||||
print('\n', repr(l), sep='')
|
||||
print('you', 'are', 'cool', 'friend', 'hi', file=l, sep='\n')
|
||||
print('hello\nmotherfucker\nits\narnold\nyour\nold\nfriend\nrunbaby!!\nlittlebitch :)', file=l)
|
||||
print('\n', repr(l), sep='')
|
||||
print('Reading Log Files:', repr(l.read()))
|
||||
print(l.name, '\n\n\n')
|
||||
|
||||
print(l.read().split('\n'))
|
||||
print(len([x for x in l.read().split('\n') if x]))
|
||||
|
||||
l = LogFile('hi.log', 10)
|
||||
print(l.read().split('\n'))
|
||||
print(len([x for x in l.read().split('\n') if x]))
|
||||
|
||||
l = Log(LogFile('hi', 100, False))
|
||||
print(repr(l))
|
||||
for x in range(10) :
|
||||
l(0, x)
|
||||
#~ l.setCol(False)
|
||||
l(0, 'info', 'Big Failure Oh NO!')
|
||||
l(0, 'error', 'Big Failure Oh NO!')
|
||||
l(0, 'crit', 'Big Failure Oh NO!')
|
||||
l(0, 'warn', 'Big Failure Oh NO!')
|
||||
l(0, 'debug', 'Big Failure Oh NO!')
|
||||
l(0, 'run', 'Big Failure Oh NO!')
|
||||
l = LogFile('hi', 500, False)
|
||||
print('hihi', 'you', 'can\'t', 'beat', 'the', 'trunc', file=l, sep='\n')
|
||||
|
||||
def logTest() :
|
||||
l = Log(open('hi.txt', 'w'))
|
||||
#~ l.setCol(False)
|
||||
print(repr(l))
|
||||
print('1', file=l)
|
||||
l(1, '2')
|
||||
a = l.reset()
|
||||
|
||||
#~ l.setCol(True)
|
||||
|
||||
l.addFiles(sys.stderr)
|
||||
print('\n', repr(l), sep='')
|
||||
|
||||
print('\n', repr(a), sep='')
|
||||
a(2, '3')
|
||||
a.write('4')
|
||||
a.addFiles(sys.stdout)
|
||||
|
||||
print('\n a + l ', repr(a + l), sep='')
|
||||
|
||||
print(a + l)
|
||||
|
||||
print('\n', repr(a + sys.stderr), sep='')
|
||||
print('\n', repr(sys.stderr + a), sep='')
|
||||
print('\n', (l + a), sep='')
|
||||
print('\n', (l + a)[0:3], sep='')
|
||||
|
||||
for item in (l + a) :
|
||||
print(item)
|
||||
|
||||
print("\nLength of l + a: ", len(l + a))
|
||||
print("\nl + a: ", repr(Log() + Files(sys.stdout)))
|
||||
print('\n', l.read(verb=0), sep='')
|
||||
|
||||
if __name__ == "__main__" :
|
||||
#~ unitTest()
|
||||
#~ logFileTest()
|
||||
coolTest()
|
|
@ -0,0 +1,108 @@
|
|||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/numpy.h>
|
||||
#include <pybind11/functional.h>
|
||||
//~ #include <pybind11/eigen.h>
|
||||
|
||||
//~ #include <Eigen/LU>
|
||||
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
|
||||
//~ #include "samplers.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace std;
|
||||
|
||||
|
||||
|
||||
//Gamma functions, ported to C++ for efficiency.
|
||||
|
||||
float lin(float x) { return x; }
|
||||
float sRGB(float x) { return x > 0.0031308 ? (( 1.055 * pow(x, (1.0f / 2.4)) ) - 0.055) : x * 12.92; }
|
||||
float sRGBinv(float x) { return x > 0.04045 ? pow(( (x + 0.055) / 1.055 ), 2.4) : x / 12.92; }
|
||||
float Rec709(float x) { return x >= 0.018 ? 1.099 * pow(x, 0.45) - 0.099 : 4.5 * x; }
|
||||
float ReinhardHDR(float x) { return x / (1.0 + x); }
|
||||
float sLog(float x) { return (0.432699 * log10(x + 0.037584) + 0.616596) + 0.03; }
|
||||
float sLog2(float x) { return ( 0.432699 * log10( (155.0 * x) / 219.0 + 0.037584) + 0.616596 ) + 0.03; }
|
||||
float DanLog(float x) { return x > 0.1496582 ? (pow(10.0, ((x - 0.385537) / 0.2471896)) - 0.071272) / 3.555556 : (x - 0.092809) / 5.367655; }
|
||||
|
||||
|
||||
//gam lets the user pass in any 1D array, any one-arg C++ function, and get a result. It's multithreaded, vectorized, etc. .
|
||||
py::array_t<float> gam(py::array_t<float> arr, const std::function<float(float)> &g_func) {
|
||||
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);
|
||||
|
||||
//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 < bufIn.shape[0]; i++) {
|
||||
//~ std::cout << g_func(ptrIn[i]) << std::endl;
|
||||
ptrOut[i] = g_func(ptrIn[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PYBIND11_PLUGIN(olOpt) {
|
||||
py::module mod("olOpt", "Optimized C++ functions for openlut.");
|
||||
|
||||
mod.def( "gam",
|
||||
&gam,
|
||||
"The sRGB function, vectorized."
|
||||
);
|
||||
|
||||
mod.def( "lin",
|
||||
&lin,
|
||||
"The linear function."
|
||||
);
|
||||
|
||||
mod.def( "sRGB",
|
||||
&sRGB,
|
||||
"The sRGB function."
|
||||
);
|
||||
|
||||
mod.def( "sRGBinv",
|
||||
&sRGBinv,
|
||||
"The sRGBinv function."
|
||||
);
|
||||
|
||||
mod.def( "Rec709",
|
||||
&Rec709,
|
||||
"The Rec709 function."
|
||||
);
|
||||
|
||||
mod.def( "ReinhardHDR",
|
||||
&ReinhardHDR,
|
||||
"The ReinhardHDR function."
|
||||
);
|
||||
|
||||
mod.def( "sLog",
|
||||
&sLog,
|
||||
"The sLog function."
|
||||
);
|
||||
|
||||
mod.def( "sLog2",
|
||||
&sLog2,
|
||||
"The sLog2 function."
|
||||
);
|
||||
|
||||
mod.def( "DanLog",
|
||||
&DanLog,
|
||||
"The DanLog function."
|
||||
);
|
||||
|
||||
return mod.ptr();
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from setuptools import setup
|
||||
from setuptools import Extension
|
||||
|
||||
setup( name = 'openlut',
|
||||
version = '0.0.2',
|
||||
description = 'OpenLUT is a practical color management library.',
|
||||
author = 'Sofus Rose',
|
||||
author_email = 'sofus@sofusrose.com',
|
||||
url = 'https://www.github.com/so-rose/openlut',
|
||||
|
||||
license = 'MIT Licence',
|
||||
|
||||
keywords = 'color image images processing',
|
||||
|
||||
install_requires = ['numpy', 'wand', 'scipy', 'pygame','PyOpenGL'],
|
||||
|
||||
classifiers = [
|
||||
'Development Status :: 3 - Alpha',
|
||||
|
||||
'License :: OSI Approved :: MIT License',
|
||||
|
||||
'Programming Language :: Python :: 3.5'
|
||||
]
|
||||
)
|
Binary file not shown.
|
@ -0,0 +1,54 @@
|
|||
from openlut import *
|
||||
|
||||
def runTest(inPath, path) :
|
||||
print('Open openlut.py and scroll down to the end to see the code that\'s working!')
|
||||
#Open any format image. Try it with exr/dpx/anything!
|
||||
img = ColMap.open(inPath + '/rock.exr') #Opens a test image 'test.exr', creating a ColMap object, automatically using the best image backend available to load the image at the correct bit depth.
|
||||
|
||||
'''
|
||||
gamma has gamma functions like gamma.sRGB, called by value like gamma.sRGB(val). All take one argument, the value (x), and returns the transformed value. Color doesn't matter for gamma.
|
||||
gamut has matrices, in 3x3 numpy array form. All are relative to ACES, with direction aptly named. So, gamut.XYZ is a matrix from ACES --> XYZ, while gamut.XYZinv goes from XYZ --> ACES. All use/are converted to the D65 illuminant, for consistency sake.
|
||||
'''
|
||||
|
||||
#gamma Functions: sRGB --> Linear.
|
||||
gFunc = Func(gamma.sRGBinv) #A Func Transform object using the sRGB-->Linear gamma formula. Apply to ColMaps!
|
||||
gFuncManualsRGB = Func(lambda val: ((val + 0.055) / 1.055) ** 2.4 if val > 0.04045 else val / 12.92) #It's generic - specify any gamma function, even inline with a lambda!
|
||||
|
||||
#LUT from Function: sRGB --> Linear
|
||||
oLut = LUT.lutFunc(gamma.sRGBinv) #A LUT Transform object, created from a gamma function. Size is 16384 by default. LUTs are faster!
|
||||
oLut.save(path + '/sRGB-->Lin.cube') #Saves the LUT to a format inferred from the extension. cube only for now!
|
||||
|
||||
#Opening LUTs from .cube files.
|
||||
lut = LUT.open(path + '/sRGB-->Lin.cube') #Opens the lut we just made into a different LUT object.
|
||||
lut.resized(17).save(path + '/sRGB-->Lin_tiny.cube') #Resizes the LUT, then saves it again to a much smaller file!
|
||||
|
||||
#Matrix Transformations
|
||||
simpleMat = ColMat(gamut.sRGBinv) #A Matrix Transform (ColMat) object, created from a color transform matrix for gamut transformations! This one is sRGB --> ACES.
|
||||
mat = ColMat(gamut.sRGBinv, gamut.XYZ, gamut.XYZinv, gamut.aRGB) * gamut.aRGBinv
|
||||
#Indeed, specify many matrices which auto-multiply into a single one! You can also combine them after, with simple multiplication.
|
||||
|
||||
#Applying and saving.
|
||||
img.apply(gFunc).save(path + '/openlut_gammafunc.png') #save saves an image using the appropriate image backend, based on the extension.
|
||||
img.apply(lut).save(path + '/openlut_lut-lin-16384.png') #apply applies any color transformation object that inherits from Transform - LUT, Func, ColMat, etc., or make your own! It's easy ;) .
|
||||
img.apply(lut.resized(17)).save(path + '/openlut_lut-lin-17.png') #Why so small? Because spline interpolation automatically turns on. It's identical to the larger LUT!
|
||||
img.apply(mat).save(path + '/openlut_mat.png') #Applies the gamut transformation.
|
||||
|
||||
#As a proof of concept, here's a long list of transformations that should, in sum, do nothing :) :
|
||||
|
||||
img.apply(lut).apply(LUT.lutFunc(gamma.sRGB)).apply(mat).apply(~mat).save('testpath/openlut_noop.png') #~mat is the inverse of mat. Easily undo the gamut operation!
|
||||
|
||||
#Format Test: All output images are in Linear ACES.
|
||||
tImg = img.apply(mat)
|
||||
tImg.save(path + '/output.exr')
|
||||
tImg.save(path + '/output.dpx')
|
||||
tImg.save(path + '/output.png')
|
||||
tImg.save(path + '/output.jpg')
|
||||
tImg.save(path + '/output.tif') #All sorts of formats work! Bit depth is 16, unless you say something else.
|
||||
|
||||
#Compression is impossible right now - wand is being difficult.
|
||||
#Keep in mind, values are clipped from 0 to 1 when done. Scary transforms can make this an issue!
|
||||
|
||||
#Color management is simple: openlut doesn't touch your data, unless you tell it to with a Transform. So, the data that goes in, goes out, unless a Transform was applied.
|
||||
|
||||
if __name__ == "__main__" :
|
||||
runTest()
|
|
@ -0,0 +1,36 @@
|
|||
import os, sys
|
||||
|
||||
import unittest as ut
|
||||
|
||||
from os import path
|
||||
|
||||
sys.path.append(0, path.abspath('..'))
|
||||
|
||||
from openlut import *
|
||||
|
||||
def verifyLUT(lut, title, size, iRange) :
|
||||
assertEqual(lut.title, title)
|
||||
assertEqual(lut.size, size)
|
||||
assertEqual(lut.range, iRange)
|
||||
assertEqual(lut.dims, 1)
|
||||
assertEqual(lut.ID, np.linspace(iRange[0], iRange[1], size))
|
||||
|
||||
assertEqual(str(lut.array), 'float32')
|
||||
|
||||
class testLUT(ut.TestCase) :
|
||||
def test_init(self) :
|
||||
lut = LUT(title="test", size=4096, iRange=(-0.125, 1.125))
|
||||
|
||||
verifyLUT(lut, 'test', 4096, (-0.125, 1.125))
|
||||
|
||||
#~ assertEqual(lut.title, 'test')
|
||||
#~ assertEqual(lut.size, 4096)
|
||||
#~ assertEqual(lut.range, (-0.125, 1.125))
|
||||
#~ assertEqual(lut.dims, 1)
|
||||
#~ assertEqual(lut.ID, np.linspace(-0.125, 1.125, 4096))
|
||||
#~ assertEqual(lut.array, np.linspace(-0.125, 1.125, 4096))
|
||||
|
||||
def test_func(self) :
|
||||
lut = LUT.lutFunc(gamma.sRGB, title='test', size=4096, iRange=(-0.125, 1.125))
|
||||
|
||||
verifyLUT(lut, 'test', 4096, (-0.125, 1.125))
|
Loading…
Reference in New Issue