import io
import os
import numpy
import pathlib2 as pathlib
from six.moves import urllib
from skimage.measure import block_reduce
from asap.utilities.pillow_utils import Image
from asap.module.render_module import RenderModuleException
from asap.utilities import uri_utils
try:
xrange
except NameError:
xrange = range
PIL_filters = {
"NEAREST": Image.NEAREST,
"BOX": Image.BOX,
"BILINEAR": Image.BILINEAR,
"HAMMING": Image.HAMMING,
"BICUBIC": Image.BICUBIC,
"LANCZOS": Image.LANCZOS
}
block_funcs = {
'mean': numpy.mean,
'median': numpy.median,
'min': numpy.min
}
[docs]class CreateMipMapException(RenderModuleException):
"""Exception raised when there is a problem creating a mipmap"""
pass
[docs]def writeImage(img, outpath, force_redo):
if not force_redo:
# FIXME uri_handler writebytes implementation allows force_redo....
raise NotImplementedError(
"toggling force_redo is not supported in this version. "
"Mipmaps must be regenerated.")
# TODO does this need a step to try to register the extension?
imgfmt = Image.EXTENSION[os.path.splitext(
urllib.parse.urlparse(outpath).path)[-1]]
imgio = io.BytesIO()
img.save(imgio, format=imgfmt)
uri_utils.uri_writebytes(outpath, imgio.getvalue())
[docs]def mipmap_block_reduce(im, levels_file_map, block_func="mean",
force_redo=True, **kwargs):
try:
reduce_func = block_funcs[block_func]
except KeyError as e:
raise CreateMipMapException(
"invalid block_reduce function {}".format(e))
tempimg = numpy.array(im)
target_dtype = tempimg.dtype
lastlevel = 0
for level, outpath in levels_file_map.items():
for i in xrange(lastlevel, level):
tempimg = block_reduce(tempimg, (2, 2), func=reduce_func).astype(
target_dtype)
# is it okay to do non-progressive downsampling (below)?
# tempimg = block_reduce(
# tempimg, (2 * (level - lastlevel), 2 * (level - lastlevel)),
# func=reduce_func)
lastlevel = level
writeImage(Image.fromarray(tempimg), outpath, force_redo)
[docs]def mipmap_PIL(im, levels_file_map, ds_filter="NEAREST",
force_redo=True, **kwargs):
try:
PIL_filter = PIL_filters[ds_filter]
except KeyError as e:
raise CreateMipMapException("invalid PIL filter {}".format(e))
origsize = im.size
for level, outpath in levels_file_map.items():
newsize = tuple(map(lambda x: x//(2**level), origsize))
dwnImage = im.resize(newsize, resample=PIL_filter)
writeImage(dwnImage, outpath, force_redo)
method_funcs = {
'PIL': mipmap_PIL,
'block_reduce': mipmap_block_reduce
}
[docs]def create_mipmaps_uri(inputImage, outputDirectory=None, method="block_reduce",
mipmaplevels=[1, 2, 3], outputformat='tif',
convertTo8bit=True, force_redo=True,
**kwargs):
"""function to create downsampled images from an input image
Parameters
==========
inputImage: str
path to input image
outputDirectory: str
path to save output images (default to current directory)
mipmaplevels: list or tuple
list or tuple of integers (default to (1,2,3))
outputformat: str
string representation of extension of image format (default tif)
convertTo8bit: boolean
whether to convert the image to 8 bit, dividing each value by 255
force_redo: boolean
whether to recreate mip map images if they already exist
method: str
string corresponding to downsampling method
block_func: str
string corresponding to function used by block_reduce
ds_filter: str
string corresponding to PIL downsample mode
Returns
=======
list
list of output images created in order of levels
Raises
======
MipMapException
if an image cannot be created for some reason
"""
# Need to check if the level 0 image exists
# TODO this is for uri implementation
inputImagepath = urllib.parse.urlparse(inputImage).path
im = Image.open(io.BytesIO(uri_utils.uri_readbytes(inputImage)))
if convertTo8bit:
table = [i//256 for i in range(65536)]
im = im.convert('I')
im = im.point(table, 'L')
levels_uri_map = {int(level): uri_utils.uri_join(
outputDirectory, str(level), '{basename}.{fmt}'.format(
basename=inputImagepath.lstrip("/"), fmt=outputformat))
for level in mipmaplevels}
try:
method_funcs[method](
im, levels_uri_map, force_redo=force_redo, **kwargs)
except KeyError as e:
raise CreateMipMapException("invalid method {}".format(e))
return levels_uri_map
[docs]def create_mipmaps_legacy(inputImage, outputDirectory='.', *args, **kwargs):
"""legacy create_mipmaps function"""
outputDirectory_uri = pathlib.Path(outputDirectory).resolve().as_uri()
inputImage_uri = pathlib.Path(inputImage).resolve().as_uri()
levels_uri_map = create_mipmaps_uri(
inputImage_uri, outputDirectory_uri, *args, **kwargs)
levels_file_map = {
lvl: urllib.parse.unquote(urllib.parse.urlparse(u).path)
for lvl, u in levels_uri_map.items()}
return levels_file_map
create_mipmaps = create_mipmaps_legacy