예제 #1
0
 def test_str(self):
     with Image.open(
             assets.path_to(
                 'SBB0000F29300010000/data/OCR-D-IMG/FILE_0001_IMAGE.tif')
     ) as img:
         exif = OcrdExif(img)
     print(str(exif.to_xml()))
예제 #2
0
 def test_jp2(self):
     exif = OcrdExif(Image.open(assets.path_to('kant_aufklaerung_1784-jp2/data/OCR-D-IMG/INPUT_0020.jp2')))
     self.assertEqual(exif.width, 1457)
     self.assertEqual(exif.height, 2084)
     self.assertEqual(exif.xResolution, 1)
     self.assertEqual(exif.yResolution, 1)
     self.assertEqual(exif.resolution, 1)
     self.assertEqual(exif.photometricInterpretation, 'RGB')
예제 #3
0
 def test_png2(self):
     exif = OcrdExif(Image.open(assets.path_to('scribo-test/data/OCR-D-IMG-BIN-SAUVOLA/OCR-D-IMG-orig_sauvola_png.jpg')))
     self.assertEqual(exif.width, 2097)
     self.assertEqual(exif.height, 3062)
     self.assertEqual(exif.xResolution, 1)
     self.assertEqual(exif.yResolution, 1)
     self.assertEqual(exif.resolution, 1)
     self.assertEqual(exif.photometricInterpretation, '1')
예제 #4
0
 def test_jpg(self):
     exif = OcrdExif(Image.open(assets.path_to('leptonica_samples/data/OCR-D-IMG/OCR-D-IMG_1555_007.jpg')))
     self.assertEqual(exif.width, 944)
     self.assertEqual(exif.height, 1472)
     self.assertEqual(exif.xResolution, 1)
     self.assertEqual(exif.yResolution, 1)
     self.assertEqual(exif.resolution, 1)
     self.assertEqual(exif.resolutionUnit, 'inches')
     self.assertEqual(exif.photometricInterpretation, 'RGB')
예제 #5
0
 def test_png1(self):
     exif = OcrdExif(Image.open(assets.path_to('kant_aufklaerung_1784-binarized/data/OCR-D-IMG-BIN/BIN_0020.png')))
     self.assertEqual(exif.width, 1457)
     self.assertEqual(exif.height, 2084)
     self.assertEqual(exif.xResolution, 300)
     self.assertEqual(exif.yResolution, 300)
     self.assertEqual(exif.resolution, 300)
     self.assertEqual(exif.compression, None)
     self.assertEqual(exif.photometricInterpretation, '1')
예제 #6
0
 def test_tiff(self):
     exif = OcrdExif(Image.open(assets.path_to('SBB0000F29300010000/data/OCR-D-IMG/FILE_0001_IMAGE.tif')))
     self.assertEqual(exif.width, 2875)
     self.assertEqual(exif.height, 3749)
     self.assertEqual(exif.xResolution, 300)
     self.assertEqual(exif.yResolution, 300)
     self.assertEqual(exif.resolution, 300)
     self.assertEqual(exif.compression, 'jpeg')
     self.assertEqual(exif.photometricInterpretation, 'RGB')
예제 #7
0
 def test_png2(self):
     with Image.open(assets.path_to('scribo-test/data/OCR-D-PRE-BIN-SAUVOLA/OCR-D-PRE-BIN-SAUVOLA_0001-BIN_sauvola.png')) as img:
         exif = OcrdExif(img)
     self.assertEqual(exif.width, 2097)
     self.assertEqual(exif.height, 3062)
     self.assertEqual(exif.xResolution, 1)
     self.assertEqual(exif.yResolution, 1)
     self.assertEqual(exif.resolution, 1)
     self.assertEqual(exif.photometricInterpretation, '1')
     self.assertEqual(exif.resolutionUnit, 'inches')
예제 #8
0
def exif_from_filename(image_filename):
    """
    Create `OcrdExif </../../ocrd_models/ocrd_models.ocrd_exif.html>`_
    by opening an image file with PIL and reading its metadata.

    Arguments:
        * image_filename (string):
    """
    if image_filename is None:
        raise Exception("Must pass 'image_filename' to 'exif_from_filename'")
    return OcrdExif(Image.open(image_filename))
예제 #9
0
 def test_png1(self):
     with Image.open(assets.path_to('kant_aufklaerung_1784-binarized/data/OCR-D-IMG-BIN/BIN_0020.png')) as img:
         exif = OcrdExif(img)
     self.assertEqual(exif.width, 1457)
     self.assertEqual(exif.height, 2084)
     self.assertEqual(exif.xResolution, 295 if pil_below_83 else 294)
     self.assertEqual(exif.yResolution, 295 if pil_below_83 else 294)
     self.assertEqual(exif.resolution, 295 if pil_below_83 else 294)
     self.assertEqual(exif.resolutionUnit, 'inches')
     self.assertEqual(exif.compression, None)
     self.assertEqual(exif.photometricInterpretation, '1')
예제 #10
0
def exif_from_filename(image_filename):
    """
    Create :py:class:`~ocrd_models.ocrd_exif.OcrdExif`
    by opening an image file with PIL and reading its metadata.

    Arguments:
        image_filename (str): Local image path name (relative to workspace).
    """
    if image_filename is None:
        raise Exception("Must pass 'image_filename' to 'exif_from_filename'")
    with Image.open(image_filename) as pil_img:
        ocrd_exif = OcrdExif(pil_img)
    return ocrd_exif
예제 #11
0
    def resolve_image_exif(self, image_url):
        """
        Get the EXIF metadata about an image URL as :class:`OcrdExif`

        Args:
            image_url (string) : URL of image

        Return
            :class:`OcrdExif`
        """
        f = next(self.mets.find_files(url=image_url), OcrdFile(None, url=image_url))
        image_filename = self.download_file(f).local_filename
        with Image.open(image_filename) as pil_img:
            ocrd_exif = OcrdExif(pil_img)
        return ocrd_exif
예제 #12
0
def check_dpi(img):
    try:
        if isinstance(img, Image.__class__):
            pil_image = img
        elif isinstance(img, str):
            pil_image = Image.open(img)
        else:
            pil_image = cv2pil(img)
        exif = OcrdExif(pil_image)
        resolution = exif.resolution
        if resolution == 1:
            raise Exception()
        if exif.resolutionUnit == 'cm':
            resolution /= 2.54
        return int(resolution)
    except Exception as e:
        print(e)
        return 230
예제 #13
0
    def resolve_image_exif(self, image_url):
        """
        Get the EXIF metadata about an image URL as :class:`OcrdExif`

        Args:
            image_url (string) : URL of image

        Return
            :class:`OcrdExif`
        """
        files = self.mets.find_files(url=image_url)
        f = files[0] if files else OcrdFile(None, url=image_url)
        image_filename = self.download_file(f).local_filename

        if image_url not in self.image_cache['exif']:
            # FIXME must be in the right directory
            self.image_cache['exif'][image_url] = OcrdExif(
                Image.open(image_filename))
        return self.image_cache['exif'][image_url]
예제 #14
0
    def resolve_image_exif(self, image_url):
        """
        Get the EXIF metadata about an image URL as :class:`OcrdExif`

        Args:
            image_url (string) : URL of image

        Return
            :class:`OcrdExif`
        """
        files = self.mets.find_files(url=image_url)
        if files:
            image_filename = self.download_file(files[0]).local_filename
        else:
            image_filename = self.download_url(image_url)

        if image_url not in self.image_cache['exif']:
            self.image_cache['exif'][image_url] = OcrdExif(
                Image.open(image_filename))
        return self.image_cache['exif'][image_url]
예제 #15
0
파일: workspace.py 프로젝트: stweil/pyocrd
    def image_from_page(self,
                        page,
                        page_id,
                        fill='background',
                        transparency=False,
                        feature_selector='',
                        feature_filter=''):
        """Extract an image for a PAGE-XML page from the workspace.

        Given ``page``, a PAGE PageType object, extract its PIL.Image,
        either from its AlternativeImage (if it exists), or from its
        @imageFilename (otherwise). Also crop it, if a Border exists,
        and rotate it, if any @orientation angle is annotated.

        If ``feature_selector`` and/or ``feature_filter`` is given, then
        select/filter among the @imageFilename image and the available
        AlternativeImages the last one which contains all of the selected,
        but none of the filtered features (i.e. @comments classes), or
        raise an error.

        (Required and produced features need not be in the same order, so
        ``feature_selector`` is merely a mask specifying Boolean AND, and
        ``feature_filter`` is merely a mask specifying Boolean OR.)

        If the chosen image does not have the feature "cropped" yet, but
        a Border exists, and unless "cropped" is being filtered, then crop it.
        Likewise, if the chosen image does not have the feature "deskewed" yet,
        but an @orientation angle is annotated, and unless "deskewed" is being
        filtered, then rotate it. (However, if @orientation is above the
        [-45°,45°] interval, then apply as much transposition as possible first,
        unless "rotated-90" / "rotated-180" / "rotated-270" is being filtered.)

        Cropping uses a polygon mask (not just the bounding box rectangle).
        Areas outside the polygon will be filled according to ``fill``:

        - if ``background`` (the default),
          then fill with the median color of the image;
        - otherwise, use the given color, e.g. ``white`` or (255,255,255).

        Moreover, if ``transparency`` is true, and unless the image already
        has an alpha channel, then add an alpha channel which is fully opaque
        before cropping and rotating. (Thus, only the exposed areas will be
        transparent afterwards, for those that can interpret alpha channels).

        Return a tuple:

         * the extracted image,
         * a dictionary with information about the extracted image:

           - ``transform``: a Numpy array with an affine transform which
             converts from absolute coordinates to those relative to the image,
             i.e. after cropping to the page's border / bounding box (if any)
             and deskewing with the page's orientation angle (if any)
           - ``angle``: the rotation/reflection angle applied to the image so far,
           - ``features``: the AlternativeImage @comments for the image, i.e.
             names of all operations that lead up to this result,

         * an OcrdExif instance associated with the original image.

        (The first two can be used to annotate a new AlternativeImage,
         or be passed down with ``image_from_segment``.)

        Example:

         * get a raw (colored) but already deskewed and cropped image:

           ``
           page_image, page_coords, page_image_info = workspace.image_from_page(
                 page, page_id,
                 feature_selector='deskewed,cropped',
                 feature_filter='binarized,grayscale_normalized')
           ``
        """
        log = getLogger('ocrd.workspace.image_from_page')
        page_image = self._resolve_image_as_pil(page.imageFilename)
        page_image_info = OcrdExif(page_image)
        page_coords = dict()
        # use identity as initial affine coordinate transform:
        page_coords['transform'] = np.eye(3)
        # interim bbox (updated with each change to the transform):
        page_bbox = [0, 0, page_image.width, page_image.height]
        page_xywh = {
            'x': 0,
            'y': 0,
            'w': page_image.width,
            'h': page_image.height
        }

        border = page.get_Border()
        # page angle: PAGE @orientation is defined clockwise,
        # whereas PIL/ndimage rotation is in mathematical direction:
        page_coords['angle'] = -(page.get_orientation() or 0)
        # map angle from (-180,180] to [0,360], and partition into multiples of 90;
        # but avoid unnecessary large remainders, i.e. split symmetrically:
        orientation = (page_coords['angle'] + 45) % 360
        orientation = orientation - (orientation % 90)
        skew = (page_coords['angle'] % 360) - orientation
        skew = 180 - (180 - skew) % 360  # map to [-45,45]
        page_coords['angle'] = 0  # nothing applied yet (depends on filters)
        log.debug("page '%s' has %s orientation=%d skew=%.2f", page_id,
                  "border," if border else "", orientation, skew)

        # initialize AlternativeImage@comments classes as empty:
        page_coords['features'] = ''
        alternative_image = None
        alternative_images = page.get_AlternativeImage()
        if alternative_images:
            # (e.g. from page-level cropping, binarization, deskewing or despeckling)
            if feature_selector or feature_filter:
                alternative_image = None
                # search from the end, because by convention we always append,
                # and among multiple satisfactory images we want the most recent:
                for alternative_image in reversed(alternative_images):
                    features = alternative_image.get_comments()
                    if (all(feature in features
                            for feature in feature_selector.split(',')
                            if feature) and
                            not any(feature in features
                                    for feature in feature_filter.split(',')
                                    if feature)):
                        break
                    else:
                        alternative_image = None
            else:
                alternative_image = alternative_images[-1]
                features = alternative_image.get_comments()
            if alternative_image:
                log.debug("Using AlternativeImage %d (%s) for page '%s'",
                          alternative_images.index(alternative_image) + 1,
                          features, page_id)
                page_image = self._resolve_image_as_pil(
                    alternative_image.get_filename())
                page_coords['features'] = features

        # adjust the coord transformation to the steps applied on the image,
        # and apply steps on the existing image in case it is missing there,
        # but traverse all steps (crop/reflect/rotate) in a particular order:
        # - existing image features take priority (in the order annotated),
        # - next is cropping (if necessary but not already applied),
        # - next is reflection (if necessary but not already applied),
        # - next is rotation (if necessary but not already applied).
        # This helps deal with arbitrary workflows (e.g. crop then deskew,
        # or deskew then crop), regardless of where images are generated.
        alternative_image_features = page_coords['features'].split(',')
        for i, feature in enumerate(
                alternative_image_features + (['cropped'] if (
                    border and not 'cropped' in page_coords['features']
                    and not 'cropped' in feature_filter.split(',')) else []) +
            (['rotated-%d' % orientation] if
             (orientation and not 'rotated-%d' %
              orientation in page_coords['features'] and not 'rotated-%d' %
              orientation in feature_filter.split(',')) else []) +
            (['deskewed'] if
             (skew and not 'deskewed' in page_coords['features']
              and not 'deskewed' in feature_filter.split(',')) else []) +
                # not a feature to be added, but merely as a fallback position
                # to always enter loop at i == len(alternative_image_features)
            ['_check']):
            # image geometry vs feature consistency can only be checked
            # after all features on the existing AlternativeImage have
            # been adjusted for in the transform, and when there is a mismatch,
            # additional steps applied here would only repeat the respective
            # error message; so we only check once at the boundary between
            # existing and new features
            # FIXME we should check/enforce consistency when _adding_ AlternativeImage
            if (i == len(alternative_image_features)
                    and not (page_xywh['w'] - 2 < page_image.width <
                             page_xywh['w'] + 2 and page_xywh['h'] - 2 <
                             page_image.height < page_xywh['h'] + 2)):
                log.error(
                    'page "%s" image (%s; %dx%d) has not been cropped properly (%dx%d)',
                    page_id, page_coords['features'], page_image.width,
                    page_image.height, page_xywh['w'], page_xywh['h'])
            # adjust transform to feature, possibly apply feature to image
            if feature == 'cropped':
                page_points = border.get_Coords().points
                log.debug(
                    "Using explicitly set page border '%s' for page '%s'",
                    page_points, page_id)
                # get polygon outline of page border:
                page_polygon = np.array(polygon_from_points(page_points),
                                        dtype=np.int32)
                page_polygon = transform_coordinates(page_polygon,
                                                     page_coords['transform'])
                page_polygon = np.round(page_polygon).astype(np.int32)
                page_bbox = bbox_from_polygon(page_polygon)
                # get size of the page after cropping but before rotation:
                page_xywh = xywh_from_bbox(*page_bbox)
                # subtract offset in affine coordinate transform:
                # (consistent with image cropping or AlternativeImage below)
                page_coords['transform'] = shift_coordinates(
                    page_coords['transform'],
                    np.array([-page_xywh['x'], -page_xywh['y']]))
                # crop, if (still) necessary:
                if not 'cropped' in page_coords['features']:
                    log.debug(
                        "Cropping %s for page '%s' to border",
                        "AlternativeImage" if alternative_image else "image",
                        page_id)
                    # create a mask from the page polygon:
                    page_image = image_from_polygon(page_image,
                                                    page_polygon,
                                                    fill=fill,
                                                    transparency=transparency)
                    # recrop into page rectangle:
                    page_image = crop_image(page_image, box=page_bbox)
                    page_coords['features'] += ',cropped'

            elif feature == 'rotated-%d' % orientation:
                # Transpose in affine coordinate transform:
                # (consistent with image transposition or AlternativeImage below)
                transposition = {
                    90: Image.ROTATE_90,
                    180: Image.ROTATE_180,
                    270: Image.ROTATE_270
                }.get(orientation)  # no default
                page_coords['transform'] = transpose_coordinates(
                    page_coords['transform'], transposition,
                    np.array([0.5 * page_xywh['w'], 0.5 * page_xywh['h']]))
                (page_xywh['w'],
                 page_xywh['h']) = adjust_canvas_to_transposition(
                     [page_xywh['w'], page_xywh['h']], transposition)
                page_coords['angle'] = orientation
                # transpose, if (still) necessary:
                if not 'rotated-%d' % orientation in page_coords['features']:
                    log.info(
                        "Transposing %s for page '%s' by %d°",
                        "AlternativeImage" if alternative_image else "image",
                        page_id, orientation)
                    page_image = transpose_image(
                        page_image, {
                            90: Image.ROTATE_90,
                            180: Image.ROTATE_180,
                            270: Image.ROTATE_270
                        }.get(orientation))  # no default
                    page_coords['features'] += ',rotated-%d' % orientation
            elif feature == 'deskewed':
                # Rotate around center in affine coordinate transform:
                # (consistent with image rotation or AlternativeImage below)
                page_coords['transform'] = rotate_coordinates(
                    page_coords['transform'], skew,
                    np.array([0.5 * page_xywh['w'], 0.5 * page_xywh['h']]))
                page_coords['angle'] += skew
                # deskew, if (still) necessary:
                if not 'deskewed' in page_coords['features']:
                    log.info(
                        "Rotating %s for page '%s' by %.2f°",
                        "AlternativeImage" if alternative_image else "image",
                        page_id, skew)
                    page_image = rotate_image(page_image,
                                              skew,
                                              fill=fill,
                                              transparency=transparency)
                    page_coords['features'] += ',deskewed'
                (page_xywh['w'], page_xywh['h']) = adjust_canvas_to_rotation(
                    [page_xywh['w'], page_xywh['h']], skew)

        # verify constraints again:
        if not all(feature in page_coords['features']
                   for feature in feature_selector.split(',') if feature):
            raise Exception(
                'Found no AlternativeImage that satisfies all requirements ' +
                'selector="%s" in page "%s"' % (feature_selector, page_id))
        if any(feature in page_coords['features']
               for feature in feature_filter.split(',') if feature):
            raise Exception(
                'Found no AlternativeImage that satisfies all requirements ' +
                'filter="%s" in page "%s"' % (feature_filter, page_id))
        page_image.format = 'PNG'  # workaround for tesserocr#194
        return page_image, page_coords, page_image_info
