From b5194ca018cbf9c12fb1d0734479967b00d7a6cd Mon Sep 17 00:00:00 2001 From: Andrey Golovizin Date: Sat, 9 Aug 2014 13:53:37 +0200 Subject: [PATCH] Add image.SubImage, move page-related logic to page.* classes. --- pixelocr/image.py | 105 +++++++++++++++++++++++++++++----------------- pixelocr/page.py | 77 ++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 39 deletions(-) create mode 100644 pixelocr/page.py diff --git a/pixelocr/image.py b/pixelocr/image.py index 3cdbcdb..ebb1177 100644 --- a/pixelocr/image.py +++ b/pixelocr/image.py @@ -31,42 +31,70 @@ def _is_nonblank(bitmap): class Image(object): """Basic image class.""" - child_cls = None - def __init__(self, data): - self._data = data + self.parent = self + self.data = data + self.y1 = 0 + self.y2 = self.width + self.x1 = 0 + self.x2 = self.height def __getitem__(self, key): - return type(self)(self._data.__getitem__(key)) + """Return a SubImage for the specified region.""" + + def indices(sliceobj, length): + """Decode a slice object and return a pair of end:start indices.""" + if sliceobj is None: + return 0, length + elif isinstance(sliceobj, int): + return sliceobj, sliceobj + 1 + elif isinstance(sliceobj, slice): + start, end, stride = sliceobj.indices(length) + if stride != 1: + raise NotImplementedError + return start, end + else: + raise NotImplementedError(sliceobj) + + if not isinstance(key, tuple): + yslice = key + xslice = None + else: + yslice, xslice = key + + ystart, yend = indices(yslice, self.height) + xstart, xend = indices(xslice, self.width) + + y1 = self.y1 + ystart + y2 = self.y1 + yend + x1 = self.x1 + xstart + x2 = self.x1 + xend + return SubImage(self.parent, y1, y2, x1, x2) def _repr_png_(self): buf = BytesIO() - imsave(buf, self._data) + imsave(buf, self.data) return buf.getvalue() @classmethod def fromfile(cls, filename): return cls(imread(filename)) - @classmethod - def space(cls, height, width): - return cls(np.ones((height, width))) - @property def shape(self): - return self._data.shape + return self.data.shape - @property + @cached_property def T(self): - return type(self)(self._data.swapaxes(0, 1)) + return type(self)(self.data.swapaxes(0, 1)) @property def height(self): - return self._data.shape[0] + return self.data.shape[0] @property def width(self): - return self._data.shape[1] + return self.data.shape[1] @cached_property def bitmap(self): @@ -76,7 +104,7 @@ class Image(object): 1 = black (letter) pixel """ - grayscale = rgb2gray(self._data) + grayscale = rgb2gray(self.data) return (grayscale < 1).astype('b') @cached_property @@ -123,10 +151,28 @@ class Image(object): bottom_margin = _get_margin_height(reversed(self.bitmap)) return self[top_margin:self.height - bottom_margin, :] - def _iter_children(self, min_space): - if self.child_cls is None: - raise NotImplementedError +class SubImage(Image): + def __init__(self, parent, y1, y2, x1, x2): + self.parent = parent + self.y1 = y1 + self.y2 = y2 + self.x1 = x1 + self.x2 = x2 + + @property + def data(self): + return self.parent.data[self.y1:self.y2, self.x1:self.x2] + + @property + def bitmap(self): + return self.parent.bitmap[self.y1:self.y2, self.x1:self.x2] + + @property + def T(self): + return type(self)(self.parent.T, self.x1, self.x2, self.y1, self.y2) + + def _iter_lines(self, min_space, T=False): line_start = None prev_line_end = 0 @@ -136,28 +182,9 @@ class Image(object): line_start = i height = line_start - prev_line_end if height >= min_space: - yield self.child_cls.space(height, self.width) + yield self[prev_line_end:line_start] else: if line_start is not None: - yield self.child_cls(self._data[line_start:i, :]) + yield self[line_start:i,:] line_start = None prev_line_end = i - - -class Letter(Image): - pass - - -class Line(Image): - child_cls = Letter - - def __iter__(self): - for rotated_letter in self.T._iter_children(min_space=10): - yield rotated_letter.T.strip() - - -class Page(Image): - child_cls = Line - - def __iter__(self): - return self._iter_children(min_space=200) diff --git a/pixelocr/page.py b/pixelocr/page.py new file mode 100644 index 0000000..9f79d60 --- /dev/null +++ b/pixelocr/page.py @@ -0,0 +1,77 @@ +# Copyright (C) 2014 Andrey Golovizin +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from .image import Image + + +class PageObject(object): + def __init__(self, image): + self.image = image + + def _repr_png_(self): + return self.image._repr_png_() + + @property + def shape(self): + return self.image.shape + + @property + def height(self): + return self.image.width + + @property + def width(self): + return self.image.width + + @property + def x1(self): + return self.image.x1 + + @property + def x2(self): + return self.image.x2 + + @property + def y1(self): + return self.image.y1 + + @property + def y2(self): + return self.image.y2 + + @property + def x1(self): + return self.image.x1 + + @property + def x2(self): + return self.image.x2 + + +class Page(PageObject): + def __iter__(self): + for line_img in self.image._iter_lines(min_space=200): + yield Line(line_img) + + +class Line(PageObject): + def __iter__(self): + for rotated_letter_img in self.image.T._iter_lines(min_space=10, T=True): + yield Letter(rotated_letter_img.T.strip()) + + +class Letter(PageObject): + pass