# Copyright (C) 2012, Almar Klein
#
# This code is subject to the (new) BSD license:
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the <organization> nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" Module images2ims
Use PIL to create a series of images.
"""
import os
from operator import itemgetter
from string import digits
try:
import numpy as np
except ImportError:
np = None
try:
import PIL
except ImportError:
PIL = None
[docs]def checkImages(images):
"""Check numpy images and correct intensity range etc.
The same for all movie formats.
:param images:
"""
# Init results
images2 = []
for im in images:
if PIL and isinstance(im, PIL.Image.Image):
# We assume PIL images are alright
images2.append(im)
elif np and isinstance(im, np.ndarray):
# Check and convert dtype
if im.dtype == np.uint8:
images2.append(im) # Ok
elif im.dtype in [np.float32, np.float64]:
theMax = im.max()
if theMax > 128 and theMax < 300:
pass # assume 0:255
else:
im = im.copy()
im[im < 0] = 0
im[im > 1] = 1
im *= 255
images2.append(im.astype(np.uint8))
else:
im = im.astype(np.uint8)
images2.append(im)
# Check size
if im.ndim == 2:
pass # ok
elif im.ndim == 3:
if im.shape[2] not in [3, 4]:
msg = "This array can not represent an image."
raise ValueError(msg)
else:
msg = "This array can not represent an image."
raise ValueError(msg)
else:
raise ValueError("Invalid image type: " + str(type(im)))
# Done
return images2
def _getFilenameParts(filename):
if "*" in filename:
return tuple(filename.split("*", 1))
return os.path.splitext(filename)
def _getFilenameWithFormatter(filename, N):
# Determine sequence number formatter
formatter = "%04i"
if N < 10:
formatter = "%i"
elif N < 100:
formatter = "%02i"
elif N < 1000:
formatter = "%03i"
# Insert sequence number formatter
part1, part2 = _getFilenameParts(filename)
return part1 + formatter + part2
def _getSequenceNumber(filename, part1, part2):
# Get string bit
seq = filename[len(part1) : -len(part2)]
# Get all numeric chars
seq2 = ""
for c in seq:
if c in digits:
seq2 += c
else:
break
# Make int and return
return int(seq2)
[docs]def writeIms(filename, images):
"""Export movie to a series of image files. If the filenenumber
contains an asterix, a sequence number is introduced at its
location. Otherwise the sequence number is introduced right
before the final dot.
To enable easy creation of a new directory with image files,
it is made sure that the full path exists.
Images should be a list consisting of PIL images or numpy arrays.
The latter should be between 0 and 255 for integer types, and
between 0 and 1 for float types.
:param filename:
:param images:
"""
# Check PIL
if PIL is None:
msg = "Need PIL to write series of image files."
raise RuntimeError(msg)
# Check images
images = checkImages(images)
# Get dirname and filename
filename = os.path.abspath(filename)
dirname, filename = os.path.split(filename)
# Create dir(s) if we need to
if not os.path.isdir(dirname):
os.makedirs(dirname)
# Insert formatter
filename = _getFilenameWithFormatter(filename, len(images))
# Write
seq = 0
for frame in images:
seq += 1
# Get filename
fname = os.path.join(dirname, filename % seq)
# Write image
if np and isinstance(frame, np.ndarray):
frame = PIL.Image.fromarray(frame)
frame.save(fname)
[docs]def readIms(filename, asNumpy=True):
"""readIms(filename, asNumpy=True)
Read images from a series of images in a single directory. Returns a
list of numpy arrays, or, if asNumpy is false, a list if PIL images.
:param filename:
:param bool asNumpy:
"""
# Check PIL
if PIL is None:
msg = "Need PIL to read a series of image files."
raise RuntimeError(msg)
# Check NumPy
if asNumpy and np is None:
msg = "Need NumPy to return numpy arrays."
raise RuntimeError(msg)
# Get dirname and filename
filename = os.path.abspath(filename)
dirname, filename = os.path.split(filename)
# Check dir exists
if not os.path.isdir(dirname):
raise OSError("Directory not found: " + str(dirname))
# Get two parts of the filename
part1, part2 = _getFilenameParts(filename)
# Init images
images = []
# Get all files in directory
for fname in os.listdir(dirname):
if fname.startswith(part1) and fname.endswith(part2):
# Get sequence number
nr = _getSequenceNumber(fname, part1, part2)
# Get Pil image and store copy (to prevent keeping the file)
im = PIL.Image.open(os.path.join(dirname, fname))
images.append((im.copy(), nr))
# Sort images
images.sort(key=itemgetter(1))
images = [im[0] for im in images]
# Convert to numpy if needed
if asNumpy:
images2 = images
images = []
for im in images2:
# Make without palette
if im.mode == "P":
im = im.convert()
# Make numpy array
a = np.asarray(im)
if len(a.shape) == 0:
msg = "Too little memory to convert PIL image to array"
raise MemoryError(msg)
# Add
images.append(a)
# Done
return images