#! /usr/bin/env python
'''
contains the ImageScanner class which is used for scanning images
'''
from __future__ import division, with_statement, print_function
from itertools import *
import numpy as np
# ------------------------------------------------------------------------------
[docs]class ImageScanner(object):
'''Used for scanning images and producting image pathches through various techniques'''
def __init__(self, image, min_resolution=(100, 100), max_resolution=(200, 200),
patch_resolution=None, resample=0, rotation=None, **kwargs):
'''
Args:
image (PIL.Image): Python Imaging Library Image object
min_resolution opt(tuple):
minimum sampling size
default: (100, 100)
max_resolution opt(tuple):
maximum sampling size
default: (200, 200)
patch_resolution opt(tuple):
output patch resolution (x, y)
default: None
resample opt(str):
resampling tfilter used by PIL.Image
options include:
`PIL.Image.NEAREST` (use nearest neighbour)
`PIL.Image.BILINEAR` (linear interpolation)
`PIL.Image.BICUBIC` (cubic spline interpolation)
`PIL.Image.LANCZOS` (a high-quality downsampling filter)
default: 0
rotation opt(int or str):
degree of rotation to be applied to output patches
options include: 0, 90, 180, 270, 'random'
default: None
'''
self._image = image
self._min_resolution = min_resolution
self._max_resolution = max_resolution
self._patch_resolution = min_resolution
if patch_resolution:
self._patch_resolution = patch_resolution
self._resample = resample
self._rotation = rotation # convenience attribute
self.__vars = self._image, self._min_resolution, self._max_resolution
# --------------------------------------------------------------------------
def _even_resolutions(self, patches):
'''
generate a list of evenly spaced patch resolutions
'''
img, min_, max_ = self.__vars
x = np.linspace(min_[0], max_[0], patches, dtype=int)
y = np.linspace(min_[1], max_[1], patches, dtype=int)
return izip(x, y)
def _random_resolutions(self, patches):
'''
generate a list of randomly spaced patch resolutions
'''
img, min_, max_ = self.__vars
# steps should be set to the patch size range of lesser of
# max_resolution's two dimensions
steps = max_[0] - min_[0]
if max_[0] > max_[1]:
steps = max_[1] - min_[1]
if steps == 0:
steps = 1
x = np.linspace(min_[0], max_[0], steps, dtype=int)
y = np.linspace(min_[1], max_[1], steps, dtype=int)
for index in np.random.choice(steps, size=patches, replace=True):
yield x[index-1], y[index-1]
def _get_patch(self, bbox):
'''
return a cropped (resized amd/or rotated) image based upon bounding box
'''
img = self._image.crop(bbox)
rotations = [0, 90, 180, 270]
if self._patch_resolution:
img = img.resize(self._patch_resolution, self._resample)
if self._rotation:
if self._rotation == 'random':
img = img.rotate( np.random.choice(rotations) )
elif self._rotation in rotations:
img = img.rotate(self._rotation)
return img
# --------------------------------------------------------------------------
[docs] def get_resolutions(self, num=10, spacing='even'):
'''
generates a list of patch resolutions
Args:
num opt(int):
number of resolutions returned
default: 10
spacing opt(str):
spacing between resolution sizes
options include: 'even', 'random'
default: 'even'
Yields:
tuple: (x, y) resolution
'''
if spacing == 'even':
return self._even_resolutions(num)
elif spacing =='random':
return self._random_resolutions(num)
[docs] def grid_scan(self, resolutions=10, spacing='even', **kwargs):
'''
scans entire image in a grid-like fashion
Args:
resolutions opt(int):
number of sampling patch resolutions to return
a single grid produces multiple patches (image / sampling resolution)
default: 10
spacing opt(str):
spacing between resolution sizes
options include: 'even', 'random'
default: 'even'
Yields:
PIL.Image: cropped (resized and/or rotated) patch
'''
def _grid_scan(resolution):
'''
scan entire area of image given a sample resolution
'''
img, min_, max_ = self.__vars
bbox_x, bbox_y = img.getbbox()[-2:]
x_sample, y_sample = resolution
x_scans = int(bbox_x / x_sample)
y_scans = int(bbox_y / y_sample)
for row in xrange(y_scans):
upper = y_sample * row
lower = upper + y_sample
for col in xrange(x_scans):
left = x_sample * col
right = left + x_sample
bbox = (left, upper, right, lower)
yield self._get_patch(bbox)
rez = self.get_resolutions(resolutions, spacing)
output = map(_grid_scan, rez)
return chain.from_iterable(output)
[docs] def random_scan(self, patches=100, **kwargs):
'''
generates patches of random sample size and location from image
Args:
patches opt(int):
number of patches returned
default: 100
Yields:
PIL.Image: cropped (resized and/or rotated) patch
'''
img, min_, max_ = self.__vars
for x1, y1 in self._random_resolutions(patches):
x, y = img.size
left = np.random.choice(range(x - x1))
right = left + x1
upper = np.random.choice(range(y - y1))
lower = upper + y1
bbox = (left, upper, right, lower)
yield self._get_patch(bbox)
# ------------------------------------------------------------------------------
__all__ = ['ImageScanner']
def main():
pass
if __name__ == '__main__':
help(main)