def image_from_page(workspace, page, page_id):
    """Extract the Page image from the workspace.
    
    Given a PageType object, `page`, extract its PIL.Image from
    AlternativeImage if it exists. Otherwise extract the PIL.Image
    from imageFilename and crop it if a Border exists. Otherwise
    just return it.
    
    When cropping, respect any orientation angle annotated for
    the page (from page-level deskewing) by rotating the
    cropped image, respectively.
    
    If the resulting page image is larger than the bounding box of
    `page`, pass down the page's box coordinates with an offset of
    half the width/height difference.
    
    Return the extracted image, and the absolute coordinates of
    the page's bounding box / border (for passing down), and
    an OcrdExif instance associated with the original image.
    """
    page_image = workspace.resolve_image_as_pil(page.imageFilename)
    page_image_info = OcrdExif(page_image)
    page_xywh = {'x': 0, 'y': 0, 'w': page_image.width, 'h': page_image.height}
    # region angle: PAGE orientation is defined clockwise,
    # whereas PIL/ndimage rotation is in mathematical direction:
    page_xywh['angle'] = -(page.get_orientation() or 0)
    # FIXME: remove PrintSpace here as soon as GT abides by the PAGE standard:
    border = page.get_Border() or page.get_PrintSpace()
    if border:
        page_points = border.get_Coords().points
        LOG.debug("Using explictly set page border '%s' for page '%s'",
                  page_points, page_id)
        page_xywh = xywh_from_points(page_points)

    alternative_image = page.get_AlternativeImage()
    if alternative_image:
        # (e.g. from page-level cropping, binarization, deskewing or despeckling)
        # assumes implicit cropping (i.e. page_xywh has been applied already)
        LOG.debug("Using AlternativeImage %d (%s) for page '%s'",
                  len(alternative_image), alternative_image[-1].get_comments(),
                  page_id)
        page_image = workspace.resolve_image_as_pil(
            alternative_image[-1].get_filename())
    elif border:
        # get polygon outline of page border:
        page_polygon = np.array(polygon_from_points(page_points))
        # create a mask from the page polygon:
        page_image = image_from_polygon(page_image, page_polygon)
        # recrop into page rectangle:
        page_image = crop_image(page_image,
                                box=(page_xywh['x'], page_xywh['y'],
                                     page_xywh['x'] + page_xywh['w'],
                                     page_xywh['y'] + page_xywh['h']))
        if 'angle' in page_xywh and page_xywh['angle']:
            LOG.info("About to rotate page '%s' by %.2f°", page_id,
                     page_xywh['angle'])
            page_image = page_image.rotate(
                page_xywh['angle'],
                expand=True,
                #resample=Image.BILINEAR,
                fillcolor='white')
    # subtract offset from any increase in binary region size over source:
    page_xywh['x'] -= round(0.5 * max(0, page_image.width - page_xywh['w']))
    page_xywh['y'] -= round(0.5 * max(0, page_image.height - page_xywh['h']))
    return page_image, page_xywh, page_image_info