diff --git a/README.md b/README.md index 303efe3..2126724 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,11 @@ This offers a OCR-D compliant workspace processor for some of the functionality This processor only operates on the text line level and so needs a line segmentation (and by extension a binarized image) as its input. -In addition to the line text it also outputs glyph segmentation including -per-glyph confidence values and per-glyph alternative predictions as provided by -the Calamari OCR engine. Note that while Calamari does not provide word -segmentation, this processor produces word segmentation inferred from text +In addition to the line text it may also output word and glyph segmentation +including per-glyph confidence values and per-glyph alternative predictions as +provided by the Calamari OCR engine, using a `textequiv_level` of `word` or +`glyph`. Note that while Calamari does not provide word segmentation, this +processor produces word segmentation inferred from text segmentation and the glyph positions. The provided glyph and word segmentation can be used for text extraction and highlighting, but is probably not useful for further image-based processing. @@ -53,7 +54,8 @@ ocrd-calamari-recognize -p test-parameters.json -m mets.xml -I OCR-D-SEG-LINE -O With `test-parameters.json`: ~~~ { - "checkpoint": "/path/to/some/trained/models/*.ckpt.json" + "checkpoint": "/path/to/some/trained/models/*.ckpt.json", + "textequiv_level": "line", } ~~~ diff --git a/ocrd_calamari/ocrd-tool.json b/ocrd_calamari/ocrd-tool.json index 54d2206..1174243 100644 --- a/ocrd_calamari/ocrd-tool.json +++ b/ocrd_calamari/ocrd-tool.json @@ -25,6 +25,12 @@ "voter": { "description": "The voting algorithm to use", "type": "string", "default": "confidence_voter_default_ctc" + }, + "textequiv_level": { + "type": "string", + "enum": ["line", "word", "glyph"], + "default": "line", + "description": "Deepest PAGE XML hierarchy level to include TextEquiv results for" } } } diff --git a/ocrd_calamari/recognize.py b/ocrd_calamari/recognize.py index cd2d84e..7b2db0d 100644 --- a/ocrd_calamari/recognize.py +++ b/ocrd_calamari/recognize.py @@ -120,46 +120,48 @@ class CalamariRecognize(Processor): spaces = (c == ' ') yield word - word_no = 0 - i = 0 - - for word_text in _words(prediction.sentence): - word_length = len(word_text) - if not all(c == ' ' for c in word_text): - word_positions = prediction.positions[i:i+word_length] - word_start = word_positions[0].global_start - word_end = word_positions[-1].global_end - - polygon = polygon_from_x0y0x1y1([word_start, 0, word_end, line_image.height]) - points = points_from_polygon(coordinates_for_segment(polygon, None, line_coords)) - # XXX Crop to line polygon? + if self.parameter['textequiv_level'] in ['word', 'glyph']: + word_no = 0 + i = 0 + + for word_text in _words(prediction.sentence): + word_length = len(word_text) + if not all(c == ' ' for c in word_text): + word_positions = prediction.positions[i:i+word_length] + word_start = word_positions[0].global_start + word_end = word_positions[-1].global_end + + polygon = polygon_from_x0y0x1y1([word_start, 0, word_end, line_image.height]) + points = points_from_polygon(coordinates_for_segment(polygon, None, line_coords)) + # XXX Crop to line polygon? - word = WordType(id='%s_word%04d' % (line.id, word_no), Coords=CoordsType(points)) - word.add_TextEquiv(TextEquivType(Unicode=word_text)) + word = WordType(id='%s_word%04d' % (line.id, word_no), Coords=CoordsType(points)) + word.add_TextEquiv(TextEquivType(Unicode=word_text)) - for glyph_no, p in enumerate(word_positions): - glyph_start = p.global_start - glyph_end = p.global_end + if self.parameter['textequiv_level'] == 'glyph': + for glyph_no, p in enumerate(word_positions): + glyph_start = p.global_start + glyph_end = p.global_end - polygon = polygon_from_x0y0x1y1([glyph_start, 0, glyph_end, line_image.height]) - points = points_from_polygon(coordinates_for_segment(polygon, None, line_coords)) + polygon = polygon_from_x0y0x1y1([glyph_start, 0, glyph_end, line_image.height]) + points = points_from_polygon(coordinates_for_segment(polygon, None, line_coords)) - glyph = GlyphType(id='%s_glyph%04d' % (word.id, glyph_no), Coords=CoordsType(points)) + glyph = GlyphType(id='%s_glyph%04d' % (word.id, glyph_no), Coords=CoordsType(points)) - chars = sorted(p.chars, key=lambda k: k.probability, reverse=True) - char_index = 1 # Must start with 1, see https://ocr-d.github.io/page#multiple-textequivs - for char in chars: - if char.char: - glyph.add_TextEquiv(TextEquivType(Unicode=char.char, index=char_index, conf=char.probability)) - char_index += 1 - # XXX Note that omission probabilities are not normalized?! + chars = sorted(p.chars, key=lambda k: k.probability, reverse=True) + char_index = 1 # Must start with 1, see https://ocr-d.github.io/page#multiple-textequivs + for char in chars: + if char.char: + glyph.add_TextEquiv(TextEquivType(Unicode=char.char, index=char_index, conf=char.probability)) + char_index += 1 + # XXX Note that omission probabilities are not normalized?! - word.add_Glyph(glyph) + word.add_Glyph(glyph) - line.add_Word(word) - word_no += 1 + line.add_Word(word) + word_no += 1 - i += word_length + i += word_length _page_update_higher_textequiv_levels('line', pcgts) diff --git a/test/test_recognize.py b/test/test_recognize.py index e576ac5..5db48cf 100644 --- a/test/test_recognize.py +++ b/test/test_recognize.py @@ -87,6 +87,7 @@ def test_word_segmentation(workspace): output_file_grp="OCR-D-OCR-CALAMARI", parameter={ "checkpoint": CHECKPOINT, + "textequiv_level": "word", # Note that we're going down to word level here } ).process() workspace.save_mets() @@ -106,5 +107,30 @@ def test_word_segmentation(workspace): line_text = line.xpath("pc:TextEquiv/pc:Unicode", namespaces=NSMAP)[0].text assert words_text == line_text + # For extra measure, check that we're not seeing any glyphs, as we asked for textequiv_level == "word" + glyphs = tree.xpath("//pc:Glyph", namespaces=NSMAP) + assert len(glyphs) == 0 + + +def test_glyphs(workspace): + CalamariRecognize( + workspace, + input_file_grp="OCR-D-GT-SEG-LINE", + output_file_grp="OCR-D-OCR-CALAMARI", + parameter={ + "checkpoint": CHECKPOINT, + "textequiv_level": "glyph", # Note that we're going down to glyph level here + } + ).process() + workspace.save_mets() + + page1 = os.path.join(workspace.directory, "OCR-D-OCR-CALAMARI/OCR-D-OCR-CALAMARI_0001.xml") + assert os.path.exists(page1) + tree = etree.parse(page1) + + # The result should contain a lot of glyphs + glyphs = tree.xpath("//pc:Glyph", namespaces=NSMAP) + assert len(glyphs) >= 100 + # vim:tw=120: