From c6009d83f3801919e3e4944362dd472a1ede20fb Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 18 Oct 2023 17:17:26 +0200 Subject: [PATCH 01/53] make changes to content item metadata --- text_importer/importers/bnf/classes.py | 11 ++++++----- text_importer/importers/bnf_en/classes.py | 6 +++--- text_importer/importers/rero/classes.py | 12 +++--------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/text_importer/importers/bnf/classes.py b/text_importer/importers/bnf/classes.py index 31084723..0a6b5493 100644 --- a/text_importer/importers/bnf/classes.py +++ b/text_importer/importers/bnf/classes.py @@ -245,7 +245,7 @@ def _parse_div(self, div_id: str, div_type: str, label: str, item_counter: int) embedded.append(ci) return embedded, item_counter - def _get_image_iiif_link(self, ci_id: str, parts: List) -> str: + def _get_image_iiif_link(self, ci_id: str, parts: List) -> tuple[str]: """ Gets the image iiif link given the ID of the CI ( :param str ci_id: The ID of the image CI @@ -267,9 +267,8 @@ def _get_image_iiif_link(self, ci_id: str, parts: List) -> str: logger.warning(f"Could not find image {image_part_id} for CI {ci_id}") else: coords = distill_coordinates(block) - iiif_link = os.path.join(IIIF_ENDPOINT_URL, page.ark_link, ",".join(str(c) for c in coords), - IIIF_IMAGE_SUFFIX) - return iiif_link + iiif_link = os.path.join(IIIF_ENDPOINT_URL, page.ark_link, "info.json") + return coords, iiif_link def _parse_mets(self): """Parses the METS file for the current Issue""" @@ -289,7 +288,9 @@ def _parse_mets(self): for x in content_items: x['m']['pp'] = list(set(c['comp_page_no'] for c in x['l']['parts'])) if x['m']['tp'] == CONTENTITEM_TYPE_IMAGE: - x['m']['iiif_link'] = self._get_image_iiif_link(x['m']['id'], x['l']['parts']) + x['c'], x['m']['iiif_link'] = self._get_image_iiif_link( + x['m']['id'], x['l']['parts'] + ) self.pages = list(self.pages.values()) diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index dfc47a5c..36bc4216 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -183,7 +183,7 @@ def _parse_content_item(self, item_div, counter: int) -> dict: content_item['m']['pp'].append(pge_no) if div_type in [CONTENTITEM_TYPE_IMAGE, CONTENTITEM_TYPE_TABLE]: - content_item['m']['c'], content_item['iiif_link'] = self._get_image_info(content_item) + content_item['c'], content_item['m']['iiif_link'] = self._get_image_info(content_item) return content_item def _decompose_section(self, div): @@ -279,8 +279,8 @@ def _get_image_info(self, content_item): coords = [int(float(hpos)), int(float(vpos)), int(float(width)), int(float(height))] # coords = convert_coordinates(coords, self.image_properties[page.number], page.page_width) - iiif_link = os.path.join(IIIF_ENDPOINT_URL, self.ark_link, "f{}".format(page.number), - ",".join([str(x) for x in coords]), 'full', '0', 'default.jpg') + iiif_link = os.path.join(IIIF_ENDPOINT_URL, self.ark_link, + "f{}".format(page.number), "info.json") return coords, iiif_link diff --git a/text_importer/importers/rero/classes.py b/text_importer/importers/rero/classes.py index 9a035ff9..686b3c97 100644 --- a/text_importer/importers/rero/classes.py +++ b/text_importer/importers/rero/classes.py @@ -313,8 +313,8 @@ def _parse_content_item(self, item_div: Tag, counter:int) -> dict[str,Any]: content_item['m']['pp'].append(pge_no) if content_item['m']['tp'] == CONTENTITEM_TYPE_IMAGE: - (content_item['m']['c'], - content_item['iiif_link']) = self._get_image_info(content_item) + (content_item['c'], + content_item['m']['iiif_link']) = self._get_image_info(content_item) return content_item def _decompose_section(self, div: Tag) -> list[Tag]: @@ -471,12 +471,6 @@ def _get_image_info( self.image_properties[page.number], page.page_width) - iiif_link = os.path.join( - IIIF_ENDPOINT_URL, page.id, - ",".join([str(x) for x in coords]), - 'full', - '0', - 'default.jpg' - ) + iiif_link = os.path.join(IIIF_ENDPOINT_URL, page.id, "info.json") return coords, iiif_link From 4c28f4dda281a68d2bd6dafd22e460c4c5a8a61d Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 24 Oct 2023 17:58:51 +0200 Subject: [PATCH 02/53] Fix issue #103 and add corresponding test --- tests/importers/test_lux_importer.py | 44 +++++++++++++++++++++++++- text_importer/importers/lux/classes.py | 4 +-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/tests/importers/test_lux_importer.py b/tests/importers/test_lux_importer.py index 66b9d82c..579be81f 100644 --- a/tests/importers/test_lux_importer.py +++ b/tests/importers/test_lux_importer.py @@ -1,11 +1,16 @@ +import bz2 import json import logging +import os +from glob import glob +import logging from contextlib import ExitStack from text_importer.utils import get_pkg_resource +from text_importer.importers import CONTENTITEM_TYPE_IMAGE from text_importer.importers.core import import_issues -from text_importer.importers.lux.classes import LuxNewspaperIssue +from text_importer.importers.lux.classes import LuxNewspaperIssue, IIIF_ENDPOINT_URL from text_importer.importers.lux.detect import detect_issues as lux_detect_issues from text_importer.importers.lux.detect import select_issues as lux_select_issues @@ -80,6 +85,43 @@ def test_selective_import(): logger.info("Finished test_selective_import, closing file manager.") f_mng.close() +def check_link(link: str): + return IIIF_ENDPOINT_URL in link and "ark:" in link and "ark:/" not in link + +def check_iiif_links(issue_data): + items = issue_data['i'] + imgs = [i for i in items if i['m']['tp'] == CONTENTITEM_TYPE_IMAGE] + return (len(imgs) == 0 or + all(check_link(data['m']['iiif_link']) for data in imgs)) + + +def test_image_iiif_links(): + + logger.info("Starting test_image_iiif_links in test_lux_importer.py") + f_mng = ExitStack() + inp_dir = get_pkg_resource(f_mng, 'data/sample_data/Luxembourg/') + out_dir = get_pkg_resource(f_mng, 'data/out/') + + issues = lux_detect_issues(base_dir=inp_dir,) + + assert issues is not None + assert len(issues) > 0 + + journals = set([x.journal for x in issues]) + blobs = [f"{j}*.jsonl.bz2" for j in journals] + issue_files = [f for b in blobs for f in glob(os.path.join(out_dir, b))] + logger.info(issue_files) + + for filename in issue_files: + with bz2.open(filename, "rt") as bzinput: + for line in bzinput: + issue = json.loads(line) + assert check_iiif_links(issue), ( + "Issue as wrong iiif_links." + ) + + logger.info("Finished test_image_iiif_links, closing file manager.") + f_mng.close() # # TODO: adapt it to Lux data # def test_verify_imported_issues(): diff --git a/text_importer/importers/lux/classes.py b/text_importer/importers/lux/classes.py index 3de4fd5e..561eb96b 100644 --- a/text_importer/importers/lux/classes.py +++ b/text_importer/importers/lux/classes.py @@ -38,7 +38,7 @@ Pageschema = get_page_schema() logger = logging.getLogger(__name__) -IIIF_ENDPOINT_URL = "https://iiif.eluxemburgensia.lu/iiif/2" +IIIF_ENDPOINT_URL = "https://iiif.eluxemburgensia.lu/image/iiif/2" class LuxNewspaperPage(MetsAltoNewspaperPage): @@ -589,7 +589,7 @@ def _parse_mets(self) -> None: content_items += section_cis # Set ark_id ark_link = mets_doc.find('mets').get('OBJID') - self.ark_id = ark_link.replace('https://persist.lu/', '') + self.ark_id = ark_link.replace('https://persist.lu/ark:/', 'ark:') for ci in content_items: From 45ee746eef189dc2d80d437099fefbdc0a924b32 Mon Sep 17 00:00:00 2001 From: Pauline Isabela Conti Date: Mon, 13 Nov 2023 17:41:26 +0100 Subject: [PATCH 03/53] Update tests based on changes --- tests/importers/test_bnf_en_importer.py | 52 ++++++++++++++++++++++--- tests/importers/test_bnf_importer.py | 48 +++++++++++++++++++++++ tests/importers/test_rero_importer.py | 17 +++++++- 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/tests/importers/test_bnf_en_importer.py b/tests/importers/test_bnf_en_importer.py index b54071b1..240c14c3 100644 --- a/tests/importers/test_bnf_en_importer.py +++ b/tests/importers/test_bnf_en_importer.py @@ -1,8 +1,13 @@ import logging +import bz2 +import os +import json +from glob import glob from contextlib import ExitStack from text_importer.utils import get_pkg_resource +from text_importer.importers import CONTENTITEM_TYPE_IMAGE from text_importer.importers.bnf_en.classes import BnfEnNewspaperIssue from text_importer.importers.bnf_en.detect import detect_issues from text_importer.importers.core import import_issues @@ -16,15 +21,11 @@ def test_import_issues(): logger.info("Starting test_import_issues in test_bnf_en_importer.py.") f_mng = ExitStack() - inp_dir = get_pkg_resource(f_mng,'data/sample_data/BNF/') - ar_file = get_pkg_resource(f_mng,'data/sample_data/BNF/access_rights.json') + inp_dir = get_pkg_resource(f_mng,'data/sample_data/BNF-EN/') out_dir = get_pkg_resource(f_mng,'data/out/') tmp_dir = get_pkg_resource(f_mng,'data/tmp/') - issues = detect_issues( - base_dir=inp_dir, - access_rights=ar_file - ) + issues = detect_issues(base_dir=inp_dir, access_rights="") assert issues is not None assert len(issues) > 0 @@ -40,3 +41,42 @@ def test_import_issues(): logger.info("Finished test_import_issues, closing file manager.") f_mng.close() + + +def check_image_coordinates_and_iiif(issue_data): + items = issue_data['i'] + imgs = [i for i in items if i['m']['tp'] == CONTENTITEM_TYPE_IMAGE] + if len(imgs) == 0: + return True + else: + return (all('c' in data and len(data['c']) == 4 for data in imgs) and + all('iiif_link' in data['m'] and "info.json" in data['m']['iiif_link'] for data in imgs)) + + +def test_image_coordinates(): + + logger.info("Starting test_image_coordinates in test_bnf_en_importer.py") + f_mng = ExitStack() + inp_dir = get_pkg_resource(f_mng, 'data/sample_data/BNF-EN/') + out_dir = get_pkg_resource(f_mng, 'data/out/') + + issues = detect_issues(base_dir=inp_dir, access_rights='') + + assert issues is not None + assert len(issues) > 0 + + journals = set([x.journal for x in issues]) + blobs = [f"{j}*.jsonl.bz2" for j in journals] + issue_files = [f for b in blobs for f in glob(os.path.join(out_dir, b))] + logger.info(issue_files) + + for filename in issue_files: + with bz2.open(filename, "rt") as bzinput: + for line in bzinput: + issue = json.loads(line) + assert check_image_coordinates_and_iiif(issue), ( + "Images do not have coordinates" + ) + + logger.info("Finished test_image_coordinate, closing file manager.") + f_mng.close() diff --git a/tests/importers/test_bnf_importer.py b/tests/importers/test_bnf_importer.py index 967bdf8e..0b9a1913 100644 --- a/tests/importers/test_bnf_importer.py +++ b/tests/importers/test_bnf_importer.py @@ -1,8 +1,13 @@ import logging +import bz2 +import json +import os +from glob import glob from contextlib import ExitStack from text_importer.utils import get_pkg_resource +from text_importer.importers import CONTENTITEM_TYPE_IMAGE from text_importer.importers.bnf.classes import BnfNewspaperIssue from text_importer.importers.bnf.detect import detect_issues from text_importer.importers.core import import_issues @@ -38,3 +43,46 @@ def test_import_issues(): logger.info("Finished test_import_issues, closing file manager.") f_mng.close() + +def check_image_coordinates_and_iiif(issue_data): + items = issue_data['i'] + imgs = [i for i in items if i['m']['tp'] == CONTENTITEM_TYPE_IMAGE] + if len(imgs) == 0: + return True + else: + return (all('c' in data and len(data['c']) == 4 for data in imgs) and + all('iiif_link' in data['m'] and "info.json" in data['m']['iiif_link'] for data in imgs)) + + +def test_image_coordinates(): + + logger.info("Starting test_image_coordinates in test_bnf_importer.py") + f_mng = ExitStack() + inp_dir = get_pkg_resource(f_mng, 'data/sample_data/BNF/') + ar_file = get_pkg_resource(f_mng, + 'data/sample_data/BNF/access_rights.json') + out_dir = get_pkg_resource(f_mng, 'data/out/') + + issues = detect_issues( + base_dir=inp_dir, + access_rights=ar_file + ) + + assert issues is not None + assert len(issues) > 0 + + journals = set([x.journal for x in issues]) + blobs = [f"{j}*.jsonl.bz2" for j in journals] + issue_files = [f for b in blobs for f in glob(os.path.join(out_dir, b))] + logger.info(issue_files) + + for filename in issue_files: + with bz2.open(filename, "rt") as bzinput: + for line in bzinput: + issue = json.loads(line) + assert check_image_coordinates_and_iiif(issue), ( + "Images do not have coordinates" + ) + + logger.info("Finished test_image_coordinate, closing file manager.") + f_mng.close() diff --git a/tests/importers/test_rero_importer.py b/tests/importers/test_rero_importer.py index f6e349d9..e7cf079c 100644 --- a/tests/importers/test_rero_importer.py +++ b/tests/importers/test_rero_importer.py @@ -52,6 +52,21 @@ def check_image_coordinates(issue_data): all('c' in data['m'] and len(data['m']['c']) == 4 for data in imgs)) +def check_image_coordinates_and_iiif(issue_data): + """This function is derived from `check_image_coordinates`. + + It checks the changes made in the following commit https://github.com/impresso/impresso-text-acquisition/commit/c6009d83f3801919e3e4944362dd472a1ede20fb, + based on the issues https://github.com/impresso/impresso-text-acquisition/issues/104 and https://github.com/impresso/impresso-text-acquisition/issues/105. + """ + items = issue_data['i'] + imgs = [i for i in items if i['m']['tp'] == CONTENTITEM_TYPE_IMAGE] + if len(imgs) == 0: + return True + else: + return (all('c' in data and len(data['c']) == 4 for data in imgs) and + all('iiif_link' in data['m'] and "info.json" in data['m']['iiif_link'] for data in imgs)) + + def test_image_coordinates(): logger.info("Starting test_image_coordinates in test_rero_importer.py") @@ -78,7 +93,7 @@ def test_image_coordinates(): with bz2.open(filename, "rt") as bzinput: for line in bzinput: issue = json.loads(line) - assert check_image_coordinates(issue), ( + assert check_image_coordinates_and_iiif(issue), ( "Images do not have coordinates" ) From 2ec7f4dfda6677310f6b32809b0bdf9d4f57a8f6 Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 14 Nov 2023 11:21:07 +0100 Subject: [PATCH 04/53] minor fixes --- text_importer/importers/bnf/classes.py | 2 +- text_importer/importers/bnf_en/classes.py | 9 +++++---- text_importer/importers/rero/classes.py | 4 +++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/text_importer/importers/bnf/classes.py b/text_importer/importers/bnf/classes.py index 0a6b5493..5e243191 100644 --- a/text_importer/importers/bnf/classes.py +++ b/text_importer/importers/bnf/classes.py @@ -267,7 +267,7 @@ def _get_image_iiif_link(self, ci_id: str, parts: List) -> tuple[str]: logger.warning(f"Could not find image {image_part_id} for CI {ci_id}") else: coords = distill_coordinates(block) - iiif_link = os.path.join(IIIF_ENDPOINT_URL, page.ark_link, "info.json") + iiif_link = os.path.join(IIIF_ENDPOINT_URL, page.ark_link, IIIF_MANIFEST_SUFFIX) return coords, iiif_link def _parse_mets(self): diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index 36bc4216..ba0fbfe0 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -20,7 +20,8 @@ logger = logging.getLogger(__name__) IIIF_ENDPOINT_URL = "https://gallica.bnf.fr/iiif/ark:/12148/" -IIIF_SUFFIX = "full/full/0/default.jpg" +IIIF_MANIFEST_SUFFIX = "full/full/0/manifest.json" +IIIF_IMAGE_SUFFIX = "full/full/0/default.jpg" SECTION_TYPE = "section" type_translation = { @@ -30,7 +31,7 @@ class BnfEnNewspaperPage(MetsAltoNewspaperPage): - """Class representing a page in RERO (Mets/Alto) data.""" + """Class representing a page in BNF-EN (Mets/Alto) data.""" def __init__(self, _id: str, n: int, filename: str, basedir: str): super().__init__(_id, n, filename, basedir) @@ -41,7 +42,7 @@ def __init__(self, _id: str, n: int, filename: str, basedir: str): def add_issue(self, issue): self.issue = issue ark = issue.ark_link - self.page_data['iiif'] = os.path.join(IIIF_ENDPOINT_URL, ark, "f{}".format(self.number), IIIF_SUFFIX) + self.page_data['iiif'] = os.path.join(IIIF_ENDPOINT_URL, ark, "f{}".format(self.number), IIIF_IMAGE_SUFFIX) class BnfEnNewspaperIssue(MetsAltoNewspaperIssue): @@ -280,7 +281,7 @@ def _get_image_info(self, content_item): # coords = convert_coordinates(coords, self.image_properties[page.number], page.page_width) iiif_link = os.path.join(IIIF_ENDPOINT_URL, self.ark_link, - "f{}".format(page.number), "info.json") + "f{}".format(page.number), IIIF_MANIFEST_SUFFIX) return coords, iiif_link diff --git a/text_importer/importers/rero/classes.py b/text_importer/importers/rero/classes.py index 686b3c97..17129955 100644 --- a/text_importer/importers/rero/classes.py +++ b/text_importer/importers/rero/classes.py @@ -25,6 +25,8 @@ logger = logging.getLogger(__name__) IIIF_ENDPOINT_URL = "https://impresso-project.ch/api/proxy/iiif/" +IIIF_MANIFEST_SUFFIX = 'info.json' +IIIF_IMAGE_SUFFIX = 'full/full/0/default.jpg' # Types used in RERO2/RERO3 that are not in impresso schema SECTION_TYPE = "section" @@ -471,6 +473,6 @@ def _get_image_info( self.image_properties[page.number], page.page_width) - iiif_link = os.path.join(IIIF_ENDPOINT_URL, page.id, "info.json") + iiif_link = os.path.join(IIIF_ENDPOINT_URL, page.id, IIIF_MANIFEST_SUFFIX) return coords, iiif_link From 065dcb0fe964578e939991fa13fe8c716b698e61 Mon Sep 17 00:00:00 2001 From: piconti Date: Fri, 17 Nov 2023 10:46:53 +0100 Subject: [PATCH 05/53] Add manifest URIs to issue and rename pages' iiif key --- text_importer/importers/bl/classes.py | 7 ++++--- text_importer/importers/bnf/classes.py | 24 +++++++++++++++-------- text_importer/importers/bnf_en/classes.py | 20 ++++++++++++------- text_importer/importers/fedgaz/classes.py | 4 ++-- text_importer/importers/lux/classes.py | 11 ++++++----- text_importer/importers/olive/classes.py | 5 +++-- text_importer/importers/rero/classes.py | 23 +++++++++++----------- text_importer/importers/swa/classes.py | 15 ++++++++++---- 8 files changed, 67 insertions(+), 42 deletions(-) diff --git a/text_importer/importers/bl/classes.py b/text_importer/importers/bl/classes.py index f640c32c..56be329f 100644 --- a/text_importer/importers/bl/classes.py +++ b/text_importer/importers/bl/classes.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) -IIIF_ENDPOINT_URL = "https://impresso-project.ch/api/proxy/iiif/" +IIIF_ENDPOINT_URI = "https://impresso-project.ch/api/proxy/iiif/" BL_PICTURE_TYPE = "picture" BL_AD_TYPE = "advert" @@ -27,7 +27,8 @@ def add_issue(self, issue: MetsAltoNewspaperIssue): :param BlNewspaperIssue issue: """ self.issue = issue - self.page_data['iiif'] = os.path.join(IIIF_ENDPOINT_URL, self.id) + self.page_data['iiif_img_base_uri'] = os.path.join(IIIF_ENDPOINT_URI, + self.id) class BlNewspaperIssue(MetsAltoNewspaperIssue): @@ -156,7 +157,7 @@ def _parse_content_items(self) -> List[dict]: # Get logical structure of issue divs = self.xml.find('structMap', {'TYPE': 'LOGICAL'}).find('div', {'TYPE': 'ISSUE'}).findChildren('div') - # Sort to have same naming + # Sort to have same naming # TODO change sorting!! sorted_divs = sorted(divs, key=lambda x: x.get('DMDID').lower()) # Get all CI types diff --git a/text_importer/importers/bnf/classes.py b/text_importer/importers/bnf/classes.py index 5e243191..56ac102d 100644 --- a/text_importer/importers/bnf/classes.py +++ b/text_importer/importers/bnf/classes.py @@ -21,9 +21,10 @@ logger = logging.getLogger(__name__) -IIIF_ENDPOINT_URL = "https://gallica.bnf.fr/iiif" -IIIF_MANIFEST_SUFFIX = "full/full/0/manifest.json" -IIIF_IMAGE_SUFFIX = "full/0/default.jpg" +IIIF_ENDPOINT_URI = "https://gallica.bnf.fr/iiif" +IIIF_MANIFEST_SUFFIX = 'manifest.json' +IIIF_SUFFIX = "info.json" +IIIF_IMAGE_SUFFIX = "full/0/default.jpg" # TODO remove class BnfNewspaperPage(MetsAltoNewspaperPage): @@ -51,8 +52,8 @@ def add_issue(self, issue: MetsAltoNewspaperIssue): :param MetsAltoNewspaperIssue issue: The parent Issue """ self.issue = issue - iiif_url = os.path.join(IIIF_ENDPOINT_URL, self.ark_link, IIIF_MANIFEST_SUFFIX) - self.page_data['iiif'] = iiif_url + self.page_data['iiif_img_base_uri'] = os.path.join(IIIF_ENDPOINT_URI, + self.ark_link) self._parse_font_styles() def parse(self): @@ -267,7 +268,7 @@ def _get_image_iiif_link(self, ci_id: str, parts: List) -> tuple[str]: logger.warning(f"Could not find image {image_part_id} for CI {ci_id}") else: coords = distill_coordinates(block) - iiif_link = os.path.join(IIIF_ENDPOINT_URL, page.ark_link, IIIF_MANIFEST_SUFFIX) + iiif_link = os.path.join(IIIF_ENDPOINT_URI, page.ark_link, IIIF_SUFFIX) return coords, iiif_link def _parse_mets(self): @@ -294,13 +295,20 @@ def _parse_mets(self): self.pages = list(self.pages.values()) + # Issue manifest iiif URI is in format {iiif_prefix}/{ark_id}/manifest.json + # By default, the ark id contains the page number, + iiif_manifest = os.path.join(IIIF_ENDPOINT_URI, + os.path.dirname(self.pages[0].ark_link), + IIIF_MANIFEST_SUFFIX) + self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), "i": content_items, "id": self.id, "ar": self.rights, - "pp": [p.id for p in self.pages] - } + "pp": [p.id for p in self.pages], + "iiif_manifest_uri": iiif_manifest + } # Note for newspapers with two dates (197 cases) if self.secondary_date is not None: self.issue_data['n'] = ["Secondary date {}".format(self.secondary_date)] diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index ba0fbfe0..f1e4e86f 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -19,9 +19,10 @@ logger = logging.getLogger(__name__) -IIIF_ENDPOINT_URL = "https://gallica.bnf.fr/iiif/ark:/12148/" -IIIF_MANIFEST_SUFFIX = "full/full/0/manifest.json" -IIIF_IMAGE_SUFFIX = "full/full/0/default.jpg" +IIIF_ENDPOINT_URI = "https://gallica.bnf.fr/iiif/ark:/12148/" +IIIF_SUFFIX = "info.json" +IIIF_MANIFEST_SUFFIX = "manifest.json" +IIIF_IMAGE_SUFFIX = "full/full/0/default.jpg" # TODO remove SECTION_TYPE = "section" type_translation = { @@ -42,7 +43,9 @@ def __init__(self, _id: str, n: int, filename: str, basedir: str): def add_issue(self, issue): self.issue = issue ark = issue.ark_link - self.page_data['iiif'] = os.path.join(IIIF_ENDPOINT_URL, ark, "f{}".format(self.number), IIIF_IMAGE_SUFFIX) + self.page_data['iiif_img_base_uri'] = os.path.join( + IIIF_ENDPOINT_URI, ark, "f{}".format(self.number) + ) class BnfEnNewspaperIssue(MetsAltoNewspaperIssue): @@ -280,18 +283,21 @@ def _get_image_info(self, content_item): coords = [int(float(hpos)), int(float(vpos)), int(float(width)), int(float(height))] # coords = convert_coordinates(coords, self.image_properties[page.number], page.page_width) - iiif_link = os.path.join(IIIF_ENDPOINT_URL, self.ark_link, - "f{}".format(page.number), IIIF_MANIFEST_SUFFIX) + iiif_link = os.path.join(IIIF_ENDPOINT_URI, self.ark_link, + "f{}".format(page.number), IIIF_SUFFIX) return coords, iiif_link def _parse_mets(self): content_items = self._parse_content_items() + + iiif_manifest = os.path.join(IIIF_ENDPOINT_URI, self.ark_link, IIIF_MANIFEST_SUFFIX) self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), "i": content_items, "id": self.id, "ar": self.rights, - "pp": [p.id for p in self.pages] + "pp": [p.id for p in self.pages], + "iiif_manifest_uri": iiif_manifest } diff --git a/text_importer/importers/fedgaz/classes.py b/text_importer/importers/fedgaz/classes.py index 5b0c0e15..48a35866 100644 --- a/text_importer/importers/fedgaz/classes.py +++ b/text_importer/importers/fedgaz/classes.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) -IMPRESSO_IIIF_BASEURI = "https://impresso-project.ch/api/proxy/iiif/" +IIIF_ENDPOINT_URI = "https://impresso-project.ch/api/proxy/iiif/" class TokPosition(namedtuple("TokPosition", "art page reg para line tok")): @@ -48,7 +48,7 @@ def parse(self): "id": self.id, "cdt": strftime("%Y-%m-%d %H:%M:%S"), "cc": True, - "iiif": os.path.join(IMPRESSO_IIIF_BASEURI, self.id), + "iiif_img_base_uri": os.path.join(IIIF_ENDPOINT_URI, self.id), "r": self.page_content["r"], } diff --git a/text_importer/importers/lux/classes.py b/text_importer/importers/lux/classes.py index 561eb96b..1b263e3f 100644 --- a/text_importer/importers/lux/classes.py +++ b/text_importer/importers/lux/classes.py @@ -38,7 +38,8 @@ Pageschema = get_page_schema() logger = logging.getLogger(__name__) -IIIF_ENDPOINT_URL = "https://iiif.eluxemburgensia.lu/image/iiif/2" +IIIF_ENDPOINT_URI = "https://iiif.eluxemburgensia.lu/image/iiif/2" +IIIF_SUFFIX = "info.json" class LuxNewspaperPage(MetsAltoNewspaperPage): @@ -75,9 +76,9 @@ def _parse_font_styles(self) -> None: def add_issue(self, issue: MetsAltoNewspaperIssue) -> None: self.issue = issue encoded_ark_id = encode_ark(self.issue.ark_id) - iiif_base_link = f'{IIIF_ENDPOINT_URL}/{encoded_ark_id}' - iiif_link = f'{iiif_base_link}%2fpages%2f{self.number}/info.json' - self.page_data['iiif'] = iiif_link + iiif_base_link = f'{IIIF_ENDPOINT_URI}/{encoded_ark_id}' + iiif_link = f'{iiif_base_link}%2fpages%2f{self.number}' + self.page_data['iiif_img_base_uri'] = iiif_link self._parse_font_styles() def _convert_coordinates( @@ -454,7 +455,7 @@ def _process_image_ci(self, ci: dict[str, Any]) -> None: coordinates = convert_coordinates(hpos, vpos, height, width, x_resolution, y_resolution) encoded_ark_id = encode_ark(self.ark_id) - iiif_base_link = f'{IIIF_ENDPOINT_URL}/{encoded_ark_id}' + iiif_base_link = f'{IIIF_ENDPOINT_URI}/{encoded_ark_id}' ci['m']['iiif_link'] = ( f'{iiif_base_link}%2fpages%2f{curr_page.number}/info.json' ) diff --git a/text_importer/importers/olive/classes.py b/text_importer/importers/olive/classes.py index 9f1d6b94..c39dda00 100644 --- a/text_importer/importers/olive/classes.py +++ b/text_importer/importers/olive/classes.py @@ -31,7 +31,7 @@ logger = logging.getLogger(__name__) IssueSchema = get_issue_schema() Pageschema = get_page_schema() -IMPRESSO_IIIF_BASEURI = "https://impresso-project.ch/api/proxy/iiif/" +IIIF_ENDPOINT_URI = "https://impresso-project.ch/api/proxy/iiif/" class OliveNewspaperPage(NewspaperPage): @@ -93,7 +93,8 @@ def parse(self) -> None: ) self.page_data['id'] = self.id - self.page_data['iiif'] = os.path.join(IMPRESSO_IIIF_BASEURI, self.id) + self.page_data['iiif_img_base_uri'] = os.path.join(IIIF_ENDPOINT_URI, + self.id) if len(self.page_data['r']) == 0: logger.warning(f"Page {self.id} has not OCR text") diff --git a/text_importer/importers/rero/classes.py b/text_importer/importers/rero/classes.py index 17129955..3d8b266d 100644 --- a/text_importer/importers/rero/classes.py +++ b/text_importer/importers/rero/classes.py @@ -24,8 +24,8 @@ logger = logging.getLogger(__name__) -IIIF_ENDPOINT_URL = "https://impresso-project.ch/api/proxy/iiif/" -IIIF_MANIFEST_SUFFIX = 'info.json' +IIIF_ENDPOINT_URI = "https://impresso-project.ch/api/proxy/iiif/" +IIIF_SUFFIX = 'info.json' IIIF_IMAGE_SUFFIX = 'full/full/0/default.jpg' # Types used in RERO2/RERO3 that are not in impresso schema @@ -90,7 +90,8 @@ def __init__(self, _id: str, number: int, def add_issue(self, issue: MetsAltoNewspaperIssue) -> None: self.issue = issue - self.page_data['iiif'] = os.path.join(IIIF_ENDPOINT_URL, self.id) + self.page_data['iiif_img_base_uri'] = os.path.join(IIIF_ENDPOINT_URI, + self.id) # no coordinate conversion needed, but keeping it here for now def _convert_coordinates( @@ -399,12 +400,12 @@ def _parse_mets(self) -> None: mets_doc = self.xml self.image_properties = parse_mets_amdsec( - mets_doc, - x_res='ImageWidth', - y_res='ImageLength', - x_res_default=0, - y_res_default=0, - ) # Parse the resolution of page images + mets_doc, + x_res='ImageWidth', + y_res='ImageLength', + x_res_default=0, + y_res_default=0, + ) # Parse the resolution of page images # Parse all the content items content_items = self._parse_content_items(mets_doc) @@ -415,7 +416,7 @@ def _parse_mets(self) -> None: "id": self.id, "ar": self.rights, "pp": [p.id for p in self.pages] - } + } def _get_image_info( self, content_item: dict[str, Any] @@ -473,6 +474,6 @@ def _get_image_info( self.image_properties[page.number], page.page_width) - iiif_link = os.path.join(IIIF_ENDPOINT_URL, page.id, IIIF_MANIFEST_SUFFIX) + iiif_link = os.path.join(IIIF_ENDPOINT_URI, page.id, IIIF_SUFFIX) return coords, iiif_link diff --git a/text_importer/importers/swa/classes.py b/text_importer/importers/swa/classes.py index aa8301c3..58e2cc5c 100644 --- a/text_importer/importers/swa/classes.py +++ b/text_importer/importers/swa/classes.py @@ -16,7 +16,9 @@ from text_importer.importers.swa.detect import SwaIssueDir logger = logging.getLogger(__name__) -IIIF_ENDPOINT_URL = "https://ub-sipi.ub.unibas.ch/impresso" +IIIF_IMG_BASE_URI = "https://ub-sipi.ub.unibas.ch/impresso" +IIIF_PRES_BASE_URI = "https://ub-iiifpresentation.ub.unibas.ch/impresso_sb" +IIIF_MANIFEST_SUFFIX = "manifest" SWA_XML_ENCODING = "utf-8-sig" @@ -44,8 +46,8 @@ def __init__(self, _id: str, number: int, alto_path: str) -> None: basedir, filename = os.path.split(alto_path) super().__init__(_id, number, filename, basedir, encoding=SWA_XML_ENCODING) - self.iiif = os.path.join(IIIF_ENDPOINT_URL, filename.split('.')[0]) - self.page_data['iiif'] = self.iiif + self.iiif = os.path.join(IIIF_IMG_BASE_URI, filename.split('.')[0]) + self.page_data['iiif_img_base_uri'] = self.iiif def add_issue(self, issue: NewspaperIssue) -> None: self.issue = issue @@ -138,6 +140,10 @@ def __init__(self, issue_dir: SwaIssueDir, temp_dir: str) -> None: self._notes = [] self._find_pages() self._find_content_items() + + iiif_manifest = os.path.join(IIIF_PRES_BASE_URI, + f"{self.id}-issue", + IIIF_MANIFEST_SUFFIX) self.issue_data = { 'id': self.id, @@ -145,8 +151,9 @@ def __init__(self, issue_dir: SwaIssueDir, temp_dir: str) -> None: 'i': self.content_items, 'ar': self.rights, 'pp': [p.id for p in self.pages], + 'iiif_manifest_uri': iiif_manifest, 'notes': self._notes - } + } def _parse_archive(self, temp_dir: str) -> ZipArchive: """Open and parse the Zip archive located at :attr:`path` if possible. From 8546837ac4229d92c812afede391b266dd9e1f3c Mon Sep 17 00:00:00 2001 From: piconti Date: Thu, 30 Nov 2023 15:48:29 +0100 Subject: [PATCH 06/53] minor changes --- text_importer/importers/lux/classes.py | 15 ++++++++------- text_importer/importers/mets_alto/classes.py | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/text_importer/importers/lux/classes.py b/text_importer/importers/lux/classes.py index 1b263e3f..2a505b03 100644 --- a/text_importer/importers/lux/classes.py +++ b/text_importer/importers/lux/classes.py @@ -226,13 +226,13 @@ def _parse_mets_div(self, div: Tag) -> list[dict[str, str | int]]: comp_page_no = int(comp_fileid.replace('ALTO', '')) parts.append( - { - 'comp_role': comp_role, - 'comp_id': comp_id, - 'comp_fileid': comp_fileid, - 'comp_page_no': comp_page_no - } - ) + { + 'comp_role': comp_role, + 'comp_id': comp_id, + 'comp_fileid': comp_fileid, + 'comp_page_no': comp_page_no + } + ) return parts def _parse_dmdsec(self) -> tuple[list[dict[str, Any]], int]: @@ -452,6 +452,7 @@ def _process_image_ci(self, ci: dict[str, Any]) -> None: img_props = self.image_properties[curr_page.number] x_resolution = img_props['x_resolution'] y_resolution = img_props['y_resolution'] + # height and width should be switched! hpos, vpos, width, height coordinates = convert_coordinates(hpos, vpos, height, width, x_resolution, y_resolution) encoded_ark_id = encode_ark(self.ark_id) diff --git a/text_importer/importers/mets_alto/classes.py b/text_importer/importers/mets_alto/classes.py index 4ac8463b..2c7badec 100644 --- a/text_importer/importers/mets_alto/classes.py +++ b/text_importer/importers/mets_alto/classes.py @@ -108,8 +108,8 @@ def parse(self) -> None: pselement = doc.find('PrintSpace') page_regions, notes = alto.parse_printspace(pselement, mappings) self.page_data['cc'], self.page_data["r"] = self._convert_coordinates( - page_regions - ) + page_regions + ) # Add notes for missing coordinates in SWA if len(notes) > 0: self.page_data['n'] = notes From 3f7fc38b862fb478091f0d35655e866ed208220e Mon Sep 17 00:00:00 2001 From: piconti Date: Thu, 4 Jan 2024 15:37:31 +0100 Subject: [PATCH 07/53] Fix coord conversion arg order for lux image CIs --- text_importer/importers/lux/classes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text_importer/importers/lux/classes.py b/text_importer/importers/lux/classes.py index 5418af40..79c4f83c 100644 --- a/text_importer/importers/lux/classes.py +++ b/text_importer/importers/lux/classes.py @@ -462,8 +462,8 @@ def _process_image_ci( img_props = self.image_properties[curr_page.number] x_resolution = img_props['x_resolution'] y_resolution = img_props['y_resolution'] - # height and width should be switched! hpos, vpos, width, height - coordinates = convert_coordinates(hpos, vpos, height, width, + # order should be: hpos, vpos, width, height + coordinates = convert_coordinates(hpos, vpos, width, height, x_resolution, y_resolution) encoded_ark_id = encode_ark(self.ark_id) iiif_base_link = f'{IIIF_ENDPOINT_URI}/{encoded_ark_id}' From 5f916b78340427958b738bd05e1bdcbb03b588f3 Mon Sep 17 00:00:00 2001 From: piconti Date: Fri, 5 Jan 2024 18:23:52 +0100 Subject: [PATCH 08/53] Add some small comments --- text_importer/importers/bnf/classes.py | 4 ++-- text_importer/importers/bnf_en/classes.py | 3 ++- text_importer/importers/rero/classes.py | 13 +++++++------ text_importer/importers/swa/classes.py | 6 +++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/text_importer/importers/bnf/classes.py b/text_importer/importers/bnf/classes.py index 9711ab23..32c68e1c 100644 --- a/text_importer/importers/bnf/classes.py +++ b/text_importer/importers/bnf/classes.py @@ -320,15 +320,15 @@ def _parse_div(self, div_id: str, div_type: str, 'id': article_id, 'tp': type_translation[div_type], 'pp': [], - } + } if label is not None: metadata['t'] = label ci = { 'm': metadata, 'l': { 'parts': parts - } } + } item_counter += 1 else: # Otherwise, only parse embedded CIs article_id = None diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index d36e83ae..f77c7d38 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -299,7 +299,8 @@ def _parse_content_items(self) -> list[dict[str, Any]]: dmd_sections = doc.findAll("dmdSec") struct_map = doc.find("div", {"TYPE": "CONTENT"}) - # Sort to have same naming + # Sort to have same namings + # TODO fix this ordering! sorted_divs = sorted(dmd_sections, key=lambda x: x.get('ID').lower()) counter = 1 diff --git a/text_importer/importers/rero/classes.py b/text_importer/importers/rero/classes.py index 798f5ccb..73920838 100644 --- a/text_importer/importers/rero/classes.py +++ b/text_importer/importers/rero/classes.py @@ -327,7 +327,7 @@ def _parse_content_item(self, item_div: Tag, counter: int, mets_doc: BeautifulSo 'tp': div_type, 'pp': [], 't': item_div.get('LABEL') - } + } # Get CI language language = self._get_ci_language(item_div.get('DMDID'), mets_doc) @@ -339,8 +339,8 @@ def _parse_content_item(self, item_div: Tag, counter: int, mets_doc: BeautifulSo "l": { "id": item_div.get('ID'), "parts": self._parse_content_parts(item_div) - } } + } for p in content_item['l']['parts']: pge_no = p["comp_page_no"] if pge_no not in content_item['m']['pp']: @@ -396,11 +396,12 @@ def _parse_content_items( """ content_items = [] divs = mets_doc.find('div', {'TYPE': 'CONTENT'}).findChildren( - 'div', - recursive=False - ) # Children of "Content" tag + 'div', + recursive=False + ) # Children of "Content" tag - # Sort to have same naming + # Sort to have same naming + # TODO MAYBE fix sorting to be based on int ID and not str. sorted_divs = sorted(divs, key=lambda x: x.get('ID').lower()) counter = 1 diff --git a/text_importer/importers/swa/classes.py b/text_importer/importers/swa/classes.py index 58e2cc5c..89c3faef 100644 --- a/text_importer/importers/swa/classes.py +++ b/text_importer/importers/swa/classes.py @@ -172,8 +172,8 @@ def _parse_archive(self, temp_dir: str) -> ZipArchive: try: archive = ZipFile(self.path) logger.debug( - f"Contents of archive for {self.id}: {archive.namelist()}" - ) + f"Contents of archive for {self.id}: {archive.namelist()}" + ) return ZipArchive(archive, temp_dir) except Exception as e: msg = f"Bad Zipfile for {self.id}, failed with error : {e}" @@ -218,6 +218,6 @@ def _find_content_items(self) -> None: 'id': ci_id, 'pp': [page_number], 'tp': 'page', - } } + } self.content_items.append(ci) From 46e7649d8816c1cc36598516f25b8391668b7401 Mon Sep 17 00:00:00 2001 From: piconti Date: Mon, 19 Feb 2024 17:43:48 +0100 Subject: [PATCH 09/53] update schemas submodule and add code for SWA patch 6 --- notebooks/canonical_patches_1_and_6.ipynb | 284 ++++++++++++++++++++++ text_importer/importers/tetml/classes.py | 1 + text_importer/impresso-schemas | 2 +- 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 notebooks/canonical_patches_1_and_6.ipynb diff --git a/notebooks/canonical_patches_1_and_6.ipynb b/notebooks/canonical_patches_1_and_6.ipynb new file mode 100644 index 00000000..d7042eae --- /dev/null +++ b/notebooks/canonical_patches_1_and_6.ipynb @@ -0,0 +1,284 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dev notebook for patching code\n", + "\n", + "Related to issue [#117](https://github.com/impresso/impresso-text-acquisition/issues/117)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import boto3\n", + "import json\n", + "import logging\n", + "import jsonlines\n", + "from impresso_commons.utils import s3\n", + "from impresso_commons.path.path_s3 import fetch_issues, list_issues, list_newspapers\n", + "from impresso_commons.utils.s3 import fixed_s3fs_glob\n", + "from impresso_commons.versioning.data_manifest import DataManifest\n", + "from text_importer.importers.core import upload_issues, write_error\n", + "from smart_open import open as smart_open_function\n", + "from impresso_commons.versioning.helpers import counts_for_canonical_issue\n", + "import dask.bag as db\n", + "from typing import Any, Callable\n", + "import git\n", + "from text_importer.utils import init_logger" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "IMPRESSO_STORAGEOPT = s3.get_storage_options()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "logger = logging.getLogger()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def add_property(object_dict: dict[str, Any], prop_name: str, prop_function: Callable[str, str], function_input: str):\n", + " object_dict[prop_name] = prop_function(function_input)\n", + " logger.debug(\"%s -> Added property %s: %s\", object_dict['id'], prop_name, object_dict[prop_name])\n", + " return object_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def write_upload_issues(\n", + " key: tuple[str, str],\n", + " issues: list[dict[str, Any]],\n", + " output_dir: str,\n", + " bucket_name: str,\n", + " failed_log: str | None = None,\n", + ") -> tuple[str, str]:\n", + " \"\"\"Compress issues for a Journal-year in a json file and upload them to s3.\n", + "\n", + " The compressed ``.bz2`` output file is a JSON-line file, where each line\n", + " corresponds to an individual and issue document in the canonical format.\n", + "\n", + " Args:\n", + " key (str): Hyphen separated Newspaper ID and year of input issues, e.g. `GDL-1900`.\n", + " issues (list[dict[str, Any]]): A list of issues as dicts.\n", + " output_dir (str): Local output directory.\n", + " bucket_name (str): Name of S3 bucket where to upload the file.\n", + " failed_log (str | None, optional): Path to the log file used when an\n", + " instantiation was not successful. Defaults to None.\n", + "\n", + " Returns:\n", + " Tuple[str, str]: Label following the template `-` and \n", + " the path to the the compressed `.bz2` file.\n", + " \"\"\"\n", + " newspaper, year = key\n", + " filename = f'{newspaper}-{year}-issues.jsonl.bz2'\n", + " filepath = os.path.join(output_dir, newspaper, filename)\n", + " logger.info(f'Compressing {len(issues)} JSON files into {filepath}')\n", + "\n", + " os.makedirs(os.path.dirname(filepath), exist_ok =True)\n", + "\n", + " try:\n", + " with smart_open_function(filepath, 'ab') as fout:\n", + " writer = jsonlines.Writer(fout)\n", + "\n", + " writer.write_all(issues)\n", + "\n", + " logger.info(f'Written {len(items)} issues to {filepath}')\n", + " writer.close()\n", + " except Exception as e:\n", + " logger.error(f\"Error for {filepath}\")\n", + " logger.exception(e)\n", + " #write_error(filepath, e, failed_log)\n", + "\n", + " upload_issues('-'.join(key), filepath, bucket_name)\n", + "\n", + " return key, filepath\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SWA - Patch 6\n", + "\n", + "The patch consists of adding a new `iiif_manifest_uri` property mapping to the IIIF presentation API for the given issue." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# initialize values for patch\n", + "SWA_TITLES = ['arbeitgeber', 'handelsztg']\n", + "SWA_IIIF_BASE_URI = 'https://ub-iiifpresentation.ub.unibas.ch/impresso_sb'\n", + "PROP_NAME = 'iiif_manifest_uri'\n", + "\n", + "error_log = '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa_errors.log'\n", + "\n", + "init_logger(logger, logging.DEBUG, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa.log')\n", + "logger.info(\"Patching titles %s: adding %s property at issue level\", SWA_TITLES, PROP_NAME)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# define patch function\n", + "def swa_manifest_uri(issue_id: str, swa_iiif: str = SWA_IIIF_BASE_URI) -> str:\n", + " \"\"\"\n", + " https://ub-iiifpresentation.ub.unibas.ch/impresso_sb/[issue canonical ID]-issue/manifest\n", + " \"\"\"\n", + " return os.path.join(swa_iiif, '-'.join([issue_id, 'issue']), 'manifest')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# initialise manifest to keep track of updates\n", + "canonical_repo = git.Repo('/home/piconti/impresso-text-acquisition')\n", + "s3_input_bucket = 'canonical-data'\n", + "s3_output_bucket = 'canonical-sandbox'\n", + "temp_dir = '/scratch/piconti/impresso/patches_temp'\n", + "patched_fields=[PROP_NAME]\n", + "schema_path = '/home/piconti/impresso-text-acquisition/text_importer/impresso-schemas/json/versioning/manifest.schema.json'\n", + "\n", + "swa_patch_6_manifest = DataManifest(\n", + " data_stage = 'canonical',\n", + " s3_output_bucket = s3_output_bucket,\n", + " git_repo = canonical_repo,\n", + " temp_dir = temp_dir,\n", + " patched_fields=patched_fields,\n", + " staging = True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Perform the patch, tracking updates and upload results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# download the issues of interest for this patch\n", + "swa_issues = fetch_issues('canonical-data', True, SWA_TITLES)\n", + "\n", + "# patch them keeping track of the data that's been modified\n", + "yearly_patched_issues = {}\n", + "\n", + "for issue in swa_issues[:200]:\n", + " # key is title-year\n", + " title, year = issue['id'].split('-')[:2]\n", + " key = '-'.join([title, year])\n", + " if key in yearly_patched_issues:\n", + " yearly_patched_issues[key].append(add_property(issue, PROP_NAME, swa_manifest_uri, issue['id']))\n", + " else:\n", + " yearly_patched_issues[key] = [add_property(issue, PROP_NAME, swa_manifest_uri, issue['id'])]\n", + " \n", + " swa_patch_6_manifest.add_by_title_year(title, year, counts_for_canonical_issue(issue))\n", + "\n", + "# write and upload the updated issues to s3\n", + "for key, issues in yearly_patched_issues.items():\n", + " write_upload_issues(key.split('-'), issues, temp_dir, s3_output_bucket, error_log)\n", + "\n", + "# finalize the manifest and export it\n", + "note = f\"Patching titles {SWA_TITLES}: adding {PROP_NAME} property at issue level\"\n", + "swa_patch_6_manifest.append_to_notes(note)\n", + "swa_patch_6_manifest.compute(export_to_git_and_s3 = False)\n", + "swa_patch_6_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=False)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## FedGaz Patch" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "text_acquisition", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/text_importer/importers/tetml/classes.py b/text_importer/importers/tetml/classes.py index e00eb7d9..2b23ea63 100644 --- a/text_importer/importers/tetml/classes.py +++ b/text_importer/importers/tetml/classes.py @@ -34,6 +34,7 @@ def parse(self): "id": self.id, "cdt": strftime("%Y-%m-%d %H:%M:%S"), "cc": True, + "iiif_img_base_uri": os.path.join(IIIF_ENDPOINT_URI, self.id), "r": self.page_content["r"], } diff --git a/text_importer/impresso-schemas b/text_importer/impresso-schemas index d1925bfe..708ba747 160000 --- a/text_importer/impresso-schemas +++ b/text_importer/impresso-schemas @@ -1 +1 @@ -Subproject commit d1925bfee8a6a5525374e353ca9391747fb9d6f6 +Subproject commit 708ba747ab3143de4a0b63f11c7216d02bb5235b From 49288c6db61d10db33e01065530402ec68075162 Mon Sep 17 00:00:00 2001 From: piconti Date: Mon, 19 Feb 2024 18:57:37 +0100 Subject: [PATCH 10/53] minor change to the patch code --- notebooks/canonical_patches_1_and_6.ipynb | 183 ++++++++++++++++++++-- 1 file changed, 168 insertions(+), 15 deletions(-) diff --git a/notebooks/canonical_patches_1_and_6.ipynb b/notebooks/canonical_patches_1_and_6.ipynb index d7042eae..1cf4bf0c 100644 --- a/notebooks/canonical_patches_1_and_6.ipynb +++ b/notebooks/canonical_patches_1_and_6.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -42,7 +42,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -79,7 +79,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -143,7 +143,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -154,13 +154,13 @@ "\n", "error_log = '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa_errors.log'\n", "\n", - "init_logger(logger, logging.DEBUG, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa.log')\n", + "init_logger(logger, logging.INFO, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa.log')\n", "logger.info(\"Patching titles %s: adding %s property at issue level\", SWA_TITLES, PROP_NAME)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -174,14 +174,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "# initialise manifest to keep track of updates\n", "canonical_repo = git.Repo('/home/piconti/impresso-text-acquisition')\n", "s3_input_bucket = 'canonical-data'\n", - "s3_output_bucket = 'canonical-sandbox'\n", + "s3_output_bucket = 'canonical-staging'\n", + "# previous manifest is not in the output bucket --> provide it as argument\n", + "previous_manifest_path = 's3://canonical-data/canonical_v0-0-1.json' \n", "temp_dir = '/scratch/piconti/impresso/patches_temp'\n", "patched_fields=[PROP_NAME]\n", "schema_path = '/home/piconti/impresso-text-acquisition/text_importer/impresso-schemas/json/versioning/manifest.schema.json'\n", @@ -192,7 +194,7 @@ " git_repo = canonical_repo,\n", " temp_dir = temp_dir,\n", " patched_fields=patched_fields,\n", - " staging = True\n", + " previous_mft_path = previous_manifest_path\n", ")" ] }, @@ -205,9 +207,160 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fetching list of newspapers from canonical-data\n", + "canonical-data contains 94 newspapers\n", + "canonical-data contains 130 .bz2 files with issues for the provided newspapers ['arbeitgeber', 'handelsztg']\n", + "Fetching issue ids from 130 .bz2 files (compute=True)\n", + "handelsztg 1861\n", + "handelsztg 1862\n", + "handelsztg 1863\n", + "handelsztg 1864\n", + "handelsztg 1865\n", + "handelsztg 1866\n", + "handelsztg 1867\n", + "handelsztg 1868\n", + "handelsztg 1869\n", + "handelsztg 1870\n", + "handelsztg 1871\n", + "handelsztg 1872\n", + "handelsztg 1873\n", + "handelsztg 1874\n", + "handelsztg 1875\n", + "handelsztg 1876\n", + "handelsztg 1877\n", + "handelsztg 1878\n", + "handelsztg 1879\n", + "handelsztg 1880\n", + "handelsztg 1881\n", + "handelsztg 1882\n", + "handelsztg 1883\n", + "handelsztg 1884\n", + "handelsztg 1885\n", + "handelsztg 1886\n", + "handelsztg 1890\n", + "handelsztg 1891\n", + "handelsztg 1892\n", + "handelsztg 1893\n", + "handelsztg 1894\n", + "arbeitgeber 1907\n", + "arbeitgeber 1908\n", + "arbeitgeber 1909\n", + "arbeitgeber 1910\n", + "arbeitgeber 1911\n", + "arbeitgeber 1912\n", + "arbeitgeber 1913\n", + "arbeitgeber 1914\n", + "arbeitgeber 1915\n", + "arbeitgeber 1916\n", + "arbeitgeber 1917\n", + "arbeitgeber 1918\n", + "arbeitgeber 1919\n", + "arbeitgeber 1924\n", + "arbeitgeber 1925\n", + "arbeitgeber 1926\n", + "arbeitgeber 1927\n", + "arbeitgeber 1928\n", + "arbeitgeber 1929\n", + "arbeitgeber 1930\n", + "arbeitgeber 1931\n", + "arbeitgeber 1932\n", + "arbeitgeber 1933\n", + "arbeitgeber 1934\n", + "arbeitgeber 1935\n", + "arbeitgeber 1936\n", + "arbeitgeber 1937\n", + "arbeitgeber 1938\n", + "arbeitgeber 1939\n", + "arbeitgeber 1940\n", + "arbeitgeber 1941\n", + "arbeitgeber 1942\n", + "arbeitgeber 1943\n", + "arbeitgeber 1944\n", + "arbeitgeber 1945\n", + "arbeitgeber 1946\n", + "arbeitgeber 1947\n", + "arbeitgeber 1948\n", + "arbeitgeber 1949\n", + "arbeitgeber 1950\n", + "arbeitgeber 1951\n", + "arbeitgeber 1952\n", + "arbeitgeber 1953\n", + "arbeitgeber 1954\n", + "arbeitgeber 1955\n", + "arbeitgeber 1956\n", + "arbeitgeber 1957\n", + "arbeitgeber 1958\n", + "arbeitgeber 1959\n", + "arbeitgeber 1960\n", + "arbeitgeber 1961\n", + "arbeitgeber 1962\n", + "arbeitgeber 1963\n", + "arbeitgeber 1964\n", + "arbeitgeber 1965\n", + "arbeitgeber 1966\n", + "arbeitgeber 1967\n", + "arbeitgeber 1968\n", + "arbeitgeber 1969\n", + "arbeitgeber 1970\n", + "arbeitgeber 1971\n", + "arbeitgeber 1972\n", + "arbeitgeber 1973\n", + "arbeitgeber 1974\n", + "arbeitgeber 1975\n", + "arbeitgeber 1976\n", + "arbeitgeber 1977\n", + "arbeitgeber 1978\n", + "arbeitgeber 1979\n", + "arbeitgeber 1980\n", + "arbeitgeber 1981\n", + "arbeitgeber 1982\n", + "arbeitgeber 1983\n", + "arbeitgeber 1984\n", + "arbeitgeber 1985\n", + "arbeitgeber 1986\n", + "arbeitgeber 1987\n", + "arbeitgeber 1988\n", + "arbeitgeber 1989\n", + "arbeitgeber 1990\n", + "arbeitgeber 1991\n", + "arbeitgeber 1992\n", + "arbeitgeber 1993\n", + "arbeitgeber 1994\n", + "arbeitgeber 1995\n", + "arbeitgeber 1996\n", + "arbeitgeber 1997\n", + "arbeitgeber 1998\n", + "arbeitgeber 1999\n", + "arbeitgeber 2000\n", + "arbeitgeber 2001\n", + "arbeitgeber 2002\n", + "arbeitgeber 2003\n", + "arbeitgeber 2004\n", + "arbeitgeber 2005\n", + "arbeitgeber 2006\n", + "arbeitgeber 2007\n", + "arbeitgeber 2008\n", + "arbeitgeber 2010\n" + ] + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# download the issues of interest for this patch\n", "swa_issues = fetch_issues('canonical-data', True, SWA_TITLES)\n", @@ -215,7 +368,7 @@ "# patch them keeping track of the data that's been modified\n", "yearly_patched_issues = {}\n", "\n", - "for issue in swa_issues[:200]:\n", + "for issue in swa_issues:\n", " # key is title-year\n", " title, year = issue['id'].split('-')[:2]\n", " key = '-'.join([title, year])\n", @@ -234,7 +387,7 @@ "note = f\"Patching titles {SWA_TITLES}: adding {PROP_NAME} property at issue level\"\n", "swa_patch_6_manifest.append_to_notes(note)\n", "swa_patch_6_manifest.compute(export_to_git_and_s3 = False)\n", - "swa_patch_6_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=False)\n", + "swa_patch_6_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True)\n", " " ] }, From c70e989f83660927314158a5b4a27bac7da3ff01 Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 20 Feb 2024 17:45:41 +0100 Subject: [PATCH 11/53] add code for patch 1+ --- notebooks/canonical_patches_1_and_6.ipynb | 1315 +++++++++++++++++++-- text_importer/importers/tetml/classes.py | 2 + 2 files changed, 1249 insertions(+), 68 deletions(-) diff --git a/notebooks/canonical_patches_1_and_6.ipynb b/notebooks/canonical_patches_1_and_6.ipynb index 1cf4bf0c..d525e328 100644 --- a/notebooks/canonical_patches_1_and_6.ipynb +++ b/notebooks/canonical_patches_1_and_6.ipynb @@ -6,7 +6,16 @@ "source": [ "# Dev notebook for patching code\n", "\n", - "Related to issue [#117](https://github.com/impresso/impresso-text-acquisition/issues/117)" + "Related to issue [#117](https://github.com/impresso/impresso-text-acquisition/issues/117)\n", + "\n", + "This notebook contains the code used to perform some of the simpler patches necessary on the canonical data. \n", + "In particular patches n°1 and n°6:\n", + "- n°1: Adding a property `iiif_img_base_uri` at the top level of all page JSONs for a given set of journals, with the base uri of the iiif image API for the specific page. \n", + " - This patch concerns the journals `FedGazDe`, `FedGazFr` and `NZZ`.\n", + "- n°6: Adding a property `iiif_manifest_uri` at the top level of all issue JSONs for a given set of journals, with the uri to the specific issue's manifest in the IIIF presentation API. \n", + " - This patch concerns the journals `arbeitgeber`, `handelsztg`.\n", + "\n", + "The result of these patches will be logged and documented in the manifest files created alongside these patches, and stored in the S3 as well as in the `impresso-data-release` GitHub repository." ] }, { @@ -18,9 +27,18 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/piconti/.conda/envs/patches/lib/python3.11/site-packages/python_jsonschema_objects/__init__.py:60: UserWarning: Schema version http://json-schema.org/draft-06/schema# not recognized. Some keywords and features may not be supported.\n", + " warnings.warn(\n" + ] + } + ], "source": [ "import os\n", "import boto3\n", @@ -28,21 +46,23 @@ "import logging\n", "import jsonlines\n", "from impresso_commons.utils import s3\n", - "from impresso_commons.path.path_s3 import fetch_issues, list_issues, list_newspapers\n", + "from impresso_commons.path.path_s3 import fetch_files, list_files, list_newspapers\n", "from impresso_commons.utils.s3 import fixed_s3fs_glob\n", "from impresso_commons.versioning.data_manifest import DataManifest\n", - "from text_importer.importers.core import upload_issues, write_error\n", + "from text_importer.importers.core import upload_issues, upload_pages\n", "from smart_open import open as smart_open_function\n", "from impresso_commons.versioning.helpers import counts_for_canonical_issue\n", "import dask.bag as db\n", "from typing import Any, Callable\n", "import git\n", - "from text_importer.utils import init_logger" + "from text_importer.utils import init_logger\n", + "import copy\n", + "from dask.distributed import Client" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -51,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -67,11 +87,11 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ - "def add_property(object_dict: dict[str, Any], prop_name: str, prop_function: Callable[str, str], function_input: str):\n", + "def add_property(object_dict: dict[str, Any], prop_name: str, prop_function: Callable[[str], str], function_input: str):\n", " object_dict[prop_name] = prop_function(function_input)\n", " logger.debug(\"%s -> Added property %s: %s\", object_dict['id'], prop_name, object_dict[prop_name])\n", " return object_dict" @@ -79,7 +99,62 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "def write_error(\n", + " thing_id: str,\n", + " origin_function: str,\n", + " error: Exception, \n", + " failed_log: str\n", + ") -> None:\n", + " \"\"\"Write the given error of a failed import to the `failed_log` file.\n", + "\n", + " Args:\n", + " thing (NewspaperIssue | NewspaperPage | IssueDir): Object for which\n", + " the error occurred.\n", + " error (Exception): Error that occurred and should be logged.\n", + " failed_log (str): Path to log file for failed imports.\n", + " \"\"\"\n", + " note = (\n", + " f\"Error in {origin_function} for {thing_id}: {error}\"\n", + " )\n", + "\n", + " logger.exception(note)\n", + "\n", + " with open(failed_log, \"a+\") as f:\n", + " f.write(note + \"\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "def write_jsonlines_file(filepath: str, contents: str | list[str], content_type: str, failed_log: str | None = None) -> None:\n", + " \n", + " os.makedirs(os.path.dirname(filepath), exist_ok =True)\n", + "\n", + " try:\n", + " with smart_open_function(filepath, 'ab') as fout:\n", + " writer = jsonlines.Writer(fout)\n", + "\n", + " writer.write_all(contents)\n", + "\n", + " logger.info(f'Written {len(contents)} {content_type} to {filepath}')\n", + " writer.close()\n", + " except Exception as e:\n", + " logger.error(f\"Error for {filepath}\")\n", + " logger.exception(e)\n", + " if failed_log is not None:\n", + " write_error(os.path.basename(filepath), 'write_jsonlines_file()', e, failed_log)" + ] + }, + { + "cell_type": "code", + "execution_count": 68, "metadata": {}, "outputs": [], "source": [ @@ -88,20 +163,17 @@ " issues: list[dict[str, Any]],\n", " output_dir: str,\n", " bucket_name: str,\n", - " failed_log: str | None = None,\n", ") -> tuple[str, str]:\n", " \"\"\"Compress issues for a Journal-year in a json file and upload them to s3.\n", "\n", " The compressed ``.bz2`` output file is a JSON-line file, where each line\n", - " corresponds to an individual and issue document in the canonical format.\n", + " corresponds to an individual issue document in the canonical format.\n", "\n", " Args:\n", " key (str): Hyphen separated Newspaper ID and year of input issues, e.g. `GDL-1900`.\n", " issues (list[dict[str, Any]]): A list of issues as dicts.\n", " output_dir (str): Local output directory.\n", " bucket_name (str): Name of S3 bucket where to upload the file.\n", - " failed_log (str | None, optional): Path to the log file used when an\n", - " instantiation was not successful. Defaults to None.\n", "\n", " Returns:\n", " Tuple[str, str]: Label following the template `-` and \n", @@ -112,24 +184,127 @@ " filepath = os.path.join(output_dir, newspaper, filename)\n", " logger.info(f'Compressing {len(issues)} JSON files into {filepath}')\n", "\n", - " os.makedirs(os.path.dirname(filepath), exist_ok =True)\n", + " write_jsonlines_file(filepath, issues, 'issues')\n", "\n", - " try:\n", - " with smart_open_function(filepath, 'ab') as fout:\n", - " writer = jsonlines.Writer(fout)\n", + " return upload_issues('-'.join(key), filepath, bucket_name)" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [], + "source": [ + "def write_upload_pages(\n", + " key: str,\n", + " pages: list[dict[str, Any]],\n", + " output_dir: str,\n", + " bucket_name: str,\n", + " failed_log: str | None = None\n", + ") -> tuple[str, tuple[bool, str]]:\n", + " \"\"\"Compress pages for a given edition in a json file and upload them to s3.\n", "\n", - " writer.write_all(issues)\n", + " The compressed ``.bz2`` output file is a JSON-line file, where each line\n", + " corresponds to an individual page document in the canonical format.\n", "\n", - " logger.info(f'Written {len(items)} issues to {filepath}')\n", - " writer.close()\n", - " except Exception as e:\n", - " logger.error(f\"Error for {filepath}\")\n", - " logger.exception(e)\n", - " #write_error(filepath, e, failed_log)\n", + " Args:\n", + " key (str): Canonical ID of the newspaper issue (e.g. GDL-1900-01-02-a).\n", + " pages (list[dict[str, Any]]): The list of pages for the provided key.\n", + " output_dir (str): Local output directory.\n", + " bucket_name (str): Name of S3 bucket where to upload the file.\n", + "\n", + " Returns:\n", + " Tuple[str, str]: Label following the template `-` and \n", + " the path to the the compressed `.bz2` file.\n", + " \"\"\"\n", + " newspaper, year, month, day, edition = key.split('-')\n", + " filename = f'{key}-pages.jsonl.bz2'\n", + " filepath = os.path.join(output_dir, newspaper, f'{newspaper}-{year}', filename)\n", + " logger.info(f'Compressing {len(pages)} JSON files into {filepath}')\n", + "\n", + " write_jsonlines_file(filepath, pages, 'pages', failed_log)\n", + "\n", + " return key, (upload_pages(key, filepath, bucket_name))" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "# adapted from https://github.com/impresso/impresso-data-sanitycheck/blob/master/sanity_check/contents/stats.py#L241\n", + "def canonical_stats_from_issue_bag(fetched_issues: db.core.Bag) -> list[dict[str, Any]]:\n", + " \"\"\"Computes number of issues and pages per newspaper from canonical data in s3.\n", + "\n", + " :param str s3_canonical_bucket: S3 bucket with canonical data.\n", + " :return: A pandas DataFrame with newspaper ID as the index and columns `n_issues`, `n_pages`.\n", + " :rtype: pd.DataFrame\n", + "\n", + " \"\"\"\n", + " pages_count_df = (\n", + " fetched_issues.map(\n", + " lambda i: {\n", + " \"np_id\": i[\"id\"].split('-')[0], \n", + " \"year\":i[\"id\"].split('-')[1], \n", + " \"id\": i['id'], \n", + " \"issue_id\": i['id'], \n", + " \"n_pages\": len(set(i['pp'])),\n", + " \"n_content_items\": len(i['i']),\n", + " \"n_images\": len([item for item in i['i'] if item['m']['tp']=='image'])\n", + " }\n", + " )\n", + " .to_dataframe(meta={'np_id': str, 'year': str, \n", + " 'id': str, 'issue_id': str, \n", + " \"n_pages\": int, 'n_images': int,\n", + " 'n_content_items': int})\n", + " .set_index('id')\n", + " .persist()\n", + " )\n", + "\n", + " # cum the counts for all values collected\n", + " aggregated_df = (pages_count_df\n", + " .groupby(by=['np_id', 'year'])\n", + " .agg({\"n_pages\": sum, 'issue_id': 'count', 'n_content_items': sum, 'n_images': sum})\n", + " .rename(columns={'issue_id': 'issues', 'n_pages': 'pages', \n", + " 'n_content_items': 'content_items_out', 'n_images':'images'})\n", + " .reset_index()\n", + " )\n", + "\n", + " # return as a list of dicts\n", + " return aggregated_df.to_bag(format='dict').compute()" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [], + "source": [ + "def process_pages_of_issue(\n", + " key: str, \n", + " pages: list[dict[str, Any]],\n", + " manifest: DataManifest,\n", + " issue_stats: list[dict],\n", + " failed_log: str | None = None \n", + ") -> tuple[bool, str]:\n", + " newspaper, year, month, day, edition = key.split('-')\n", + "\n", + " if not manifest.has_title_year_key(newspaper, year):\n", + " current_stats = [d for d in issue_stats if d['np_id']==newspaper and d['year']==year][0]\n", + " # reduce the number of stats to consider at each step\n", + " issue_stats.remove(current_stats)\n", + " # remove unwanted keys from the dict\n", + " del current_stats['np_id']\n", + " del current_stats['year']\n", + " success = manifest.replace_by_title_year(newspaper, year, current_stats)\n", + " if not success:\n", + " logger.warning(\"Problem encountered when trying to add %s for %s-%s\", current_stats, newspaper, year)\n", "\n", - " upload_issues('-'.join(key), filepath, bucket_name)\n", + " key, filepath = write_upload_pages(key, pages, manifest.temp_dir, manifest.output_bucket_name, failed_log)\n", "\n", - " return key, filepath\n" + " return key, (filepath, manifest)\n", + " " ] }, { @@ -143,7 +318,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -160,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -174,7 +349,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -191,6 +366,7 @@ "swa_patch_6_manifest = DataManifest(\n", " data_stage = 'canonical',\n", " s3_output_bucket = s3_output_bucket,\n", + " s3_input_bucket = s3_input_bucket,\n", " git_repo = canonical_repo,\n", " temp_dir = temp_dir,\n", " patched_fields=patched_fields,\n", @@ -207,7 +383,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -218,37 +394,6 @@ "canonical-data contains 94 newspapers\n", "canonical-data contains 130 .bz2 files with issues for the provided newspapers ['arbeitgeber', 'handelsztg']\n", "Fetching issue ids from 130 .bz2 files (compute=True)\n", - "handelsztg 1861\n", - "handelsztg 1862\n", - "handelsztg 1863\n", - "handelsztg 1864\n", - "handelsztg 1865\n", - "handelsztg 1866\n", - "handelsztg 1867\n", - "handelsztg 1868\n", - "handelsztg 1869\n", - "handelsztg 1870\n", - "handelsztg 1871\n", - "handelsztg 1872\n", - "handelsztg 1873\n", - "handelsztg 1874\n", - "handelsztg 1875\n", - "handelsztg 1876\n", - "handelsztg 1877\n", - "handelsztg 1878\n", - "handelsztg 1879\n", - "handelsztg 1880\n", - "handelsztg 1881\n", - "handelsztg 1882\n", - "handelsztg 1883\n", - "handelsztg 1884\n", - "handelsztg 1885\n", - "handelsztg 1886\n", - "handelsztg 1890\n", - "handelsztg 1891\n", - "handelsztg 1892\n", - "handelsztg 1893\n", - "handelsztg 1894\n", "arbeitgeber 1907\n", "arbeitgeber 1908\n", "arbeitgeber 1909\n", @@ -347,7 +492,38 @@ "arbeitgeber 2006\n", "arbeitgeber 2007\n", "arbeitgeber 2008\n", - "arbeitgeber 2010\n" + "arbeitgeber 2010\n", + "handelsztg 1861\n", + "handelsztg 1862\n", + "handelsztg 1863\n", + "handelsztg 1864\n", + "handelsztg 1865\n", + "handelsztg 1866\n", + "handelsztg 1867\n", + "handelsztg 1868\n", + "handelsztg 1869\n", + "handelsztg 1870\n", + "handelsztg 1871\n", + "handelsztg 1872\n", + "handelsztg 1873\n", + "handelsztg 1874\n", + "handelsztg 1875\n", + "handelsztg 1876\n", + "handelsztg 1877\n", + "handelsztg 1878\n", + "handelsztg 1879\n", + "handelsztg 1880\n", + "handelsztg 1881\n", + "handelsztg 1882\n", + "handelsztg 1883\n", + "handelsztg 1884\n", + "handelsztg 1885\n", + "handelsztg 1886\n", + "handelsztg 1890\n", + "handelsztg 1891\n", + "handelsztg 1892\n", + "handelsztg 1893\n", + "handelsztg 1894\n" ] }, { @@ -356,7 +532,7 @@ "True" ] }, - "execution_count": 14, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -395,15 +571,1018 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## FedGaz Patch" + "# FedGaz + NZZ – Patch 1\n", + "\n", + "The patch consists of adding a new `iiif_img_base_uri` property mapping to the base uri of the IIIF image API for the given page." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
\n", + "

Client

\n", + "

Client-0a4729f5-d005-11ee-8242-90e2baa156fc

\n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "
Connection method: Cluster objectCluster type: distributed.LocalCluster
\n", + " Dashboard: http://127.0.0.1:8787/status\n", + "
\n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "

Cluster Info

\n", + "
\n", + "
\n", + "
\n", + "
\n", + "

LocalCluster

\n", + "

37346621

\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + "
\n", + " Dashboard: http://127.0.0.1:8787/status\n", + " \n", + " Workers: 16\n", + "
\n", + " Total threads: 32\n", + " \n", + " Total memory: 251.79 GiB\n", + "
Status: runningUsing processes: True
\n", + "\n", + "
\n", + " \n", + "

Scheduler Info

\n", + "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "
\n", + "

Scheduler

\n", + "

Scheduler-0a56e181-845a-4c01-ba24-f33361ec05e0

\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " Comm: tcp://127.0.0.1:40417\n", + " \n", + " Workers: 16\n", + "
\n", + " Dashboard: http://127.0.0.1:8787/status\n", + " \n", + " Total threads: 32\n", + "
\n", + " Started: Just now\n", + " \n", + " Total memory: 251.79 GiB\n", + "
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "

Workers

\n", + "
\n", + "\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 0

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:46445\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:46371/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:42875\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-5cw1vcb6\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 1

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:41743\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:43645/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:40005\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-c2hw6qf2\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 2

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:40427\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:37585/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:46235\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-n964b9ka\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 3

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:39089\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:38367/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:38337\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-qxf8qgc3\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 4

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:33831\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:42137/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:44295\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-7danon1m\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 5

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:43713\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:35149/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:37895\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-pp3s1ieg\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 6

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:44305\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:39069/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:38289\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-9y770j8_\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 7

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:44335\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:33063/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:42583\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-q6cu1xjw\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 8

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:34571\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:34545/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:44235\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-0f76q4gs\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 9

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:37737\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:33031/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:42603\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-frtac1bc\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 10

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:44507\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:39469/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:42061\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-86xe1ry_\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 11

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:36779\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:44301/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:43463\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-3trmf8ie\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 12

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:41599\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:44009/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:46141\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-llp8s_gp\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 13

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:38823\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:44141/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:37807\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-tihadj4i\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 14

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:41087\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:33327/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:41891\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-cnt6y6_r\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "

Worker: 15

\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + "\n", + " \n", + "\n", + "
\n", + " Comm: tcp://127.0.0.1:36671\n", + " \n", + " Total threads: 2\n", + "
\n", + " Dashboard: http://127.0.0.1:42903/status\n", + " \n", + " Memory: 15.74 GiB\n", + "
\n", + " Nanny: tcp://127.0.0.1:37259\n", + "
\n", + " Local directory: /tmp/dask-scratch-space/worker-n0apo__o\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "\n", + "
\n", + "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "
\n", + " \n", + "\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client = Client(n_workers=16, threads_per_worker=2)\n", + "client" + ] + }, + { + "cell_type": "code", + "execution_count": 10, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "# initialize values for patch\n", + "UZH_TITLES = ['FedGazDe', 'FedGazFr', 'NZZ']\n", + "IMPRESSO_IIIF_BASE_URI = \"https://impresso-project.ch/api/proxy/iiif/\"\n", + "PROP_NAME = 'iiif_img_base_uri'\n", + "\n", + "error_log = '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_1_fedgaz_errors.log'\n", + "\n", + "init_logger(logger, logging.INFO, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_1_fedgaz_nzz.log')\n", + "logger.info(\"Patching titles %s: adding %s property at page level\", UZH_TITLES, PROP_NAME)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# define patch function\n", + "def uzh_image_base_uri(page_id: str, impresso_iiif: str = IMPRESSO_IIIF_BASE_URI) -> str:\n", + " \"\"\"\n", + " https://impresso-project.ch/api/proxy/iiif/[page canonical ID]\n", + " \"\"\"\n", + " return os.path.join(impresso_iiif, page_id)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "# initialise manifest to keep track of updates\n", + "canonical_repo = git.Repo('/home/piconti/impresso-text-acquisition')\n", + "s3_input_bucket = 'canonical-data'\n", + "s3_output_bucket = 'canonical-sandbox' #'canonical-staging'\n", + "# previous manifest is not in the output bucket --> provide it as argument\n", + "previous_manifest_path = 's3://canonical-staging/canonical_v0-0-2.json' \n", + "temp_dir = '/scratch/piconti/impresso/patches_temp'\n", + "patched_fields=[PROP_NAME]\n", + "schema_path = '/home/piconti/impresso-text-acquisition/text_importer/impresso-schemas/json/versioning/manifest.schema.json'\n", + "\n", + "nzz_patch_1_manifest = DataManifest(\n", + " data_stage = 'canonical',\n", + " s3_output_bucket = s3_output_bucket,\n", + " s3_input_bucket = s3_input_bucket,\n", + " git_repo = canonical_repo,\n", + " temp_dir = temp_dir,\n", + " patched_fields=patched_fields,\n", + " previous_mft_path = previous_manifest_path\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Perform the patch, tracking updates and upload results" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fetching list of newspapers from canonical-data\n", + "canonical-data contains 94 newspapers\n", + "canonical-data contains 473 .bz2 issue files for the provided newspapers ['FedGazDe', 'FedGazFr', 'NZZ']\n", + "canonical-data contains 128930 .bz2 page files for the provided newspapers ['FedGazDe', 'FedGazFr', 'NZZ']\n" + ] + } + ], + "source": [ + "# download the issues of interest for this patch\n", + "uzh_issues, uzh_pages = fetch_files('canonical-data', False, 'both', UZH_TITLES)\n", + "\n", + "# compute the statistics that correspond to this\n", + "logger.info(\"computing the canonical statistics on the issues...\")\n", + "stats_from_issues = canonical_stats_from_issue_bag(uzh_issues)\n", + "\n", + "issue_stats = copy.deepcopy(stats_from_issues)\n", + "\n", + "# patch the pages and write them back to s3.\n", + "uzh_patched_pages = (\n", + " uzh_pages\n", + " .map(lambda p: add_property(p, PROP_NAME, uzh_image_base_uri, p['id']))\n", + " .groupby(lambda p: '-'.join(p['id'].split('-')[:-1]))\n", + " .starmap(\n", + " write_upload_pages,\n", + " output_dir=temp_dir,\n", + " bucket_name=s3_output_bucket,\n", + " failed_log=error_log\n", + " )\n", + ").compute()\n", + "\n", + "# fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket.\n", + "issues_with_patched_pages = {}\n", + "for issue_id, (success, path) in uzh_patched_pages:\n", + " title, year, month, day, edition = issue_id.split('-')\n", + " \n", + " if success and not nzz_patch_1_manifest.has_title_year_key(title, year):\n", + " current_stats = [d for d in issue_stats if d['np_id']==title and d['year']==year][0]\n", + " # reduce the number of stats to consider at each step\n", + " issue_stats.remove(current_stats)\n", + " # remove unwanted keys from the dict\n", + " del current_stats['np_id']\n", + " del current_stats['year']\n", + "\n", + " add_ok = nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats)\n", + "\n", + " if add_ok:\n", + " specific_issue = [i for i in uzh_issues if i['id']==issue_id]\n", + " assert len(specific_issue) == 1, f\"More than one issue had the exact issue id: {issue_id}\"\n", + " # if patching and addition to manifest was successful, the issue can be copied to the new bucket\n", + " if key in issues_with_patched_pages:\n", + " issues_with_patched_pages['-'.join([title, year])] = specific_issue\n", + " else:\n", + " issues_with_patched_pages['-'.join([title, year])].extend(specific_issue)\n", + " else:\n", + " logger.warning(\"Problem encountered when trying to add %s for %s-%s\", current_stats, title, year)\n", + " elif not success:\n", + " logger.warning(\"The pages for issue %s were not correctly uploaded\", issue_id)\n", + "\n", + "# write and upload the issues to the new s3 bucket\n", + "for key, issues in issues_with_patched_pages.items():\n", + " success, issue_path = write_upload_issues(key.split('-'), issues, temp_dir, s3_output_bucket, error_log)\n", + " if not success:\n", + " logger.warning(\"The copy of issues %s had a problem\", key)\n", + "\n", + "# finalize the manifest and export it\n", + "note = f\"Patching titles {UZH_TITLES}: adding {PROP_NAME} property at page level\"\n", + "nzz_patch_1_manifest.append_to_notes(note)\n", + "nzz_patch_1_manifest.compute(export_to_git_and_s3 = False)\n", + "nzz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True)" + ] }, { "cell_type": "code", diff --git a/text_importer/importers/tetml/classes.py b/text_importer/importers/tetml/classes.py index 2b23ea63..ab9ca0b9 100644 --- a/text_importer/importers/tetml/classes.py +++ b/text_importer/importers/tetml/classes.py @@ -1,5 +1,6 @@ """Classes to handle the TETML OCR format.""" +import os import logging from pathlib import Path from time import strftime @@ -12,6 +13,7 @@ logger = logging.getLogger(__name__) +IIIF_ENDPOINT_URI = "https://impresso-project.ch/api/proxy/iiif/" class TetmlNewspaperPage(NewspaperPage): """Generic class representing a page in Tetml format. From 713cf72217dc798a9af1648ac7174e419156b141 Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 20 Feb 2024 21:00:35 +0100 Subject: [PATCH 12/53] Minor fixes --- notebooks/canonical_patches_1_and_6.ipynb | 1142 ++------------------- 1 file changed, 73 insertions(+), 1069 deletions(-) diff --git a/notebooks/canonical_patches_1_and_6.ipynb b/notebooks/canonical_patches_1_and_6.ipynb index d525e328..1760e0bb 100644 --- a/notebooks/canonical_patches_1_and_6.ipynb +++ b/notebooks/canonical_patches_1_and_6.ipynb @@ -27,18 +27,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/piconti/.conda/envs/patches/lib/python3.11/site-packages/python_jsonschema_objects/__init__.py:60: UserWarning: Schema version http://json-schema.org/draft-06/schema# not recognized. Some keywords and features may not be supported.\n", - " warnings.warn(\n" - ] - } - ], + "outputs": [], "source": [ "import os\n", "import boto3\n", @@ -62,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -71,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -87,7 +78,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -99,7 +90,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -129,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -154,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -163,6 +154,7 @@ " issues: list[dict[str, Any]],\n", " output_dir: str,\n", " bucket_name: str,\n", + " failed_log: str | None = None\n", ") -> tuple[str, str]:\n", " \"\"\"Compress issues for a Journal-year in a json file and upload them to s3.\n", "\n", @@ -184,14 +176,14 @@ " filepath = os.path.join(output_dir, newspaper, filename)\n", " logger.info(f'Compressing {len(issues)} JSON files into {filepath}')\n", "\n", - " write_jsonlines_file(filepath, issues, 'issues')\n", + " write_jsonlines_file(filepath, issues, 'issues', failed_log)\n", "\n", " return upload_issues('-'.join(key), filepath, bucket_name)" ] }, { "cell_type": "code", - "execution_count": 65, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -229,7 +221,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -277,7 +269,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -318,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -335,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -349,7 +341,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -383,160 +375,9 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Fetching list of newspapers from canonical-data\n", - "canonical-data contains 94 newspapers\n", - "canonical-data contains 130 .bz2 files with issues for the provided newspapers ['arbeitgeber', 'handelsztg']\n", - "Fetching issue ids from 130 .bz2 files (compute=True)\n", - "arbeitgeber 1907\n", - "arbeitgeber 1908\n", - "arbeitgeber 1909\n", - "arbeitgeber 1910\n", - "arbeitgeber 1911\n", - "arbeitgeber 1912\n", - "arbeitgeber 1913\n", - "arbeitgeber 1914\n", - "arbeitgeber 1915\n", - "arbeitgeber 1916\n", - "arbeitgeber 1917\n", - "arbeitgeber 1918\n", - "arbeitgeber 1919\n", - "arbeitgeber 1924\n", - "arbeitgeber 1925\n", - "arbeitgeber 1926\n", - "arbeitgeber 1927\n", - "arbeitgeber 1928\n", - "arbeitgeber 1929\n", - "arbeitgeber 1930\n", - "arbeitgeber 1931\n", - "arbeitgeber 1932\n", - "arbeitgeber 1933\n", - "arbeitgeber 1934\n", - "arbeitgeber 1935\n", - "arbeitgeber 1936\n", - "arbeitgeber 1937\n", - "arbeitgeber 1938\n", - "arbeitgeber 1939\n", - "arbeitgeber 1940\n", - "arbeitgeber 1941\n", - "arbeitgeber 1942\n", - "arbeitgeber 1943\n", - "arbeitgeber 1944\n", - "arbeitgeber 1945\n", - "arbeitgeber 1946\n", - "arbeitgeber 1947\n", - "arbeitgeber 1948\n", - "arbeitgeber 1949\n", - "arbeitgeber 1950\n", - "arbeitgeber 1951\n", - "arbeitgeber 1952\n", - "arbeitgeber 1953\n", - "arbeitgeber 1954\n", - "arbeitgeber 1955\n", - "arbeitgeber 1956\n", - "arbeitgeber 1957\n", - "arbeitgeber 1958\n", - "arbeitgeber 1959\n", - "arbeitgeber 1960\n", - "arbeitgeber 1961\n", - "arbeitgeber 1962\n", - "arbeitgeber 1963\n", - "arbeitgeber 1964\n", - "arbeitgeber 1965\n", - "arbeitgeber 1966\n", - "arbeitgeber 1967\n", - "arbeitgeber 1968\n", - "arbeitgeber 1969\n", - "arbeitgeber 1970\n", - "arbeitgeber 1971\n", - "arbeitgeber 1972\n", - "arbeitgeber 1973\n", - "arbeitgeber 1974\n", - "arbeitgeber 1975\n", - "arbeitgeber 1976\n", - "arbeitgeber 1977\n", - "arbeitgeber 1978\n", - "arbeitgeber 1979\n", - "arbeitgeber 1980\n", - "arbeitgeber 1981\n", - "arbeitgeber 1982\n", - "arbeitgeber 1983\n", - "arbeitgeber 1984\n", - "arbeitgeber 1985\n", - "arbeitgeber 1986\n", - "arbeitgeber 1987\n", - "arbeitgeber 1988\n", - "arbeitgeber 1989\n", - "arbeitgeber 1990\n", - "arbeitgeber 1991\n", - "arbeitgeber 1992\n", - "arbeitgeber 1993\n", - "arbeitgeber 1994\n", - "arbeitgeber 1995\n", - "arbeitgeber 1996\n", - "arbeitgeber 1997\n", - "arbeitgeber 1998\n", - "arbeitgeber 1999\n", - "arbeitgeber 2000\n", - "arbeitgeber 2001\n", - "arbeitgeber 2002\n", - "arbeitgeber 2003\n", - "arbeitgeber 2004\n", - "arbeitgeber 2005\n", - "arbeitgeber 2006\n", - "arbeitgeber 2007\n", - "arbeitgeber 2008\n", - "arbeitgeber 2010\n", - "handelsztg 1861\n", - "handelsztg 1862\n", - "handelsztg 1863\n", - "handelsztg 1864\n", - "handelsztg 1865\n", - "handelsztg 1866\n", - "handelsztg 1867\n", - "handelsztg 1868\n", - "handelsztg 1869\n", - "handelsztg 1870\n", - "handelsztg 1871\n", - "handelsztg 1872\n", - "handelsztg 1873\n", - "handelsztg 1874\n", - "handelsztg 1875\n", - "handelsztg 1876\n", - "handelsztg 1877\n", - "handelsztg 1878\n", - "handelsztg 1879\n", - "handelsztg 1880\n", - "handelsztg 1881\n", - "handelsztg 1882\n", - "handelsztg 1883\n", - "handelsztg 1884\n", - "handelsztg 1885\n", - "handelsztg 1886\n", - "handelsztg 1890\n", - "handelsztg 1891\n", - "handelsztg 1892\n", - "handelsztg 1893\n", - "handelsztg 1894\n" - ] - }, - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# download the issues of interest for this patch\n", "swa_issues = fetch_issues('canonical-data', True, SWA_TITLES)\n", @@ -578,862 +419,9 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "
\n", - "
\n", - "

Client

\n", - "

Client-0a4729f5-d005-11ee-8242-90e2baa156fc

\n", - " \n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - "
Connection method: Cluster objectCluster type: distributed.LocalCluster
\n", - " Dashboard: http://127.0.0.1:8787/status\n", - "
\n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "

Cluster Info

\n", - "
\n", - "
\n", - "
\n", - "
\n", - "

LocalCluster

\n", - "

37346621

\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - "\n", - " \n", - "
\n", - " Dashboard: http://127.0.0.1:8787/status\n", - " \n", - " Workers: 16\n", - "
\n", - " Total threads: 32\n", - " \n", - " Total memory: 251.79 GiB\n", - "
Status: runningUsing processes: True
\n", - "\n", - "
\n", - " \n", - "

Scheduler Info

\n", - "
\n", - "\n", - "
\n", - "
\n", - "
\n", - "
\n", - "

Scheduler

\n", - "

Scheduler-0a56e181-845a-4c01-ba24-f33361ec05e0

\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " Comm: tcp://127.0.0.1:40417\n", - " \n", - " Workers: 16\n", - "
\n", - " Dashboard: http://127.0.0.1:8787/status\n", - " \n", - " Total threads: 32\n", - "
\n", - " Started: Just now\n", - " \n", - " Total memory: 251.79 GiB\n", - "
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "

Workers

\n", - "
\n", - "\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 0

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:46445\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:46371/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:42875\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-5cw1vcb6\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 1

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:41743\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:43645/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:40005\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-c2hw6qf2\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 2

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:40427\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:37585/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:46235\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-n964b9ka\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 3

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:39089\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:38367/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:38337\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-qxf8qgc3\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 4

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:33831\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:42137/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:44295\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-7danon1m\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 5

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:43713\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:35149/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:37895\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-pp3s1ieg\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 6

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:44305\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:39069/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:38289\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-9y770j8_\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 7

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:44335\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:33063/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:42583\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-q6cu1xjw\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 8

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:34571\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:34545/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:44235\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-0f76q4gs\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 9

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:37737\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:33031/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:42603\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-frtac1bc\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 10

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:44507\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:39469/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:42061\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-86xe1ry_\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 11

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:36779\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:44301/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:43463\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-3trmf8ie\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 12

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:41599\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:44009/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:46141\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-llp8s_gp\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 13

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:38823\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:44141/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:37807\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-tihadj4i\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 14

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:41087\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:33327/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:41891\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-cnt6y6_r\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "

Worker: 15

\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - "\n", - " \n", - "\n", - "
\n", - " Comm: tcp://127.0.0.1:36671\n", - " \n", - " Total threads: 2\n", - "
\n", - " Dashboard: http://127.0.0.1:42903/status\n", - " \n", - " Memory: 15.74 GiB\n", - "
\n", - " Nanny: tcp://127.0.0.1:37259\n", - "
\n", - " Local directory: /tmp/dask-scratch-space/worker-n0apo__o\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "\n", - "
\n", - "
\n", - "\n", - "
\n", - "
\n", - "
\n", - "
\n", - " \n", - "\n", - "
\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "client = Client(n_workers=16, threads_per_worker=2)\n", "client" @@ -1441,7 +429,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1458,7 +446,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1472,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1506,23 +494,20 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Fetching list of newspapers from canonical-data\n", - "canonical-data contains 94 newspapers\n", - "canonical-data contains 473 .bz2 issue files for the provided newspapers ['FedGazDe', 'FedGazFr', 'NZZ']\n", - "canonical-data contains 128930 .bz2 page files for the provided newspapers ['FedGazDe', 'FedGazFr', 'NZZ']\n" - ] - } - ], + "outputs": [], "source": [ "# download the issues of interest for this patch\n", - "uzh_issues, uzh_pages = fetch_files('canonical-data', False, 'both', UZH_TITLES)\n", + "uzh_issues, uzh_pages = fetch_files('canonical-data', False, 'both', UZH_TITLES)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "\n", "# compute the statistics that correspond to this\n", "logger.info(\"computing the canonical statistics on the issues...\")\n", @@ -1548,26 +533,27 @@ "for issue_id, (success, path) in uzh_patched_pages:\n", " title, year, month, day, edition = issue_id.split('-')\n", " \n", - " if success and not nzz_patch_1_manifest.has_title_year_key(title, year):\n", - " current_stats = [d for d in issue_stats if d['np_id']==title and d['year']==year][0]\n", - " # reduce the number of stats to consider at each step\n", - " issue_stats.remove(current_stats)\n", - " # remove unwanted keys from the dict\n", - " del current_stats['np_id']\n", - " del current_stats['year']\n", - "\n", - " add_ok = nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats)\n", - "\n", - " if add_ok:\n", - " specific_issue = [i for i in uzh_issues if i['id']==issue_id]\n", - " assert len(specific_issue) == 1, f\"More than one issue had the exact issue id: {issue_id}\"\n", - " # if patching and addition to manifest was successful, the issue can be copied to the new bucket\n", - " if key in issues_with_patched_pages:\n", - " issues_with_patched_pages['-'.join([title, year])] = specific_issue\n", - " else:\n", - " issues_with_patched_pages['-'.join([title, year])].extend(specific_issue)\n", + " if success:\n", + " if not nzz_patch_1_manifest.has_title_year_key(title, year):\n", + " current_stats = [d for d in issue_stats if d['np_id']==title and d['year']==year][0]\n", + " # reduce the number of stats to consider at each step\n", + " issue_stats.remove(current_stats)\n", + " # remove unwanted keys from the dict\n", + " del current_stats['np_id']\n", + " del current_stats['year']\n", + "\n", + " add_ok = nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats)\n", + "\n", + " # if patching and addition to manifest was successful, the issue can be copied to the new bucket\n", + " specific_issue = [i for i in uzh_issues if i['id']==issue_id]\n", + " print(specific_issue[0]['id'])\n", + " assert len(specific_issue) == 1, f\"More than one issue had the exact issue id: {issue_id}\"\n", + "\n", + " key = '-'.join([title, year])\n", + " if key not in issues_with_patched_pages:\n", + " issues_with_patched_pages[key] = specific_issue\n", " else:\n", - " logger.warning(\"Problem encountered when trying to add %s for %s-%s\", current_stats, title, year)\n", + " issues_with_patched_pages[key].extend(specific_issue)\n", " elif not success:\n", " logger.warning(\"The pages for issue %s were not correctly uploaded\", issue_id)\n", "\n", @@ -1581,7 +567,25 @@ "note = f\"Patching titles {UZH_TITLES}: adding {PROP_NAME} property at page level\"\n", "nzz_patch_1_manifest.append_to_notes(note)\n", "nzz_patch_1_manifest.compute(export_to_git_and_s3 = False)\n", - "nzz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True)" + "nzz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=False)#, push_to_git=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "issues_with_patched_pages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "uzh_patched_pages" ] }, { From 91d59fe64af967310a7df039ed76de5e02dc3c94 Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 21 Feb 2024 13:18:13 +0100 Subject: [PATCH 13/53] Fix several issues with patches, speed-up process for patch 1 --- notebooks/canonical_patches_1_and_6.ipynb | 166 +++++++++++++++------- 1 file changed, 116 insertions(+), 50 deletions(-) diff --git a/notebooks/canonical_patches_1_and_6.ipynb b/notebooks/canonical_patches_1_and_6.ipynb index 1760e0bb..fba395f2 100644 --- a/notebooks/canonical_patches_1_and_6.ipynb +++ b/notebooks/canonical_patches_1_and_6.ipynb @@ -40,7 +40,7 @@ "from impresso_commons.path.path_s3 import fetch_files, list_files, list_newspapers\n", "from impresso_commons.utils.s3 import fixed_s3fs_glob\n", "from impresso_commons.versioning.data_manifest import DataManifest\n", - "from text_importer.importers.core import upload_issues, upload_pages\n", + "from text_importer.importers.core import upload_issues, upload_pages, remove_filelocks\n", "from smart_open import open as smart_open_function\n", "from impresso_commons.versioning.helpers import counts_for_canonical_issue\n", "import dask.bag as db\n", @@ -48,7 +48,10 @@ "import git\n", "from text_importer.utils import init_logger\n", "import copy\n", - "from dask.distributed import Client" + "from dask.distributed import Client\n", + "from filelock import FileLock\n", + "import shutil\n", + "\n" ] }, { @@ -128,14 +131,18 @@ " \n", " os.makedirs(os.path.dirname(filepath), exist_ok =True)\n", "\n", + " # put a file lock to avoid the overwriting of files due to parallelization\n", + " lock = FileLock(filepath + \".lock\", timeout=13)\n", + "\n", " try:\n", - " with smart_open_function(filepath, 'ab') as fout:\n", - " writer = jsonlines.Writer(fout)\n", + " with lock:\n", + " with smart_open_function(filepath, 'ab') as fout:\n", + " writer = jsonlines.Writer(fout)\n", "\n", - " writer.write_all(contents)\n", + " writer.write_all(contents)\n", "\n", - " logger.info(f'Written {len(contents)} {content_type} to {filepath}')\n", - " writer.close()\n", + " logger.info(f'Written {len(contents)} {content_type} to {filepath}')\n", + " writer.close()\n", " except Exception as e:\n", " logger.error(f\"Error for {filepath}\")\n", " logger.exception(e)\n", @@ -178,6 +185,8 @@ "\n", " write_jsonlines_file(filepath, issues, 'issues', failed_log)\n", "\n", + " remove_filelocks(os.path.join(output_dir, newspaper))\n", + "\n", " return upload_issues('-'.join(key), filepath, bucket_name)" ] }, @@ -192,7 +201,8 @@ " pages: list[dict[str, Any]],\n", " output_dir: str,\n", " bucket_name: str,\n", - " failed_log: str | None = None\n", + " failed_log: str | None = None,\n", + " #uploaded_pages = UPLOADED_PAGES,\n", ") -> tuple[str, tuple[bool, str]]:\n", " \"\"\"Compress pages for a given edition in a json file and upload them to s3.\n", "\n", @@ -213,12 +223,37 @@ " filename = f'{key}-pages.jsonl.bz2'\n", " filepath = os.path.join(output_dir, newspaper, f'{newspaper}-{year}', filename)\n", " logger.info(f'Compressing {len(pages)} JSON files into {filepath}')\n", - "\n", + " \n", + " #stat_key = f'{newspaper}-{year}'\n", + " #if stat_key not in uploaded_pages:\n", + " # uploaded_pages[stat_key] = []\n", + " #for p in pages:\n", + " # uploaded_pages[stat_key].append(p['id'])\n", + " \n", " write_jsonlines_file(filepath, pages, 'pages', failed_log)\n", "\n", + " remove_filelocks(os.path.dirname(filepath))\n", + "\n", " return key, (upload_pages(key, filepath, bucket_name))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def to_pairs(pages: list[dict[str, Any]]) -> tuple[str, list[dict[str, Any]]]:\n", + " issues_present = set()\n", + " for page in pages:\n", + " issue_id = '-'.join(page['id'].split('-')[:-1])\n", + " issues_present.add(issue_id) \n", + "\n", + " issues = list(issues_present)\n", + " assert len(issues)==1, \"there should only be one issue\"\n", + " return issues[0], pages" + ] + }, { "cell_type": "code", "execution_count": null, @@ -299,6 +334,21 @@ " " ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def empty_folder(dir_path: str):\n", + " if os.path.exists(dir_path):\n", + " shutil.rmtree(dir_path)\n", + " logger.info(\"Emptied directory at %s\", dir_path)\n", + " os.mkdir(dir_path)\n", + " " + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -321,7 +371,7 @@ "\n", "error_log = '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa_errors.log'\n", "\n", - "init_logger(logger, logging.INFO, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa.log')\n", + "init_logger(logger, logging.DEBUG, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa.log')\n", "logger.info(\"Patching titles %s: adding %s property at issue level\", SWA_TITLES, PROP_NAME)" ] }, @@ -355,6 +405,9 @@ "patched_fields=[PROP_NAME]\n", "schema_path = '/home/piconti/impresso-text-acquisition/text_importer/impresso-schemas/json/versioning/manifest.schema.json'\n", "\n", + "# empty the temp folder before starting processing to prevent duplication of content inside the files.\n", + "empty_folder(temp_dir)\n", + "\n", "swa_patch_6_manifest = DataManifest(\n", " data_stage = 'canonical',\n", " s3_output_bucket = s3_output_bucket,\n", @@ -380,7 +433,15 @@ "outputs": [], "source": [ "# download the issues of interest for this patch\n", - "swa_issues = fetch_issues('canonical-data', True, SWA_TITLES)\n", + "swa_issues, _ = fetch_files('canonical-data', True, type='issues', newspapers_filter=SWA_TITLES)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "\n", "# patch them keeping track of the data that's been modified\n", "yearly_patched_issues = {}\n", @@ -394,7 +455,9 @@ " else:\n", " yearly_patched_issues[key] = [add_property(issue, PROP_NAME, swa_manifest_uri, issue['id'])]\n", " \n", - " swa_patch_6_manifest.add_by_title_year(title, year, counts_for_canonical_issue(issue))\n", + " success= swa_patch_6_manifest.add_by_title_year(title, year, counts_for_canonical_issue(issue))\n", + " if not success:\n", + " print(\"counts not added for %s-%s\", title, year)\n", "\n", "# write and upload the updated issues to s3\n", "for key, issues in yearly_patched_issues.items():\n", @@ -437,6 +500,7 @@ "UZH_TITLES = ['FedGazDe', 'FedGazFr', 'NZZ']\n", "IMPRESSO_IIIF_BASE_URI = \"https://impresso-project.ch/api/proxy/iiif/\"\n", "PROP_NAME = 'iiif_img_base_uri'\n", + "UPLOADED_PAGES = {}\n", "\n", "error_log = '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_1_fedgaz_errors.log'\n", "\n", @@ -467,13 +531,15 @@ "# initialise manifest to keep track of updates\n", "canonical_repo = git.Repo('/home/piconti/impresso-text-acquisition')\n", "s3_input_bucket = 'canonical-data'\n", - "s3_output_bucket = 'canonical-sandbox' #'canonical-staging'\n", + "s3_output_bucket = 'canonical-staging' #'canonical-sandbox'\n", "# previous manifest is not in the output bucket --> provide it as argument\n", "previous_manifest_path = 's3://canonical-staging/canonical_v0-0-2.json' \n", "temp_dir = '/scratch/piconti/impresso/patches_temp'\n", "patched_fields=[PROP_NAME]\n", "schema_path = '/home/piconti/impresso-text-acquisition/text_importer/impresso-schemas/json/versioning/manifest.schema.json'\n", "\n", + "empty_folder(temp_dir)\n", + "\n", "nzz_patch_1_manifest = DataManifest(\n", " data_stage = 'canonical',\n", " s3_output_bucket = s3_output_bucket,\n", @@ -498,8 +564,13 @@ "metadata": {}, "outputs": [], "source": [ + "logger.info(\"Fetiching the page and issues files form S3...\")\n", "# download the issues of interest for this patch\n", - "uzh_issues, uzh_pages = fetch_files('canonical-data', False, 'both', UZH_TITLES)" + "uzh_issues, uzh_pages = fetch_files('canonical-data', False, 'both', UZH_TITLES)\n", + "\n", + "# compute the statistics that correspond to this\n", + "logger.info(\"Computing the canonical statistics on the issues...\")\n", + "stats_from_issues = canonical_stats_from_issue_bag(uzh_issues)" ] }, { @@ -508,29 +579,37 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "# compute the statistics that correspond to this\n", - "logger.info(\"computing the canonical statistics on the issues...\")\n", - "stats_from_issues = canonical_stats_from_issue_bag(uzh_issues)\n", - "\n", - "issue_stats = copy.deepcopy(stats_from_issues)\n", - "\n", + "logger.info(\"Updating the page files and uploading them to s3...\")\n", "# patch the pages and write them back to s3.\n", "uzh_patched_pages = (\n", " uzh_pages\n", - " .map(lambda p: add_property(p, PROP_NAME, uzh_image_base_uri, p['id']))\n", - " .groupby(lambda p: '-'.join(p['id'].split('-')[:-1]))\n", - " .starmap(\n", - " write_upload_pages,\n", - " output_dir=temp_dir,\n", - " bucket_name=s3_output_bucket,\n", - " failed_log=error_log\n", + " .map_partitions(\n", + " lambda pages: [add_property(p, PROP_NAME, uzh_image_base_uri, p['id']) for p in pages]\n", + " )\n", + " .map_partitions(to_pairs)\n", + " .map_partitions(\n", + " lambda issue: write_upload_pages( \n", + " issue[0], issue[1],\n", + " output_dir=temp_dir,\n", + " bucket_name=s3_output_bucket,\n", + " failed_log=error_log,\n", + " )\n", " )\n", - ").compute()\n", + ").compute()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "issue_stats = copy.deepcopy(stats_from_issues)\n", "\n", + "logger.info(\"Done uploading the page files to s3, filling in the manifest...\")\n", "# fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket.\n", "issues_with_patched_pages = {}\n", - "for issue_id, (success, path) in uzh_patched_pages:\n", + "for issue_id, (success, path) in zip(uzh_patched_pages[::2], uzh_patched_pages[1::2]): #uzh_patched_pages:\n", " title, year, month, day, edition = issue_id.split('-')\n", " \n", " if success:\n", @@ -542,11 +621,14 @@ " del current_stats['np_id']\n", " del current_stats['year']\n", "\n", + " #if len(UPLOADED_PAGES['-'.join([title, year])]) != current_stats['pages']:\n", + " # logger.warning(\"Mismatch in the number of pages for %s-%s\", title, year)\n", + " # print(\"!!!! Mismatch in the number of pages for %s-%s\", title, year)\n", " add_ok = nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats)\n", "\n", " # if patching and addition to manifest was successful, the issue can be copied to the new bucket\n", " specific_issue = [i for i in uzh_issues if i['id']==issue_id]\n", - " print(specific_issue[0]['id'])\n", + " \n", " assert len(specific_issue) == 1, f\"More than one issue had the exact issue id: {issue_id}\"\n", "\n", " key = '-'.join([title, year])\n", @@ -557,35 +639,19 @@ " elif not success:\n", " logger.warning(\"The pages for issue %s were not correctly uploaded\", issue_id)\n", "\n", + "logger.info(\"Uploading the issue files to the new bucket\")\n", "# write and upload the issues to the new s3 bucket\n", "for key, issues in issues_with_patched_pages.items():\n", " success, issue_path = write_upload_issues(key.split('-'), issues, temp_dir, s3_output_bucket, error_log)\n", " if not success:\n", " logger.warning(\"The copy of issues %s had a problem\", key)\n", "\n", + "logger.info(\"Finalizing, computing and exporting the manifest\")\n", "# finalize the manifest and export it\n", "note = f\"Patching titles {UZH_TITLES}: adding {PROP_NAME} property at page level\"\n", "nzz_patch_1_manifest.append_to_notes(note)\n", "nzz_patch_1_manifest.compute(export_to_git_and_s3 = False)\n", - "nzz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=False)#, push_to_git=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "issues_with_patched_pages" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "uzh_patched_pages" + "nzz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True)" ] }, { From d2116c9ff067d31c779ff6d40f8d2473374e1da5 Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 21 Feb 2024 18:12:58 +0100 Subject: [PATCH 14/53] Separate processing of fedgaz and nzz --- notebooks/canonical_patches_1_and_6.ipynb | 71 ++-- .../scripts/patching/canonical_patch_1_uzh.py | 396 ++++++++++++++++++ 2 files changed, 436 insertions(+), 31 deletions(-) create mode 100644 text_importer/scripts/patching/canonical_patch_1_uzh.py diff --git a/notebooks/canonical_patches_1_and_6.ipynb b/notebooks/canonical_patches_1_and_6.ipynb index fba395f2..3c7ba096 100644 --- a/notebooks/canonical_patches_1_and_6.ipynb +++ b/notebooks/canonical_patches_1_and_6.ipynb @@ -185,7 +185,12 @@ "\n", " write_jsonlines_file(filepath, issues, 'issues', failed_log)\n", "\n", - " remove_filelocks(os.path.join(output_dir, newspaper))\n", + " if os.path.exists(filepath) and os.path.isfile(filepath):\n", + " # file shsould only be modified once\n", + " logger.warning(\"The file %s already exists, not modifying it.\", filepath)\n", + " return False, filepath\n", + "\n", + " remove_filelocks(os.path.dirname(filepath))\n", "\n", " return upload_issues('-'.join(key), filepath, bucket_name)" ] @@ -224,14 +229,14 @@ " filepath = os.path.join(output_dir, newspaper, f'{newspaper}-{year}', filename)\n", " logger.info(f'Compressing {len(pages)} JSON files into {filepath}')\n", " \n", - " #stat_key = f'{newspaper}-{year}'\n", - " #if stat_key not in uploaded_pages:\n", - " # uploaded_pages[stat_key] = []\n", - " #for p in pages:\n", - " # uploaded_pages[stat_key].append(p['id'])\n", - " \n", + " if os.path.exists(filepath) and os.path.isfile(filepath):\n", + " # file shsould only be modified once\n", + " logger.warning(\"The file %s already exists, not modifying it.\", filepath)\n", + " return key, (False, filepath)\n", + " \n", + " logger.info(\"uploading pages for %s\", key)\n", " write_jsonlines_file(filepath, pages, 'pages', failed_log)\n", - "\n", + " \n", " remove_filelocks(os.path.dirname(filepath))\n", "\n", " return key, (upload_pages(key, filepath, bucket_name))" @@ -243,14 +248,19 @@ "metadata": {}, "outputs": [], "source": [ - "def to_pairs(pages: list[dict[str, Any]]) -> tuple[str, list[dict[str, Any]]]:\n", + "def to_issue_id_pages_pairs(pages: list[dict[str, Any]]) -> tuple[str, list[dict[str, Any]]]:\n", " issues_present = set()\n", " for page in pages:\n", " issue_id = '-'.join(page['id'].split('-')[:-1])\n", " issues_present.add(issue_id) \n", "\n", " issues = list(issues_present)\n", - " assert len(issues)==1, \"there should only be one issue\"\n", + " if len(issues)!=1: \n", + " logger.warning(\"Did not find exactly one issue in the pages: %s, %s\", issues, [p['id'] for p in pages])\n", + " if len(issues)==0:\n", + " return '', pages\n", + " assert len(issues)<=1, \"there should only be one issue\"\n", + "\n", " return issues[0], pages" ] }, @@ -371,7 +381,7 @@ "\n", "error_log = '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa_errors.log'\n", "\n", - "init_logger(logger, logging.DEBUG, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa.log')\n", + "init_logger(logger, logging.INFO, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_6_swa.log')\n", "logger.info(\"Patching titles %s: adding %s property at issue level\", SWA_TITLES, PROP_NAME)" ] }, @@ -497,14 +507,14 @@ "outputs": [], "source": [ "# initialize values for patch\n", - "UZH_TITLES = ['FedGazDe', 'FedGazFr', 'NZZ']\n", + "UZH_TITLES = ['FedGazDe', 'FedGazFr'] #, 'NZZ']\n", "IMPRESSO_IIIF_BASE_URI = \"https://impresso-project.ch/api/proxy/iiif/\"\n", "PROP_NAME = 'iiif_img_base_uri'\n", "UPLOADED_PAGES = {}\n", "\n", "error_log = '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_1_fedgaz_errors.log'\n", "\n", - "init_logger(logger, logging.INFO, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_1_fedgaz_nzz.log')\n", + "init_logger(logger, logging.INFO, '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_1_fedgaz.log')\n", "logger.info(\"Patching titles %s: adding %s property at page level\", UZH_TITLES, PROP_NAME)" ] }, @@ -540,7 +550,7 @@ "\n", "empty_folder(temp_dir)\n", "\n", - "nzz_patch_1_manifest = DataManifest(\n", + "fedgaz_patch_1_manifest = DataManifest(\n", " data_stage = 'canonical',\n", " s3_output_bucket = s3_output_bucket,\n", " s3_input_bucket = s3_input_bucket,\n", @@ -564,13 +574,13 @@ "metadata": {}, "outputs": [], "source": [ - "logger.info(\"Fetiching the page and issues files form S3...\")\n", + "logger.info(\"Fetching the page and issues files from S3...\")\n", "# download the issues of interest for this patch\n", - "uzh_issues, uzh_pages = fetch_files('canonical-data', False, 'both', UZH_TITLES)\n", + "fedgaz_issues, fedgaz_pages = fetch_files('canonical-data', False, 'both', UZH_TITLES)\n", "\n", "# compute the statistics that correspond to this\n", "logger.info(\"Computing the canonical statistics on the issues...\")\n", - "stats_from_issues = canonical_stats_from_issue_bag(uzh_issues)" + "stats_from_issues = canonical_stats_from_issue_bag(fedgaz_issues)" ] }, { @@ -581,12 +591,12 @@ "source": [ "logger.info(\"Updating the page files and uploading them to s3...\")\n", "# patch the pages and write them back to s3.\n", - "uzh_patched_pages = (\n", - " uzh_pages\n", + "fedgaz_patched_pages = (\n", + " fedgaz_pages\n", " .map_partitions(\n", " lambda pages: [add_property(p, PROP_NAME, uzh_image_base_uri, p['id']) for p in pages]\n", " )\n", - " .map_partitions(to_pairs)\n", + " .map_partitions(to_issue_id_pages_pairs)\n", " .map_partitions(\n", " lambda issue: write_upload_pages( \n", " issue[0], issue[1],\n", @@ -609,11 +619,13 @@ "logger.info(\"Done uploading the page files to s3, filling in the manifest...\")\n", "# fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket.\n", "issues_with_patched_pages = {}\n", - "for issue_id, (success, path) in zip(uzh_patched_pages[::2], uzh_patched_pages[1::2]): #uzh_patched_pages:\n", + "for issue_id, (success, path) in zip(fedgaz_patched_pages[::2], fedgaz_patched_pages[1::2]):\n", " title, year, month, day, edition = issue_id.split('-')\n", " \n", " if success:\n", - " if not nzz_patch_1_manifest.has_title_year_key(title, year):\n", + " logger.info(\"Processing outputs to add to manifest for %s\", issue_id)\n", + " if not fedgaz_patch_1_manifest.has_title_year_key(title, year):\n", + " logger.info(\"Adding stats for %s-%s to manifest\", title, year)\n", " current_stats = [d for d in issue_stats if d['np_id']==title and d['year']==year][0]\n", " # reduce the number of stats to consider at each step\n", " issue_stats.remove(current_stats)\n", @@ -621,13 +633,10 @@ " del current_stats['np_id']\n", " del current_stats['year']\n", "\n", - " #if len(UPLOADED_PAGES['-'.join([title, year])]) != current_stats['pages']:\n", - " # logger.warning(\"Mismatch in the number of pages for %s-%s\", title, year)\n", - " # print(\"!!!! Mismatch in the number of pages for %s-%s\", title, year)\n", - " add_ok = nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats)\n", + " add_ok = fedgaz_patch_1_manifest.replace_by_title_year(title, year, current_stats)\n", "\n", " # if patching and addition to manifest was successful, the issue can be copied to the new bucket\n", - " specific_issue = [i for i in uzh_issues if i['id']==issue_id]\n", + " specific_issue = [i for i in fedgaz_issues if i['id']==issue_id]\n", " \n", " assert len(specific_issue) == 1, f\"More than one issue had the exact issue id: {issue_id}\"\n", "\n", @@ -648,10 +657,10 @@ "\n", "logger.info(\"Finalizing, computing and exporting the manifest\")\n", "# finalize the manifest and export it\n", - "note = f\"Patching titles {UZH_TITLES}: adding {PROP_NAME} property at page level\"\n", - "nzz_patch_1_manifest.append_to_notes(note)\n", - "nzz_patch_1_manifest.compute(export_to_git_and_s3 = False)\n", - "nzz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True)" + "note = f\"Patching titles {UZH_TITLES}: adding {PROP_NAME} property at page level.\"\n", + "fedgaz_patch_1_manifest.append_to_notes(note)\n", + "fedgaz_patch_1_manifest.compute(export_to_git_and_s3 = False)\n", + "fedgaz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True)" ] }, { diff --git a/text_importer/scripts/patching/canonical_patch_1_uzh.py b/text_importer/scripts/patching/canonical_patch_1_uzh.py new file mode 100644 index 00000000..0238d278 --- /dev/null +++ b/text_importer/scripts/patching/canonical_patch_1_uzh.py @@ -0,0 +1,396 @@ +"""Command-line script to perform the patch #1 on the UZH canonical data (FedGaz, NZZ). + +Usage: + canonical_patch_1_uzh.py --input-bucket= --output-bucket= --canonical-repo-path= --prev-manifest-path= --temp-dir= --log-file= --error-log= + +Options: + +--input-bucket= S3 input bucket. +--output-bucket= S3 output bucket. +--canonical-repo-path= Path to the local impresso-text-acquisition git repository. +--prev-manifest-path= S3 path of the previous version of the canonical manifest. +--temp-dir= Temporary directory to write files in. +--log-file= Path to log file. +--error-log= Path to error log file. +""" + +import os +import boto3 +import json +import logging +import jsonlines +from impresso_commons.utils import s3 +from impresso_commons.path.path_s3 import fetch_files, list_files, list_newspapers +from impresso_commons.utils.s3 import fixed_s3fs_glob +from impresso_commons.versioning.data_manifest import DataManifest +from text_importer.importers.core import upload_issues, upload_pages +from smart_open import open as smart_open_function +from impresso_commons.versioning.helpers import counts_for_canonical_issue +import dask.bag as db +from typing import Any, Callable +import git +from text_importer.utils import init_logger +import copy +from dask.distributed import Client +from docopt import docopt + +IMPRESSO_STORAGEOPT = s3.get_storage_options() +UZH_TITLES = ['FedGazDe', 'FedGazFr', 'NZZ'] +IMPRESSO_IIIF_BASE_URI = "https://impresso-project.ch/api/proxy/iiif/" +PROP_NAME = 'iiif_img_base_uri' + +logger = logging.getLogger() + +def add_property(object_dict: dict[str, Any], prop_name: str, prop_function: Callable[[str], str], function_input: str): + object_dict[prop_name] = prop_function(function_input) + logger.debug("%s -> Added property %s: %s", object_dict['id'], prop_name, object_dict[prop_name]) + return object_dict + +def empty_folder(dir_path: str): + if os.path.exists(dir_path): + shutil.rmtree(dir_path) + logger.info("Emptied directory at %s", dir_path) + os.mkdir(dir_path) + +def write_error( + thing_id: str, + origin_function: str, + error: Exception, + failed_log: str +) -> None: + """Write the given error of a failed import to the `failed_log` file. + + Args: + thing (NewspaperIssue | NewspaperPage | IssueDir): Object for which + the error occurred. + error (Exception): Error that occurred and should be logged. + failed_log (str): Path to log file for failed imports. + """ + note = ( + f"Error in {origin_function} for {thing_id}: {error}" + ) + logger.exception(note) + with open(failed_log, "a+") as f: + f.write(note + "\n") + +def write_jsonlines_file(filepath: str, contents: str | list[str], content_type: str, failed_log: str | None = None) -> None: + + os.makedirs(os.path.dirname(filepath), exist_ok =True) + + # put a file lock to avoid the overwriting of files due to parallelization + lock = FileLock(filepath + ".lock", timeout=13) + + try: + with lock: + with smart_open_function(filepath, 'ab') as fout: + writer = jsonlines.Writer(fout) + + writer.write_all(contents) + + logger.info(f'Written {len(contents)} {content_type} to {filepath}') + writer.close() + except Exception as e: + logger.error(f"Error for {filepath}") + logger.exception(e) + if failed_log is not None: + write_error(os.path.basename(filepath), 'write_jsonlines_file()', e, failed_log) + + +def write_upload_issues( + key: tuple[str, str], + issues: list[dict[str, Any]], + output_dir: str, + bucket_name: str, + failed_log: str | None = None +) -> tuple[str, str]: + """Compress issues for a Journal-year in a json file and upload them to s3. + + The compressed ``.bz2`` output file is a JSON-line file, where each line + corresponds to an individual issue document in the canonical format. + + Args: + key (str): Hyphen separated Newspaper ID and year of input issues, e.g. `GDL-1900`. + issues (list[dict[str, Any]]): A list of issues as dicts. + output_dir (str): Local output directory. + bucket_name (str): Name of S3 bucket where to upload the file. + + Returns: + Tuple[str, str]: Label following the template `-` and + the path to the the compressed `.bz2` file. + """ + newspaper, year = key + filename = f'{newspaper}-{year}-issues.jsonl.bz2' + filepath = os.path.join(output_dir, newspaper, filename) + logger.info(f'Compressing {len(issues)} JSON files into {filepath}') + + write_jsonlines_file(filepath, issues, 'issues', failed_log) + + if os.path.exists(filepath) and os.path.isfile(filepath): + # file shsould only be modified once + logger.warning("The file %s already exists, not modifying it.", filepath) + return False, filepath + + remove_filelocks(os.path.join(output_dir, newspaper)) + + return upload_issues('-'.join(key), filepath, bucket_name) + +def write_upload_pages( + key: str, + pages: list[dict[str, Any]], + output_dir: str, + bucket_name: str, + failed_log: str | None = None, + #uploaded_pages = UPLOADED_PAGES, +) -> tuple[str, tuple[bool, str]]: + """Compress pages for a given edition in a json file and upload them to s3. + + The compressed ``.bz2`` output file is a JSON-line file, where each line + corresponds to an individual page document in the canonical format. + + Args: + key (str): Canonical ID of the newspaper issue (e.g. GDL-1900-01-02-a). + pages (list[dict[str, Any]]): The list of pages for the provided key. + output_dir (str): Local output directory. + bucket_name (str): Name of S3 bucket where to upload the file. + + Returns: + Tuple[str, str]: Label following the template `-` and + the path to the the compressed `.bz2` file. + """ + newspaper, year, month, day, edition = key.split('-') + filename = f'{key}-pages.jsonl.bz2' + filepath = os.path.join(output_dir, newspaper, f'{newspaper}-{year}', filename) + logger.info(f'Compressing {len(pages)} JSON files into {filepath}') + + if os.path.exists(filepath) and os.path.isfile(filepath): + # file shsould only be modified once + logger.warning("The file %s already exists, not modifying it.", filepath) + return key, (False, filepath) + + logger.info("uploading pages for %s", key) + write_jsonlines_file(filepath, pages, 'pages', failed_log) + + remove_filelocks(os.path.dirname(filepath)) + + return key, (upload_pages(key, filepath, bucket_name)) + +# adapted from https://github.com/impresso/impresso-data-sanitycheck/blob/master/sanity_check/contents/stats.py#L241 +def canonical_stats_from_issue_bag(fetched_issues: db.core.Bag) -> list[dict[str, Any]]: + """Computes number of issues and pages per newspaper from canonical data in s3. + + :param str s3_canonical_bucket: S3 bucket with canonical data. + :return: A pandas DataFrame with newspaper ID as the index and columns `n_issues`, `n_pages`. + :rtype: pd.DataFrame + + """ + pages_count_df = ( + fetched_issues.map( + lambda i: { + "np_id": i["id"].split('-')[0], + "year":i["id"].split('-')[1], + "id": i['id'], + "issue_id": i['id'], + "n_pages": len(set(i['pp'])), + "n_content_items": len(i['i']), + "n_images": len([item for item in i['i'] if item['m']['tp']=='image']) + } + ) + .to_dataframe(meta={'np_id': str, 'year': str, + 'id': str, 'issue_id': str, + "n_pages": int, 'n_images': int, + 'n_content_items': int}) + .set_index('id') + .persist() + ) + + # cum the counts for all values collected + aggregated_df = (pages_count_df + .groupby(by=['np_id', 'year']) + .agg({"n_pages": sum, 'issue_id': 'count', 'n_content_items': sum, 'n_images': sum}) + .rename(columns={'issue_id': 'issues', 'n_pages': 'pages', + 'n_content_items': 'content_items_out', 'n_images':'images'}) + .reset_index() + ) + + # return as a list of dicts + return aggregated_df.to_bag(format='dict').compute() + +# define patch function +def uzh_image_base_uri(page_id: str, impresso_iiif: str = IMPRESSO_IIIF_BASE_URI) -> str: + """ + https://impresso-project.ch/api/proxy/iiif/[page canonical ID] + """ + return os.path.join(impresso_iiif, page_id) + + +def to_issue_id_pages_pairs(pages: list[dict[str, Any]]) -> tuple[str, list[dict[str, Any]]]: + issues_present = set() + for page in pages: + issue_id = '-'.join(page['id'].split('-')[:-1]) + issues_present.add(issue_id) + + issues = list(issues_present) + if len(issues)!=1: + logger.warning("Did not find exactly one issue in the pages: %s, %s", issues, [p['id'] for p in pages]) + if len(issues)==0: + return '', pages + assert len(issues)<=1, "there should only be one issue" + + return issues[0], pages + + +def to_issue_id_pages_dict(pages: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]: + + issues_present = {} + for page in pages: + issue_id = '-'.join(page['id'].split('-')[:-1]) + if issue_id not in issues_present: + issues_present[issue_id] = [page] + else: + issues_present[issue_id].append(page) + + if len(issues_present)!=1: + logger.warning("Did not find exactly one issue in the pages; issue(s): %s", issues_present) + print("Did not find exactly one issue in the pages; issue(s): %s", issues_present) + if len(issues_present) >1: + pairs = [((k, [p['id'] for p in ps]) for k,ps in issues_present)] + print(f"Here are the specific contents of the pages: {pairs}") + + return issues_present + + +def nzz_write_upload_pages( + issues_to_pages: dict[str, list[dict[str, Any]]], + output_dir: str, + bucket_name: str, + failed_log: str | None = None, +) -> tuple[str, tuple[bool, str]]: + + if len(issues_to_pages) == 0: + return '', (False, '') + + upload_results = [] + for issue_id, pages in issues_to_pages.items(): + upload_results.append(write_upload_pages(issue_id, pages, output_dir, bucket_name, failed_log)) + + return upload_results + + +def main(): + arguments = docopt(__doc__) + s3_input_bucket = arguments["--input-bucket"] + s3_output_bucket = arguments["--output-bucket"] + canonical_repo_path = arguments["--canonical-repo-path"] + previous_manifest_path = arguments["--prev-manifest-path"] + temp_dir = arguments["--temp-dir"] + log_file = arguments["--log-file"] + error_log = arguments["--error-log"] + + # initialize values for patch + UZH_TITLES = ['NZZ'] + IMPRESSO_IIIF_BASE_URI = "https://impresso-project.ch/api/proxy/iiif/" + PROP_NAME = 'iiif_img_base_uri' + patched_fields=[PROP_NAME] + canonical_repo = git.Repo(canonical_repo_path) + + schema_path = f'{canonical_repo_path}/text_importer/impresso-schemas/json/versioning/manifest.schema.json' + + init_logger(logger, logging.INFO, log_file) + logger.info("Patching titles %s: adding %s property at page level", UZH_TITLES, PROP_NAME) + + # empty the temp folder before starting processing to prevent duplication of content inside the files. + empty_folder(temp_dir) + + # initialise manifest to keep track of updates + nzz_patch_1_manifest = DataManifest( + data_stage = 'canonical', + s3_output_bucket = s3_output_bucket, + s3_input_bucket = s3_input_bucket, + git_repo = canonical_repo, + temp_dir = temp_dir, + patched_fields=patched_fields, + previous_mft_path = previous_manifest_path + ) + + logger.info("Fetching the page and issues files from S3...") + # download the issues of interest for this patch + nzz_issues, nzz_pages = fetch_files('canonical-data', False, 'both', UZH_TITLES) + + # compute the statistics that correspond to this + logger.info("Computing the canonical statistics on the issues...") + nzz_stats_from_issues = canonical_stats_from_issue_bag(nzz_issues) + + logger.info("Updating the page files and uploading them to s3...") + # patch the pages and write them back to s3. + nzz_patched_pages = ( + nzz_pages + .map_partitions( + lambda pages: [add_property(p, PROP_NAME, uzh_image_base_uri, p['id']) for p in pages] + ) + .map_partitions(to_issue_id_pages_dict) + .map_partitions( + lambda issue_to_pages: nzz_write_upload_pages( + issue_to_pages, + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, + ) + ) + ).compute() + + # free the memory allocated + del nzz_pages + + logger.info("Done uploading the page files to s3, filling in the manifest...") + + + issue_stats = copy.deepcopy(nzz_stats_from_issues) + + # fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket. + issues_with_patched_pages = {} + for issue_id, (success, path) in zip(uzh_patched_pages[::2], uzh_patched_pages[1::2]) + title, year, month, day, edition = issue_id.split('-') + + if success: + logger.info("Processing outputs to add to manifest for %s", issue_id) + if not nzz_patch_1_manifest.has_title_year_key(title, year): + logger.info("Adding stats for %s-%s to manifest", title, year) + current_stats = [d for d in issue_stats if d['np_id']==title and d['year']==year][0] + # reduce the number of stats to consider at each step + issue_stats.remove(current_stats) + # remove unwanted keys from the dict + del current_stats['np_id'] + del current_stats['year'] + + add_ok = nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats) + + # if patching and addition to manifest was successful, the issue can be copied to the new bucket + specific_issue = [i for i in nzz_issues if i['id']==issue_id] + + assert len(specific_issue) == 1, f"More or less than one issue had the exact issue id: {issue_id}" + + key = '-'.join([title, year]) + if key not in issues_with_patched_pages: + issues_with_patched_pages[key] = specific_issue + else: + issues_with_patched_pages[key].extend(specific_issue) + elif not success: + logger.warning("The pages for issue %s were not correctly uploaded", issue_id) + + logger.info("Uploading the issue files to the new bucket") + # write and upload the issues to the new s3 bucket + for key, issues in issues_with_patched_pages.items(): + success, issue_path = write_upload_issues(key.split('-'), issues, temp_dir, s3_output_bucket, error_log) + if not success: + logger.warning("The copy of issues %s had a problem", key) + + logger.info("Finalizing, computing and exporting the manifest") + # finalize the manifest and export it + note = f"Patching titles {UZH_TITLES}: adding {PROP_NAME} property at page level" + nzz_patch_1_manifest.append_to_notes(note) + nzz_patch_1_manifest.compute(export_to_git_and_s3 = False) + nzz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True) + + +if __name__ == "__main__": + main() From e202a79e1c3c1dec67561c8335076fc442017731 Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 21 Feb 2024 18:40:37 +0100 Subject: [PATCH 15/53] minor change --- notebooks/canonical_patches_1_and_6.ipynb | 70 ++++++++++++------- .../scripts/patching/canonical_patch_1_uzh.py | 21 +++--- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/notebooks/canonical_patches_1_and_6.ipynb b/notebooks/canonical_patches_1_and_6.ipynb index 3c7ba096..e6243956 100644 --- a/notebooks/canonical_patches_1_and_6.ipynb +++ b/notebooks/canonical_patches_1_and_6.ipynb @@ -27,9 +27,18 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/piconti/.conda/envs/patches/lib/python3.11/site-packages/python_jsonschema_objects/__init__.py:60: UserWarning: Schema version http://json-schema.org/draft-06/schema# not recognized. Some keywords and features may not be supported.\n", + " warnings.warn(\n" + ] + } + ], "source": [ "import os\n", "import boto3\n", @@ -56,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -65,7 +74,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -81,7 +90,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -93,7 +102,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -123,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -152,7 +161,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -197,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -244,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -266,7 +275,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -314,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -346,7 +355,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -502,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -520,7 +529,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -534,7 +543,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -617,8 +626,11 @@ "issue_stats = copy.deepcopy(stats_from_issues)\n", "\n", "logger.info(\"Done uploading the page files to s3, filling in the manifest...\")\n", + "\n", + "# keep track of the issues, default dict allows to prevent checking if key exists in it\n", + "issues_with_patched_pages = defaultdict(list)\n", + "\n", "# fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket.\n", - "issues_with_patched_pages = {}\n", "for issue_id, (success, path) in zip(fedgaz_patched_pages[::2], fedgaz_patched_pages[1::2]):\n", " title, year, month, day, edition = issue_id.split('-')\n", " \n", @@ -638,13 +650,16 @@ " # if patching and addition to manifest was successful, the issue can be copied to the new bucket\n", " specific_issue = [i for i in fedgaz_issues if i['id']==issue_id]\n", " \n", - " assert len(specific_issue) == 1, f\"More than one issue had the exact issue id: {issue_id}\"\n", + " # ensure there is only one issue.\n", + " if len(specific_issue) != 1:\n", + " logger.warning(\"Multiple issues had the exact id %s: %s\", issue_id, specific_issue)\n", + "\n", + " # remove issue from the list of possible issues to reduce the search time\n", + " fedgaz_issues.remove(specific_issue)\n", "\n", - " key = '-'.join([title, year])\n", - " if key not in issues_with_patched_pages:\n", - " issues_with_patched_pages[key] = specific_issue\n", - " else:\n", - " issues_with_patched_pages[key].extend(specific_issue)\n", + " # add to list of issues for this specific year\n", + " issues_with_patched_pages['-'.join([title, year])].extend(specific_issue)\n", + " \n", " elif not success:\n", " logger.warning(\"The pages for issue %s were not correctly uploaded\", issue_id)\n", "\n", @@ -663,6 +678,13 @@ "fedgaz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, diff --git a/text_importer/scripts/patching/canonical_patch_1_uzh.py b/text_importer/scripts/patching/canonical_patch_1_uzh.py index 0238d278..19575220 100644 --- a/text_importer/scripts/patching/canonical_patch_1_uzh.py +++ b/text_importer/scripts/patching/canonical_patch_1_uzh.py @@ -346,8 +346,10 @@ def main(): issue_stats = copy.deepcopy(nzz_stats_from_issues) + # keep track of the issues, default dict allows to prevent checking if key exists in it + issues_with_patched_pages = defaultdict(list) + # fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket. - issues_with_patched_pages = {} for issue_id, (success, path) in zip(uzh_patched_pages[::2], uzh_patched_pages[1::2]) title, year, month, day, edition = issue_id.split('-') @@ -366,14 +368,15 @@ def main(): # if patching and addition to manifest was successful, the issue can be copied to the new bucket specific_issue = [i for i in nzz_issues if i['id']==issue_id] - - assert len(specific_issue) == 1, f"More or less than one issue had the exact issue id: {issue_id}" - - key = '-'.join([title, year]) - if key not in issues_with_patched_pages: - issues_with_patched_pages[key] = specific_issue - else: - issues_with_patched_pages[key].extend(specific_issue) + # ensure there is only one issue. + if len(specific_issue) != 1: + logger.warning("Multiple issues had the exact id %s: %s", issue_id, specific_issue) + + # remove issue from the list of possible issues to reduce the search time + nzz_issues.remove(specific_issue) + + # add to list of issues for this specific year + issues_with_patched_pages['-'.join([title, year])].extend(specific_issue) elif not success: logger.warning("The pages for issue %s were not correctly uploaded", issue_id) From f68a131f441f976cb95e7d949f74ca33d4de971b Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 21 Feb 2024 21:00:34 +0100 Subject: [PATCH 16/53] slightly modify outputs writing approach --- notebooks/canonical_patches_1_and_6.ipynb | 114 +++++++++--------- .../scripts/patching/canonical_patch_1_uzh.py | 56 ++++++--- 2 files changed, 98 insertions(+), 72 deletions(-) diff --git a/notebooks/canonical_patches_1_and_6.ipynb b/notebooks/canonical_patches_1_and_6.ipynb index e6243956..31f11b86 100644 --- a/notebooks/canonical_patches_1_and_6.ipynb +++ b/notebooks/canonical_patches_1_and_6.ipynb @@ -27,18 +27,9 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/piconti/.conda/envs/patches/lib/python3.11/site-packages/python_jsonschema_objects/__init__.py:60: UserWarning: Schema version http://json-schema.org/draft-06/schema# not recognized. Some keywords and features may not be supported.\n", - " warnings.warn(\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "import os\n", "import boto3\n", @@ -60,12 +51,12 @@ "from dask.distributed import Client\n", "from filelock import FileLock\n", "import shutil\n", - "\n" + "from collections import defaultdict\n" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -74,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -90,7 +81,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -102,7 +93,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -132,7 +123,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -161,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -192,13 +183,12 @@ " filepath = os.path.join(output_dir, newspaper, filename)\n", " logger.info(f'Compressing {len(issues)} JSON files into {filepath}')\n", "\n", - " write_jsonlines_file(filepath, issues, 'issues', failed_log)\n", - "\n", " if os.path.exists(filepath) and os.path.isfile(filepath):\n", " # file shsould only be modified once\n", " logger.warning(\"The file %s already exists, not modifying it.\", filepath)\n", " return False, filepath\n", "\n", + " write_jsonlines_file(filepath, issues, 'issues', failed_log)\n", " remove_filelocks(os.path.dirname(filepath))\n", "\n", " return upload_issues('-'.join(key), filepath, bucket_name)" @@ -206,7 +196,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -253,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -275,7 +265,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -323,7 +313,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -355,7 +345,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -368,6 +358,28 @@ " " ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def title_year_pair_to_issues(issues: list[dict[str, Any]]) -> tuple[tuple[str, str], list[dict[str, Any]]]:\n", + " keys_present = set()\n", + " for issue in issues:\n", + " title, year = issue['id'].split('-')[:2]\n", + " keys_present.add((title, year)) \n", + "\n", + " keys = list(keys_present)\n", + " if len(keys)!=1: \n", + " logger.warning(\"Did not find exactly one key. Keys: %s\", keys)\n", + " if len(keys)==0:\n", + " return '', issues\n", + " assert len(keys)>=1, \"there should only be one key\"\n", + "\n", + " return keys[0], issues" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -511,7 +523,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -529,7 +541,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -543,7 +555,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -623,6 +635,8 @@ "metadata": {}, "outputs": [], "source": [ + "final_output_path = '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_outputs_fedgaz.txt'\n", + "\n", "issue_stats = copy.deepcopy(stats_from_issues)\n", "\n", "logger.info(\"Done uploading the page files to s3, filling in the manifest...\")\n", @@ -633,6 +647,10 @@ "# fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket.\n", "for issue_id, (success, path) in zip(fedgaz_patched_pages[::2], fedgaz_patched_pages[1::2]):\n", " title, year, month, day, edition = issue_id.split('-')\n", + "\n", + " # write to file to track potential missing data.\n", + " with open(final_output_path, \"a\", encoding=\"utf-8\") as outfile:\n", + " outfile.write(f\"{issue_id}: {success}, {path} \\n\")\n", " \n", " if success:\n", " logger.info(\"Processing outputs to add to manifest for %s\", issue_id)\n", @@ -645,30 +663,25 @@ " del current_stats['np_id']\n", " del current_stats['year']\n", "\n", - " add_ok = fedgaz_patch_1_manifest.replace_by_title_year(title, year, current_stats)\n", - "\n", - " # if patching and addition to manifest was successful, the issue can be copied to the new bucket\n", - " specific_issue = [i for i in fedgaz_issues if i['id']==issue_id]\n", - " \n", - " # ensure there is only one issue.\n", - " if len(specific_issue) != 1:\n", - " logger.warning(\"Multiple issues had the exact id %s: %s\", issue_id, specific_issue)\n", - "\n", - " # remove issue from the list of possible issues to reduce the search time\n", - " fedgaz_issues.remove(specific_issue)\n", - "\n", - " # add to list of issues for this specific year\n", - " issues_with_patched_pages['-'.join([title, year])].extend(specific_issue)\n", + " fedgaz_patch_1_manifest.replace_by_title_year(title, year, current_stats)\n", " \n", " elif not success:\n", " logger.warning(\"The pages for issue %s were not correctly uploaded\", issue_id)\n", "\n", "logger.info(\"Uploading the issue files to the new bucket\")\n", "# write and upload the issues to the new s3 bucket\n", - "for key, issues in issues_with_patched_pages.items():\n", - " success, issue_path = write_upload_issues(key.split('-'), issues, temp_dir, s3_output_bucket, error_log)\n", - " if not success:\n", - " logger.warning(\"The copy of issues %s had a problem\", key)\n", + "yearly_issue_files = (\n", + " fedgaz_issues\n", + " .map_partitions(lambda issues: [i for i in issues])\n", + " .map_partitions(title_year_pair_to_issues)\n", + " .map_partitions(lambda issues: write_upload_issues(\n", + " issues[0], issues[1],\n", + " output_dir=temp_dir,\n", + " bucket_name= s3_output_bucket,\n", + " failed_log=error_log,\n", + " )\n", + " )\n", + ").compute()\n", "\n", "logger.info(\"Finalizing, computing and exporting the manifest\")\n", "# finalize the manifest and export it\n", @@ -678,13 +691,6 @@ "fedgaz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True)" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, diff --git a/text_importer/scripts/patching/canonical_patch_1_uzh.py b/text_importer/scripts/patching/canonical_patch_1_uzh.py index 19575220..372ffced 100644 --- a/text_importer/scripts/patching/canonical_patch_1_uzh.py +++ b/text_importer/scripts/patching/canonical_patch_1_uzh.py @@ -1,7 +1,7 @@ """Command-line script to perform the patch #1 on the UZH canonical data (FedGaz, NZZ). Usage: - canonical_patch_1_uzh.py --input-bucket= --output-bucket= --canonical-repo-path= --prev-manifest-path= --temp-dir= --log-file= --error-log= + canonical_patch_1_uzh.py --input-bucket= --output-bucket= --canonical-repo-path= --prev-manifest-path= --temp-dir= --log-file= --error-log= --patch-outputs-filename= Options: @@ -12,6 +12,7 @@ --temp-dir= Temporary directory to write files in. --log-file= Path to log file. --error-log= Path to error log file. +--patch-outputs-filename= Filename of the .txt file containing the output of the patches. """ import os @@ -33,6 +34,7 @@ import copy from dask.distributed import Client from docopt import docopt +from collections import defaultdict IMPRESSO_STORAGEOPT = s3.get_storage_options() UZH_TITLES = ['FedGazDe', 'FedGazFr', 'NZZ'] @@ -258,6 +260,21 @@ def to_issue_id_pages_dict(pages: list[dict[str, Any]]) -> dict[str, list[dict[s return issues_present +def title_year_pair_to_issues(issues: list[dict[str, Any]]) -> tuple[tuple[str, str], list[dict[str, Any]]]: + keys_present = set() + for issue in issues: + title, year = issue['id'].split('-')[:2] + keys_present.add((title, year)) + + keys = list(keys_present) + if len(keys)!=1: + logger.warning("Did not find exactly one key. Keys: %s", keys) + if len(keys)==0: + return '', issues + assert len(keys)>=1, "there should only be one key" + + return keys[0], issues + def nzz_write_upload_pages( issues_to_pages: dict[str, list[dict[str, Any]]], @@ -285,6 +302,7 @@ def main(): temp_dir = arguments["--temp-dir"] log_file = arguments["--log-file"] error_log = arguments["--error-log"] + patch_outputs = arguments["--patch-outputs-filename"] # initialize values for patch UZH_TITLES = ['NZZ'] @@ -294,6 +312,7 @@ def main(): canonical_repo = git.Repo(canonical_repo_path) schema_path = f'{canonical_repo_path}/text_importer/impresso-schemas/json/versioning/manifest.schema.json' + final_patches_output_path = os.path.join(os.path.dirname(log_file), patch_outputs) init_logger(logger, logging.INFO, log_file) logger.info("Patching titles %s: adding %s property at page level", UZH_TITLES, PROP_NAME) @@ -353,8 +372,11 @@ def main(): for issue_id, (success, path) in zip(uzh_patched_pages[::2], uzh_patched_pages[1::2]) title, year, month, day, edition = issue_id.split('-') + # write to file to track potential missing data. + with open(final_patches_output_path, "a", encoding="utf-8") as outfile: + outfile.write(f"{issue_id}: {success}, {path} \n") + if success: - logger.info("Processing outputs to add to manifest for %s", issue_id) if not nzz_patch_1_manifest.has_title_year_key(title, year): logger.info("Adding stats for %s-%s to manifest", title, year) current_stats = [d for d in issue_stats if d['np_id']==title and d['year']==year][0] @@ -364,28 +386,26 @@ def main(): del current_stats['np_id'] del current_stats['year'] - add_ok = nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats) - - # if patching and addition to manifest was successful, the issue can be copied to the new bucket - specific_issue = [i for i in nzz_issues if i['id']==issue_id] - # ensure there is only one issue. - if len(specific_issue) != 1: - logger.warning("Multiple issues had the exact id %s: %s", issue_id, specific_issue) + nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats) - # remove issue from the list of possible issues to reduce the search time - nzz_issues.remove(specific_issue) - - # add to list of issues for this specific year - issues_with_patched_pages['-'.join([title, year])].extend(specific_issue) elif not success: logger.warning("The pages for issue %s were not correctly uploaded", issue_id) logger.info("Uploading the issue files to the new bucket") # write and upload the issues to the new s3 bucket - for key, issues in issues_with_patched_pages.items(): - success, issue_path = write_upload_issues(key.split('-'), issues, temp_dir, s3_output_bucket, error_log) - if not success: - logger.warning("The copy of issues %s had a problem", key) + yearly_issue_files = ( + nzz_issues + .map_partitions(lambda issues: [i for i in issues]) + .map_partitions(title_year_pair_to_issues) + .map_partitions(lambda issues: write_upload_issues( + issues[0], issues[1], + output_dir=temp_dir, + bucket_name= s3_output_bucket, + failed_log=error_log, + ) + ) + ).compute() + logger.info("Finalizing, computing and exporting the manifest") # finalize the manifest and export it From c9d486503d331943717da87db3fa4d48d9e5fb7c Mon Sep 17 00:00:00 2001 From: piconti Date: Thu, 22 Feb 2024 09:49:30 +0100 Subject: [PATCH 17/53] Catching filelock removal errors and logging --- text_importer/importers/core.py | 7 +++++-- text_importer/scripts/patching/canonical_patch_1_uzh.py | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/text_importer/importers/core.py b/text_importer/importers/core.py index 6a2aeb6f..d21b61ec 100644 --- a/text_importer/importers/core.py +++ b/text_importer/importers/core.py @@ -571,5 +571,8 @@ def remove_filelocks(output_dir: str) -> None: files = os.listdir(output_dir) for file in files: - if file.endswith(".lock"): - os.remove(os.path.join(output_dir, file)) + try: + if file.endswith(".lock"): + os.remove(os.path.join(output_dir, file)) + except FileNotFoundError as e: + logger.error(f"The removal of {file} from {output_dir} failed with exception: {e}.") diff --git a/text_importer/scripts/patching/canonical_patch_1_uzh.py b/text_importer/scripts/patching/canonical_patch_1_uzh.py index 372ffced..3ffef385 100644 --- a/text_importer/scripts/patching/canonical_patch_1_uzh.py +++ b/text_importer/scripts/patching/canonical_patch_1_uzh.py @@ -24,7 +24,7 @@ from impresso_commons.path.path_s3 import fetch_files, list_files, list_newspapers from impresso_commons.utils.s3 import fixed_s3fs_glob from impresso_commons.versioning.data_manifest import DataManifest -from text_importer.importers.core import upload_issues, upload_pages +from text_importer.importers.core import upload_issues, upload_pages, remove_filelocks from smart_open import open as smart_open_function from impresso_commons.versioning.helpers import counts_for_canonical_issue import dask.bag as db @@ -35,6 +35,8 @@ from dask.distributed import Client from docopt import docopt from collections import defaultdict +import shutil +from filelock import FileLock IMPRESSO_STORAGEOPT = s3.get_storage_options() UZH_TITLES = ['FedGazDe', 'FedGazFr', 'NZZ'] @@ -166,7 +168,7 @@ def write_upload_pages( if os.path.exists(filepath) and os.path.isfile(filepath): # file shsould only be modified once - logger.warning("The file %s already exists, not modifying it.", filepath) + logger.info("The file %s already exists, not modifying it.", filepath) return key, (False, filepath) logger.info("uploading pages for %s", key) @@ -369,7 +371,7 @@ def main(): issues_with_patched_pages = defaultdict(list) # fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket. - for issue_id, (success, path) in zip(uzh_patched_pages[::2], uzh_patched_pages[1::2]) + for issue_id, (success, path) in zip(uzh_patched_pages[::2], uzh_patched_pages[1::2]): title, year, month, day, edition = issue_id.split('-') # write to file to track potential missing data. From 1f1dde7ddaa97d58cb4d1087fd1e7651d8763593 Mon Sep 17 00:00:00 2001 From: piconti Date: Thu, 22 Feb 2024 11:26:13 +0100 Subject: [PATCH 18/53] minor typo --- text_importer/scripts/patching/canonical_patch_1_uzh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_importer/scripts/patching/canonical_patch_1_uzh.py b/text_importer/scripts/patching/canonical_patch_1_uzh.py index 3ffef385..3403c884 100644 --- a/text_importer/scripts/patching/canonical_patch_1_uzh.py +++ b/text_importer/scripts/patching/canonical_patch_1_uzh.py @@ -371,7 +371,7 @@ def main(): issues_with_patched_pages = defaultdict(list) # fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket. - for issue_id, (success, path) in zip(uzh_patched_pages[::2], uzh_patched_pages[1::2]): + for issue_id, (success, path) in zip(nzz_patched_pages[::2], nzz_patched_pages[1::2]): title, year, month, day, edition = issue_id.split('-') # write to file to track potential missing data. From 8f779b38c60bbffbdd54f03911237e19f079d8d4 Mon Sep 17 00:00:00 2001 From: piconti Date: Thu, 22 Feb 2024 17:09:50 +0100 Subject: [PATCH 19/53] Add comments to nzz patch and small optimizations --- .../scripts/patching/canonical_patch_1_uzh.py | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/text_importer/scripts/patching/canonical_patch_1_uzh.py b/text_importer/scripts/patching/canonical_patch_1_uzh.py index 3403c884..fd2df866 100644 --- a/text_importer/scripts/patching/canonical_patch_1_uzh.py +++ b/text_importer/scripts/patching/canonical_patch_1_uzh.py @@ -50,12 +50,18 @@ def add_property(object_dict: dict[str, Any], prop_name: str, prop_function: Cal logger.debug("%s -> Added property %s: %s", object_dict['id'], prop_name, object_dict[prop_name]) return object_dict -def empty_folder(dir_path: str): +def empty_folder(dir_path: str) -> None: + """Empty a directoy given its path if it exists. + + Args: + dir_path (str): Path to the directory to empty. + """ if os.path.exists(dir_path): shutil.rmtree(dir_path) logger.info("Emptied directory at %s", dir_path) os.mkdir(dir_path) + def write_error( thing_id: str, origin_function: str, @@ -64,9 +70,12 @@ def write_error( ) -> None: """Write the given error of a failed import to the `failed_log` file. + Adapted from `impresso-text-acquisition/text_importer/importers/core.py` to allow + using a issue or page id, and provide the function in which the error took place. + Args: - thing (NewspaperIssue | NewspaperPage | IssueDir): Object for which - the error occurred. + thing_id (str): Canonical ID of the object/file for which the error occurred. + origin_function (str): Function in which the exception occured. error (Exception): Error that occurred and should be logged. failed_log (str): Path to log file for failed imports. """ @@ -106,35 +115,35 @@ def write_upload_issues( output_dir: str, bucket_name: str, failed_log: str | None = None -) -> tuple[str, str]: +) -> tuple[bool, str]: """Compress issues for a Journal-year in a json file and upload them to s3. The compressed ``.bz2`` output file is a JSON-line file, where each line corresponds to an individual issue document in the canonical format. Args: - key (str): Hyphen separated Newspaper ID and year of input issues, e.g. `GDL-1900`. + key (tuple[str, str]): Newspaper ID and year of input issues, e.g. `GDL,1900`. issues (list[dict[str, Any]]): A list of issues as dicts. output_dir (str): Local output directory. bucket_name (str): Name of S3 bucket where to upload the file. + failed_log (str | None): Path to the log in which failed operations are logged. + Defaults to None. Returns: - Tuple[str, str]: Label following the template `-` and - the path to the the compressed `.bz2` file. + tuple[bool, str]: Whether the upload was successful and the path to the + uploaded file. """ newspaper, year = key filename = f'{newspaper}-{year}-issues.jsonl.bz2' filepath = os.path.join(output_dir, newspaper, filename) logger.info(f'Compressing {len(issues)} JSON files into {filepath}') - write_jsonlines_file(filepath, issues, 'issues', failed_log) - if os.path.exists(filepath) and os.path.isfile(filepath): # file shsould only be modified once logger.warning("The file %s already exists, not modifying it.", filepath) return False, filepath - remove_filelocks(os.path.join(output_dir, newspaper)) + write_jsonlines_file(filepath, issues, 'issues', failed_log) return upload_issues('-'.join(key), filepath, bucket_name) @@ -173,8 +182,6 @@ def write_upload_pages( logger.info("uploading pages for %s", key) write_jsonlines_file(filepath, pages, 'pages', failed_log) - - remove_filelocks(os.path.dirname(filepath)) return key, (upload_pages(key, filepath, bucket_name)) @@ -283,7 +290,7 @@ def nzz_write_upload_pages( output_dir: str, bucket_name: str, failed_log: str | None = None, -) -> tuple[str, tuple[bool, str]]: +) -> list[str, tuple[bool, str]]: if len(issues_to_pages) == 0: return '', (False, '') @@ -357,6 +364,7 @@ def main(): failed_log=error_log, ) ) + .flatten() ).compute() # free the memory allocated @@ -371,6 +379,7 @@ def main(): issues_with_patched_pages = defaultdict(list) # fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket. + logger.info(f"nzz_patched_pages[0], nzz_patched_pages[1]: {nzz_patched_pages[0]}, {nzz_patched_pages[1]}") for issue_id, (success, path) in zip(nzz_patched_pages[::2], nzz_patched_pages[1::2]): title, year, month, day, edition = issue_id.split('-') @@ -390,6 +399,9 @@ def main(): nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats) + # remove all the filelocks for this title and year + remove_filelocks(os.path.join(temp_dir, title, f"{title}-{year}")) + elif not success: logger.warning("The pages for issue %s were not correctly uploaded", issue_id) @@ -402,13 +414,12 @@ def main(): .map_partitions(lambda issues: write_upload_issues( issues[0], issues[1], output_dir=temp_dir, - bucket_name= s3_output_bucket, + bucket_name=s3_output_bucket, failed_log=error_log, ) ) ).compute() - logger.info("Finalizing, computing and exporting the manifest") # finalize the manifest and export it note = f"Patching titles {UZH_TITLES}: adding {PROP_NAME} property at page level" From 6ab05be7d52e09ccb2558ffcaaa2c84a43087373 Mon Sep 17 00:00:00 2001 From: piconti Date: Thu, 22 Feb 2024 21:55:11 +0100 Subject: [PATCH 20/53] Include versioning manifest handling in text importer --- text_importer/importers/core.py | 32 +++++++++++++++------ text_importer/importers/generic_importer.py | 21 ++++++++++++-- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/text_importer/importers/core.py b/text_importer/importers/core.py index d21b61ec..b22e3d5e 100644 --- a/text_importer/importers/core.py +++ b/text_importer/importers/core.py @@ -30,6 +30,8 @@ from impresso_commons.text.rebuilder import cleanup from impresso_commons.utils import chunk from impresso_commons.utils.s3 import get_s3_resource +from impresso_commons.versioning.data_manifest import DataManifest +from impresso_commons.versioning.helpers import DataStage, counts_for_canonical_issue from smart_open import open as smart_open_function from text_importer.importers.classes import NewspaperIssue, NewspaperPage @@ -242,6 +244,7 @@ def import_issues( image_dirs: str | None, temp_dir: str | None, chunk_size: int | None, + manifest: DataManifest, client: Client | None = None, ) -> None: """Import a bunch of newspaper issues. @@ -301,11 +304,15 @@ def import_issues( compressed_issue_files = ( issue_bag.groupby(lambda i: (i.journal, i.date.year)) .starmap(compress_issues, + manifest=manifest, output_dir=out_dir, failed_log=failed_log_path) .compute() ) + # recover the updated manifest + manifest = compressed_issue_files[0][2] + logger.info(f'Done compressing issues for {period}') logger.info(f'Start uploading issues for {period}') @@ -320,6 +327,7 @@ def import_issues( # to get cleaner code while ensuring proper partitioning. (db.from_sequence(set(compressed_issue_files)) + .map(lambda tuple: (tuple[0], tuple[1])) .starmap(upload_issues, bucket_name=s3_bucket) .starmap(cleanup).compute()) @@ -372,6 +380,10 @@ def import_issues( remove_filelocks(out_dir) + # finalize and compute the manifest + manifest.compute(export_to_git_and_s3 = False) + manifest.validate_and_export_manifest(push_to_git=False) + if temp_dir is not None and os.path.isdir(temp_dir): shutil.rmtree(temp_dir, ignore_errors=True) @@ -434,9 +446,10 @@ def compress_pages( def compress_issues( key: Tuple[str, int], issues: list[NewspaperIssue], + manifest: DataManifest, output_dir: str | None = None, failed_log: str | None = None, -) -> Tuple[str, str]: +) -> Tuple[str, str, str]: """Compress issues of the same Journal-year and save them in a json file. First check if the file exists, load it and then over-write/add the newly @@ -463,13 +476,13 @@ def compress_issues( # put a file lock to avoid the overwriting of files due to parallelization lock = FileLock(filepath + ".lock", timeout=13) - + items = [issue.issue_data for issue in issues] try: with lock: with smart_open_function(filepath, 'ab') as fout: writer = jsonlines.Writer(fout) - items = [issue.issue_data for issue in issues] + #items = [issue.issue_data for issue in issues] writer.write_all(items) logger.info(f'Written {len(items)} issues to {filepath}') @@ -479,7 +492,11 @@ def compress_issues( logger.exception(e) write_error(filepath, e, failed_log) - return f'{newspaper}-{year}', filepath + # Once the issues were written without issues, add their info to the manifest + for i in items: + manifest.add_by_title_year(newspaper, year, counts_for_canonical_issue(i)) + + return f'{newspaper}-{year}', filepath, manifest def upload_issues( @@ -571,8 +588,5 @@ def remove_filelocks(output_dir: str) -> None: files = os.listdir(output_dir) for file in files: - try: - if file.endswith(".lock"): - os.remove(os.path.join(output_dir, file)) - except FileNotFoundError as e: - logger.error(f"The removal of {file} from {output_dir} failed with exception: {e}.") + if file.endswith(".lock"): + os.remove(os.path.join(output_dir, file)) diff --git a/text_importer/importers/generic_importer.py b/text_importer/importers/generic_importer.py index 138ced87..d0ea4a1d 100644 --- a/text_importer/importers/generic_importer.py +++ b/text_importer/importers/generic_importer.py @@ -2,7 +2,7 @@ Functions and CLI script to convert any OCR data into Impresso's format. Usage: - importer.py --input-dir= (--clear | --incremental) [--output-dir= --image-dirs= --temp-dir= --chunk-size= --s3-bucket= --config-file= --log-file= --verbose --scheduler= --access-rights=] + importer.py --input-dir= (--clear | --incremental) [--output-dir= --image-dirs= --temp-dir= --chunk-size= --s3-bucket= --config-file= --log-file= --verbose --scheduler= --access-rights= --git-repo=] importer.py --version Options: @@ -16,6 +16,7 @@ --log-file= Log file; when missing print log to stdout --access-rights= Access right file if relevant (only for `olive` and `rero` importers) --chunk-size= Chunk size in years used to group issues when importing + --git-repo= Local path to the "impresso-text-acquisition" git directory (including it). --verbose Verbose log messages (good for debugging) --clear Removes the output folder (if already existing) --incremental Skips issues already present in output directory @@ -32,11 +33,16 @@ from dask.distributed import Client, LocalCluster from docopt import docopt +import git from impresso_commons.path.path_fs import (KNOWN_JOURNALS, detect_canonical_issues, IssueDir) +from impresso_commons.versioning.data_manifest import DataManifest +from impresso_commons.versioning.helpers import DataStage + from text_importer import __version__ + from text_importer.importers.classes import NewspaperIssue from text_importer.importers.bl.classes import BlNewspaperIssue from text_importer.importers.core import import_issues @@ -194,6 +200,7 @@ def main( log_level = logging.DEBUG if args["--verbose"] else logging.INFO print_version = args["--version"] config_file = args["--config-file"] + repo_path = args["--git-repo"] if print_version: print(f'impresso-txt-importer v{__version__}') @@ -246,6 +253,16 @@ def main( logger.debug("Following issues will be imported:{}".format(issues)) assert outp_dir is not None or out_bucket is not None + + # ititialize manifest + manifest = DataManifest( + data_stage='canonical', + s3_output_bucket=out_bucket, + git_repo=git.Repo(repo_path), + temp_dir=temp_dir + ) + manifest_note = f'Ingestion of {len(issues)} Newspaper titles into canonical format.' + manifest.append_to_notes(manifest_note) import_issues(issues, outp_dir, out_bucket, issue_class, - image_dirs, temp_dir, chunk_size, client) + image_dirs, temp_dir, chunk_size, manifest, client) From ca9e90f5d82b7a4b6f7a2a976e622cfa415bdebd Mon Sep 17 00:00:00 2001 From: piconti Date: Fri, 23 Feb 2024 16:04:39 +0100 Subject: [PATCH 21/53] Add the reading order to BNF, BNF-EN, and BNL --- text_importer/importers/bnf/classes.py | 12 +++++++++--- text_importer/importers/bnf_en/classes.py | 8 +++++++- text_importer/importers/core.py | 7 +++++-- text_importer/importers/lux/classes.py | 8 +++++++- text_importer/utils.py | 21 +++++++++++++++++++++ 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/text_importer/importers/bnf/classes.py b/text_importer/importers/bnf/classes.py index 32c68e1c..66b92ac0 100644 --- a/text_importer/importers/bnf/classes.py +++ b/text_importer/importers/bnf/classes.py @@ -24,7 +24,7 @@ MetsAltoNewspaperPage) from text_importer.importers.mets_alto.alto import (distill_coordinates, parse_style) -from text_importer.utils import get_issue_schema, get_page_schema +from text_importer.utils import get_issue_schema, get_page_schema, get_reading_order IssueSchema = get_issue_schema() Pageschema = get_page_schema() @@ -400,14 +400,20 @@ def _parse_mets(self) -> None: item_counter, mets_doc) content_items += cis - # Finally add the pages and iiif link + # Finally add the pages and iiif link for x in content_items: x['m']['pp'] = list(set(c['comp_page_no'] for c in x['l']['parts'])) if x['m']['tp'] == CONTENTITEM_TYPE_IMAGE: x['c'], x['m']['iiif_link'] = self._get_image_iiif_link( x['m']['id'], x['l']['parts'] ) - + + # once the pages are added to the metadata, compute & add the reading order + reading_order_dict = get_reading_order(content_items) + for item in content_items: + item['m']['ro'] = reading_order_dict[item['m']['id']] + + self.pages = list(self.pages.values()) # Issue manifest iiif URI is in format {iiif_prefix}/{ark_id}/manifest.json diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index f77c7d38..914b07f9 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -21,7 +21,7 @@ from text_importer.importers.bnf.helpers import BNF_CONTENT_TYPES from text_importer.importers.mets_alto import (MetsAltoNewspaperIssue, MetsAltoNewspaperPage) -from text_importer.utils import get_issue_schema, get_page_schema +from text_importer.utils import get_issue_schema, get_page_schema, get_reading_order IssueSchema = get_issue_schema() Pageschema = get_page_schema() @@ -322,6 +322,12 @@ def _parse_content_items(self) -> list[dict[str, Any]]: else: content_items.append(self._parse_content_item(div, counter, doc)) counter += 1 + + # add the reading order to the items metadata + reading_order_dict = get_reading_order(content_items) + for item in content_items: + item['m']['ro'] = reading_order_dict[item['m']['id']] + return content_items def _get_image_info( diff --git a/text_importer/importers/core.py b/text_importer/importers/core.py index b22e3d5e..1cc3d780 100644 --- a/text_importer/importers/core.py +++ b/text_importer/importers/core.py @@ -588,5 +588,8 @@ def remove_filelocks(output_dir: str) -> None: files = os.listdir(output_dir) for file in files: - if file.endswith(".lock"): - os.remove(os.path.join(output_dir, file)) + try: + if file.endswith(".lock"): + os.remove(os.path.join(output_dir, file)) + except FileNotFoundError as e: + logger.error("File %s could not be removed as it does not exist: %s.", file, e) diff --git a/text_importer/importers/lux/classes.py b/text_importer/importers/lux/classes.py index 79c4f83c..c108f48e 100644 --- a/text_importer/importers/lux/classes.py +++ b/text_importer/importers/lux/classes.py @@ -31,7 +31,7 @@ from text_importer.importers.mets_alto import (MetsAltoNewspaperIssue, MetsAltoNewspaperPage, parse_mets_amdsec) -from text_importer.utils import get_issue_schema, get_page_schema +from text_importer.utils import get_issue_schema, get_page_schema, get_reading_order IssueSchema = get_issue_schema() Pageschema = get_page_schema() @@ -607,6 +607,9 @@ def _parse_mets(self) -> None: ark_link = mets_doc.find('mets').get('OBJID') self.ark_id = ark_link.replace('https://persist.lu/ark:/', 'ark:') + # compute the reading order for the issue's items + reading_order_dict = get_reading_order(content_items) + for ci in content_items: # ci['l']['parts'] = self._parse_mets_div(item_div) @@ -618,6 +621,9 @@ def _parse_mets(self) -> None: page_no = part["comp_page_no"] if page_no not in ci['m']['pp']: ci['m']['pp'].append(page_no) + + # add the reading order + ci['m']['ro'] = reading_order_dict[ci['m']['id']] self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), diff --git a/text_importer/utils.py b/text_importer/utils.py index 6d2adffd..f37e1dfe 100644 --- a/text_importer/utils.py +++ b/text_importer/utils.py @@ -172,3 +172,24 @@ def verify_imported_issues( logger.info(f"Content item {actual_content_item['m']['id']}" "dit not change (legacy metadata are identical)") + + +def get_reading_order(items: list[dict[str, Any]]) -> dict[str, int]: + """Generate a reading order for items based on their id and the pages they span. + + This reading order can be used to display the content items properly in a table + of contents without skipping form page to page. + + Args: + items (list[dict[str, Any]]): List of items to reorder for the ToC. + + Returns: + dict[str, int]: A dictionary mapping item IDs to their reading order. + """ + items_copy = copy.deepcopy(items) + ids_and_pages = [(i['m']['id'], i['m']['pp']) for i in items_copy] + sorted_ids = sorted( + sorted(ids_and_pages, key=lambda x: int(x[0].split('-i')[-1])), + key=lambda x: x[1] + ) + return {t[0]: index+1 for index, t in enumerate(sorted_ids)} \ No newline at end of file From 79948cde0f856c10a2b8c478ab9af7e9e8126cb1 Mon Sep 17 00:00:00 2001 From: piconti Date: Fri, 23 Feb 2024 17:38:47 +0100 Subject: [PATCH 22/53] Replace issue ID in output --- text_importer/importers/bnf/classes.py | 2 +- text_importer/importers/bnf_en/classes.py | 2 +- text_importer/importers/lux/classes.py | 2 +- text_importer/utils.py | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/text_importer/importers/bnf/classes.py b/text_importer/importers/bnf/classes.py index 66b92ac0..8fee4519 100644 --- a/text_importer/importers/bnf/classes.py +++ b/text_importer/importers/bnf/classes.py @@ -424,8 +424,8 @@ def _parse_mets(self) -> None: self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), - "i": content_items, "id": self.id, + "i": content_items, "ar": self.rights, "pp": [p.id for p in self.pages], "iiif_manifest_uri": iiif_manifest diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index 914b07f9..04032d4a 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -397,8 +397,8 @@ def _parse_mets(self) -> None: self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), - "i": content_items, "id": self.id, + "i": content_items, "ar": self.rights, "pp": [p.id for p in self.pages], "iiif_manifest_uri": iiif_manifest diff --git a/text_importer/importers/lux/classes.py b/text_importer/importers/lux/classes.py index c108f48e..5b9262d3 100644 --- a/text_importer/importers/lux/classes.py +++ b/text_importer/importers/lux/classes.py @@ -627,8 +627,8 @@ def _parse_mets(self) -> None: self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), - "i": content_items, "id": self.id, + "i": content_items, "ar": self.rights, "pp": [p.id for p in self.pages] } diff --git a/text_importer/utils.py b/text_importer/utils.py index f37e1dfe..87eb9821 100644 --- a/text_importer/utils.py +++ b/text_importer/utils.py @@ -1,6 +1,7 @@ import json import logging import os +import copy from contextlib import ExitStack import pathlib import importlib_resources From af99fcbcdeb6a1d179356b47f570cf9fa5727355 Mon Sep 17 00:00:00 2001 From: piconti Date: Fri, 23 Feb 2024 17:52:40 +0100 Subject: [PATCH 23/53] Activate automatic push to git once manifest is generated --- text_importer/importers/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text_importer/importers/core.py b/text_importer/importers/core.py index 1cc3d780..cbbbf85f 100644 --- a/text_importer/importers/core.py +++ b/text_importer/importers/core.py @@ -381,8 +381,8 @@ def import_issues( remove_filelocks(out_dir) # finalize and compute the manifest - manifest.compute(export_to_git_and_s3 = False) - manifest.validate_and_export_manifest(push_to_git=False) + manifest.compute(export_to_git_and_s3 = True) + # manifest.validate_and_export_manifest(push_to_git=False) if temp_dir is not None and os.path.isdir(temp_dir): shutil.rmtree(temp_dir, ignore_errors=True) From 506c5db9272641be8881562f557ae9a47b4546df Mon Sep 17 00:00:00 2001 From: piconti Date: Sun, 25 Feb 2024 15:50:03 +0100 Subject: [PATCH 24/53] add default value to define coords --- text_importer/importers/bnf/classes.py | 2 +- text_importer/importers/mets_alto/alto.py | 3 +++ text_importer/importers/mets_alto/classes.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/text_importer/importers/bnf/classes.py b/text_importer/importers/bnf/classes.py index 8fee4519..17401c7f 100644 --- a/text_importer/importers/bnf/classes.py +++ b/text_importer/importers/bnf/classes.py @@ -356,7 +356,7 @@ def _get_image_iiif_link( p for p in parts if p['comp_role'] == CONTENTITEM_TYPE_IMAGE ] - iiif_link = None + iiif_link, coords = None, None if len(image_part) == 0: message = (f"Content item {ci_id} of type " f"{CONTENTITEM_TYPE_IMAGE} does not have image part.") diff --git a/text_importer/importers/mets_alto/alto.py b/text_importer/importers/mets_alto/alto.py index dc5aacf6..dfc7f262 100644 --- a/text_importer/importers/mets_alto/alto.py +++ b/text_importer/importers/mets_alto/alto.py @@ -53,8 +53,11 @@ def parse_textline(element: Tag) -> tuple[dict, list[str]]: try: coords = distill_coordinates(child) except TypeError as e: + logger.error(f"Token {child.get('ID')} does not have coordinates") notes.append(f"Token {child.get('ID')} does not have coordinates") + coords = None continue + token = { 'c': coords, 'tx': child.get('CONTENT') diff --git a/text_importer/importers/mets_alto/classes.py b/text_importer/importers/mets_alto/classes.py index ccc84114..6bc407f1 100644 --- a/text_importer/importers/mets_alto/classes.py +++ b/text_importer/importers/mets_alto/classes.py @@ -59,7 +59,7 @@ def __init__(self, _id: str, number: int, filename: str, 'id': _id, 'cdt': strftime("%Y-%m-%d %H:%M:%S"), 'r': [] # here go the page regions - } + } @property def xml(self) -> BeautifulSoup: From 11f01018a0ba2c5f23b08b1048aa4fce2ad1f943 Mon Sep 17 00:00:00 2001 From: piconti Date: Mon, 26 Feb 2024 16:55:08 +0100 Subject: [PATCH 25/53] pull latest updates from impresso schemas --- text_importer/impresso-schemas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_importer/impresso-schemas b/text_importer/impresso-schemas index 708ba747..f44c8ae3 160000 --- a/text_importer/impresso-schemas +++ b/text_importer/impresso-schemas @@ -1 +1 @@ -Subproject commit 708ba747ab3143de4a0b63f11c7216d02bb5235b +Subproject commit f44c8ae3be910d1d0a3495bdd5b648249506bb85 From 277de6e2342f0f88d5a9dec72b7c83c22b8661c2 Mon Sep 17 00:00:00 2001 From: piconti Date: Mon, 26 Feb 2024 22:36:34 +0100 Subject: [PATCH 26/53] correct manifest updates during ingestion --- text_importer/importers/bnf/detect.py | 1 - text_importer/importers/core.py | 54 +++++++++++++-------- text_importer/importers/generic_importer.py | 7 ++- text_importer/importers/mets_alto/alto.py | 1 - 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/text_importer/importers/bnf/detect.py b/text_importer/importers/bnf/detect.py index 7d0f8409..8119f676 100644 --- a/text_importer/importers/bnf/detect.py +++ b/text_importer/importers/bnf/detect.py @@ -246,7 +246,6 @@ def select_issues( ) and i.journal not in exclude_list ).compute() ) - exclude_flag = False if not exclude_list else True filtered_issues = _apply_datefilter( filter_dict, selected_issues, year_only=year_flag diff --git a/text_importer/importers/core.py b/text_importer/importers/core.py index cbbbf85f..f4e1781b 100644 --- a/text_importer/importers/core.py +++ b/text_importer/importers/core.py @@ -44,7 +44,7 @@ def write_error( thing: NewspaperIssue | NewspaperPage | IssueDir, error: Exception, - failed_log: str + failed_log: str | None ) -> None: """Write the given error of a failed import to the `failed_log` file. @@ -69,8 +69,9 @@ def write_error( f"{error}" ) - with open(failed_log, "a+") as f: - f.write(note + "\n") + if failed_log is not None: + with open(failed_log, "a+") as f: + f.write(note + "\n") def dir2issue( @@ -304,14 +305,19 @@ def import_issues( compressed_issue_files = ( issue_bag.groupby(lambda i: (i.journal, i.date.year)) .starmap(compress_issues, - manifest=manifest, output_dir=out_dir, failed_log=failed_log_path) .compute() ) + # Once the issues were written to the fs without issues, add their info to the manifest + for index, (np_year, filepath, yearly_stats) in enumerate(compressed_issue_files): + manifest.add_count_list_by_title_year(np_year.split('-')[0], np_year.split('-')[1], yearly_stats) + # remove the yearly stats from the filenames + compressed_issue_files[index] = (np_year, filepath) + # recover the updated manifest - manifest = compressed_issue_files[0][2] + #manifest = compressed_issue_files[0][2] logger.info(f'Done compressing issues for {period}') @@ -326,9 +332,8 @@ def import_issues( # TODO: The issues should be processed within a dask dataframe instead of bag # to get cleaner code while ensuring proper partitioning. - (db.from_sequence(set(compressed_issue_files)) - .map(lambda tuple: (tuple[0], tuple[1])) - .starmap(upload_issues, bucket_name=s3_bucket) + (db.from_sequence(set(compressed_issue_files)) + .starmap(upload_issues, bucket_name=s3_bucket, failed_log=failed_log_path) .starmap(cleanup).compute()) logger.info(f'Done uploading issues for {period}') @@ -361,9 +366,12 @@ def import_issues( ).replace('/', '-') ) .starmap( - compress_pages, suffix='pages', output_dir=pages_out_dir + compress_pages, + suffix='pages', + output_dir=pages_out_dir, + failed_log=failed_log_path ) - .starmap(upload_pages, bucket_name=s3_bucket) + .starmap(upload_pages,bucket_name=s3_bucket,failed_log=failed_log_path) .starmap(cleanup) .compute() ) @@ -398,7 +406,8 @@ def compress_pages( key: str, json_files: list[str], output_dir: str, - suffix: str = "" + suffix: str = "", + failed_log: str | None = None ) -> Tuple[str, str]: """Merge a set of JSON line files into a single compressed archive. @@ -434,6 +443,7 @@ def compress_pages( except JSONDecodeError as e: logger.error(f'Reading data from {json_file} failed') logger.exception(e) + write_error(filepath, e, failed_log) logger.info( f'Written {items_count} docs from {json_file} to {filepath}' ) @@ -446,10 +456,9 @@ def compress_pages( def compress_issues( key: Tuple[str, int], issues: list[NewspaperIssue], - manifest: DataManifest, output_dir: str | None = None, failed_log: str | None = None, -) -> Tuple[str, str, str]: +) -> Tuple[str, str, list[dict[str, int]]]: """Compress issues of the same Journal-year and save them in a json file. First check if the file exists, load it and then over-write/add the newly @@ -468,6 +477,7 @@ def compress_issues( Returns: Tuple[str, str]: Label following the template `-` and the path to the the compressed `.bz2` file. + TODO: add update """ newspaper, year = key filename = f'{newspaper}-{year}-issues.jsonl.bz2' @@ -493,16 +503,19 @@ def compress_issues( write_error(filepath, e, failed_log) # Once the issues were written without issues, add their info to the manifest + yearly_stats = [] for i in items: - manifest.add_by_title_year(newspaper, year, counts_for_canonical_issue(i)) + yearly_stats.append(counts_for_canonical_issue(i)) + #manifest.add_by_title_year(newspaper, year, counts_for_canonical_issue(i)) - return f'{newspaper}-{year}', filepath, manifest + return f'{newspaper}-{year}', filepath, yearly_stats def upload_issues( sort_key: str, filepath: str, - bucket_name: str | None = None + bucket_name: str | None = None, + failed_log: str | None = None ) -> Tuple[bool, str]: """Upload an issues JSON-line file to a given S3 bucket. @@ -513,7 +526,7 @@ def upload_issues( filepath (str): Path of the file to upload to S3. bucket_name (str | None, optional): Name of S3 bucket where to upload the file. Defaults to None. - + TODO: update docstring Returns: Tuple[bool, str]: Whether the upload was successful and the path to the uploaded file. @@ -535,6 +548,7 @@ def upload_issues( return True, filepath except Exception as e: logger.error(f'The upload of {filepath} failed with error {e}') + write_error(filepath, e, failed_log) else: logger.info(f'Bucket name is None, not uploading issue {filepath}.') return False, filepath @@ -543,7 +557,8 @@ def upload_issues( def upload_pages( sort_key: str, filepath: str, - bucket_name: str | None = None + bucket_name: str | None = None, + failed_log: str | None = None ) -> Tuple[bool, str]: """Upload a page JSON file to a given S3 bucket. @@ -552,7 +567,7 @@ def upload_pages( filepath (str): Path of the file to upload to S3. bucket_name (str | None, optional): Name of S3 bucket where to upload the file. Defaults to None. - + TODO: update docstring Returns: Tuple[bool, str]: Whether the upload was successful and the path to the uploaded file. @@ -574,6 +589,7 @@ def upload_pages( return True, filepath except Exception as e: logger.error(f'The upload of {filepath} failed with error {e}') + write_error(filepath, e, failed_log) else: logger.info(f'Bucket name is None, not uploading page {filepath}.') return False, filepath diff --git a/text_importer/importers/generic_importer.py b/text_importer/importers/generic_importer.py index d0ea4a1d..1b291e2e 100644 --- a/text_importer/importers/generic_importer.py +++ b/text_importer/importers/generic_importer.py @@ -261,7 +261,12 @@ def main( git_repo=git.Repo(repo_path), temp_dir=temp_dir ) - manifest_note = f'Ingestion of {len(issues)} Newspaper titles into canonical format.' + + if config_file: + newspapers = f" for titles {list(config['newspapers'].keys())}" + else: + newspapers = '' + manifest_note = f'Ingestion of {len(issues)} Newspaper issues into canonical format{newspapers}.' manifest.append_to_notes(manifest_note) import_issues(issues, outp_dir, out_bucket, issue_class, diff --git a/text_importer/importers/mets_alto/alto.py b/text_importer/importers/mets_alto/alto.py index dfc7f262..d06826f2 100644 --- a/text_importer/importers/mets_alto/alto.py +++ b/text_importer/importers/mets_alto/alto.py @@ -53,7 +53,6 @@ def parse_textline(element: Tag) -> tuple[dict, list[str]]: try: coords = distill_coordinates(child) except TypeError as e: - logger.error(f"Token {child.get('ID')} does not have coordinates") notes.append(f"Token {child.get('ID')} does not have coordinates") coords = None continue From dac584838d2ce99f9b36826c451c16b0ac7b11b8 Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 27 Feb 2024 10:41:35 +0100 Subject: [PATCH 27/53] improve logging of errors --- text_importer/importers/bnf_en/classes.py | 13 +++++++------ text_importer/importers/core.py | 6 +----- text_importer/importers/mets_alto/classes.py | 4 ++-- text_importer/impresso-schemas | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index 04032d4a..09394754 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -114,13 +114,14 @@ def _find_pages(self) -> None: alto_path = os.path.join(self.path, 'ALTO') if not os.path.exists(alto_path): - logger.critical(f"Could not find pages for {self.id}") + logger.critical(f"Could not find pages for {self.id}, non-existing path: {alto_path}") + raise Exception(msg) page_file_names = [ file for file in os.listdir(alto_path) if not file.startswith('.') and '.xml' in file - ] + ] page_numbers = [] @@ -131,7 +132,7 @@ def _find_pages(self) -> None: page_canonical_names = [ "{}-p{}".format(self.id, str(page_n).zfill(4)) for page_n in page_numbers - ] + ] self.pages = [] for filename, page_no, page_id in zip( @@ -147,9 +148,9 @@ def _find_pages(self) -> None: ) ) except Exception as e: - logger.error(f'Adding page {page_no} {page_id} {filename}', - f'raised following exception: {e}') - raise e + msg = f'Adding page {page_no} {page_id} {filename} raised following exception: {e}' + logger.error(msg) + raise Exception(msg) from e def _parse_content_parts(self, content_div: Tag) -> list[dict[str, Any]]: """Parse given div's children tags to create legacy `parts` component. diff --git a/text_importer/importers/core.py b/text_importer/importers/core.py index f4e1781b..ea9cc031 100644 --- a/text_importer/importers/core.py +++ b/text_importer/importers/core.py @@ -310,17 +310,13 @@ def import_issues( .compute() ) + logger.info(f'Done compressing issues for {period}, updating the manifest...') # Once the issues were written to the fs without issues, add their info to the manifest for index, (np_year, filepath, yearly_stats) in enumerate(compressed_issue_files): manifest.add_count_list_by_title_year(np_year.split('-')[0], np_year.split('-')[1], yearly_stats) # remove the yearly stats from the filenames compressed_issue_files[index] = (np_year, filepath) - # recover the updated manifest - #manifest = compressed_issue_files[0][2] - - logger.info(f'Done compressing issues for {period}') - logger.info(f'Start uploading issues for {period}') # NOTE: As a function of the partitioning size and the number of issues, diff --git a/text_importer/importers/mets_alto/classes.py b/text_importer/importers/mets_alto/classes.py index 6bc407f1..829e9fe1 100644 --- a/text_importer/importers/mets_alto/classes.py +++ b/text_importer/importers/mets_alto/classes.py @@ -81,10 +81,10 @@ def xml(self) -> BeautifulSoup: return alto_doc except IOError as e: if i < tries - 1: # i is zero indexed - logger.warning(f"Caught error for {self.id}, retrying (up to {tries} times) to read xml file. Error: {e}.") + logger.error(f"Caught error for {self.id}, retrying (up to {tries} times) to read xml file. Error: {e}.") continue else: - logger.warning(f"Reached maximum amount of errors for {self.id}.") + logger.erro(f"Reached maximum amount of errors for {self.id}.") raise e diff --git a/text_importer/impresso-schemas b/text_importer/impresso-schemas index f44c8ae3..708ba747 160000 --- a/text_importer/impresso-schemas +++ b/text_importer/impresso-schemas @@ -1 +1 @@ -Subproject commit f44c8ae3be910d1d0a3495bdd5b648249506bb85 +Subproject commit 708ba747ab3143de4a0b63f11c7216d02bb5235b From 22bbafa99e25a492cfdcabf247ff679a0bfa4e53 Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 28 Feb 2024 10:39:44 +0100 Subject: [PATCH 28/53] increase default number of workers --- text_importer/importers/bnf_en/classes.py | 7 ++++--- text_importer/importers/generic_importer.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index 09394754..a79bd9be 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -114,7 +114,8 @@ def _find_pages(self) -> None: alto_path = os.path.join(self.path, 'ALTO') if not os.path.exists(alto_path): - logger.critical(f"Could not find pages for {self.id}, non-existing path: {alto_path}") + msg = f"Could not find pages for {self.id}, non-existing path: {alto_path}" + logger.critical(msg) raise Exception(msg) page_file_names = [ @@ -373,8 +374,8 @@ def _get_image_info( continue element = elements[0] - hpos, vpos= element.get('HPOS'), element.get('VPOS') - width, height = element.get('WIDTH'), element.get('HEIGHT') + hpos, vpos = element.get('HPOS'), element.get('VPOS') + width, height = element.get('WIDTH'), element.get('HEIGHT') # Select largest image area = int(float(width)) * int(float(height)) diff --git a/text_importer/importers/generic_importer.py b/text_importer/importers/generic_importer.py index 1b291e2e..8730f2bb 100644 --- a/text_importer/importers/generic_importer.py +++ b/text_importer/importers/generic_importer.py @@ -98,7 +98,7 @@ def get_dask_client( if scheduler is None: #cluster = LocalCluster(n_workers=32, memory_limit='auto', threads_per_worker=2) #client = Client(cluster) - client = Client(n_workers=16, threads_per_worker=2) + client = Client(n_workers=24, threads_per_worker=2) else: client = Client(scheduler) client.run( From f4b7366c71ea94183c5221882c15e3da93c98a5c Mon Sep 17 00:00:00 2001 From: piconti Date: Thu, 29 Feb 2024 09:49:18 +0100 Subject: [PATCH 29/53] small typo and edge-case fixes --- text_importer/importers/mets_alto/alto.py | 2 +- text_importer/importers/mets_alto/classes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/text_importer/importers/mets_alto/alto.py b/text_importer/importers/mets_alto/alto.py index d06826f2..7336b37b 100644 --- a/text_importer/importers/mets_alto/alto.py +++ b/text_importer/importers/mets_alto/alto.py @@ -159,7 +159,7 @@ def parse_style(style_div: Tag) -> dict[str, float | str]: style = { "id": font_id, - "fs": float(font_size), + "fs": float(font_size) if font_size!='' else None, "f": font_name } return style diff --git a/text_importer/importers/mets_alto/classes.py b/text_importer/importers/mets_alto/classes.py index 829e9fe1..df2a0d5e 100644 --- a/text_importer/importers/mets_alto/classes.py +++ b/text_importer/importers/mets_alto/classes.py @@ -84,7 +84,7 @@ def xml(self) -> BeautifulSoup: logger.error(f"Caught error for {self.id}, retrying (up to {tries} times) to read xml file. Error: {e}.") continue else: - logger.erro(f"Reached maximum amount of errors for {self.id}.") + logger.error(f"Reached maximum amount of errors for {self.id}.") raise e From c8f0c3388d17406cda100c51be60502f07af774a Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 5 Mar 2024 11:58:28 +0100 Subject: [PATCH 30/53] update schema submodule --- text_importer/impresso-schemas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_importer/impresso-schemas b/text_importer/impresso-schemas index 708ba747..f44c8ae3 160000 --- a/text_importer/impresso-schemas +++ b/text_importer/impresso-schemas @@ -1 +1 @@ -Subproject commit 708ba747ab3143de4a0b63f11c7216d02bb5235b +Subproject commit f44c8ae3be910d1d0a3495bdd5b648249506bb85 From e99103c36b0a5e4bd21e3da439d2978962c42959 Mon Sep 17 00:00:00 2001 From: piconti Date: Thu, 7 Mar 2024 15:45:28 +0100 Subject: [PATCH 31/53] upade core.py's documentation and adapt logs for pylint --- text_importer/importers/core.py | 256 ++++++++++++++++---------------- 1 file changed, 128 insertions(+), 128 deletions(-) diff --git a/text_importer/importers/core.py b/text_importer/importers/core.py index ea9cc031..0b1ca821 100644 --- a/text_importer/importers/core.py +++ b/text_importer/importers/core.py @@ -21,6 +21,7 @@ from pathlib import Path from time import strftime from typing import Tuple, Type +from botocore.exceptions import BotoCoreError import jsonlines from dask import bag as db @@ -31,7 +32,7 @@ from impresso_commons.utils import chunk from impresso_commons.utils.s3 import get_s3_resource from impresso_commons.versioning.data_manifest import DataManifest -from impresso_commons.versioning.helpers import DataStage, counts_for_canonical_issue +from impresso_commons.versioning.helpers import counts_for_canonical_issue from smart_open import open as smart_open_function from text_importer.importers.classes import NewspaperIssue, NewspaperPage @@ -43,8 +44,8 @@ def write_error( thing: NewspaperIssue | NewspaperPage | IssueDir, - error: Exception, - failed_log: str | None + error: Exception, + failed_log: str | None, ) -> None: """Write the given error of a failed import to the `failed_log` file. @@ -54,7 +55,7 @@ def write_error( error (Exception): Error that occurred and should be logged. failed_log (str): Path to log file for failed imports. """ - logger.error(f'Error when processing {thing}: {error}') + logger.error("Error when processing %s: %s", thing, error) logger.exception(error) if isinstance(thing, NewspaperPage): issuedir = thing.issue.issuedir @@ -64,13 +65,10 @@ def write_error( # if it's neither an issue nor a page it must be an issuedir issuedir = thing - note = ( - f"{canonical_path(issuedir, path_type='dir').replace('/', '-')}: " - f"{error}" - ) + note = f"{canonical_path(issuedir, path_type='dir').replace('/', '-')}: " f"{error}" if failed_log is not None: - with open(failed_log, "a+") as f: + with open(failed_log, "a+", encoding="utf-8") as f: f.write(note + "\n") @@ -78,8 +76,8 @@ def dir2issue( issue: IssueDir, issue_class: Type[NewspaperIssue], failed_log: str | None = None, - image_dirs: str | None = None, - temp_dir: str | None = None + image_dirs: str | None = None, + temp_dir: str | None = None, ) -> NewspaperIssue | None: """Instantiate a `NewspaperIssue` object from an `IssueDir`. @@ -93,7 +91,7 @@ def dir2issue( instantiation was not successful. Defaults to None. image_dirs (str | None, optional): Path to the directory containing the information on images, only for Olive importer. Defaults to None. - temp_dir (str | None, optional): Temporary directory to unpack the + temp_dir (str | None, optional): Temporary directory to unpack the issue's zip archive into. Defaults to None. Returns: @@ -117,8 +115,8 @@ def dirs2issues( issues: list[IssueDir], issue_class: Type[NewspaperIssue], failed_log: str | None = None, - image_dirs: str | None = None, - temp_dir: str | None = None + image_dirs: str | None = None, + temp_dir: str | None = None, ) -> list[NewspaperIssue]: """Instantiate the `NewspaperIssue` objects to import to Impresso's format. @@ -140,9 +138,7 @@ def dirs2issues( """ ret = [] for issue in issues: - np_issue = dir2issue( - issue, issue_class, failed_log, image_dirs, temp_dir - ) + np_issue = dir2issue(issue, issue_class, failed_log, image_dirs, temp_dir) if np_issue is not None: ret.append(np_issue) return ret @@ -152,7 +148,7 @@ def issue2pages(issue: NewspaperIssue) -> list[NewspaperPage]: """Flatten an issue into a list of their pages. As an issue consists of several pages, this function is useful - in order to process each page in a truly parallel fashion. + in order to process each page in a truly parallel fashion. Args: issue (NewspaperIssue): Issue to collect the pages of. @@ -168,19 +164,18 @@ def issue2pages(issue: NewspaperIssue) -> list[NewspaperPage]: def serialize_pages( - pages: list[NewspaperPage], - output_dir: str | None = None + pages: list[NewspaperPage], output_dir: str | None = None ) -> list[Tuple[IssueDir, str]]: """Serialize a list of pages to an output directory. Args: pages (list[NewspaperPage]): Input newspaper pages. - output_dir (str | None, optional): Path to the output directory. + output_dir (str | None, optional): Path to the output directory. Defaults to None. Returns: - list[Tuple[IssueDir, str]]: A list of tuples (`IssueDir`, `path`), - where the `IssueDir` object represents the issue to which pages + list[Tuple[IssueDir, str]]: A list of tuples (`IssueDir`, `path`), + where the `IssueDir` object represents the issue to which pages belong, and `path` the path to the individual page JSON file. """ result = [] @@ -189,35 +184,28 @@ def serialize_pages( issue_dir = copy(page.issue.issuedir) - out_dir = os.path.join( - output_dir, - canonical_path(issue_dir, path_type="dir") - ) + out_dir = os.path.join(output_dir, canonical_path(issue_dir, path_type="dir")) if not os.path.exists(out_dir): os.makedirs(out_dir) canonical_filename = canonical_path( - issue_dir, - "p" + str(page.number).zfill(4), - ".json" + issue_dir, "p" + str(page.number).zfill(4), ".json" ) out_file = os.path.join(out_dir, canonical_filename) - with open(out_file, 'w', encoding='utf-8') as jsonfile: + with open(out_file, "w", encoding="utf-8") as jsonfile: json.dump(page.page_data, jsonfile) - logger.info(f"Written page \'{page.number}\' to {out_file}") + logger.info("Written page '%s' to %s", page.number, out_file) result.append((issue_dir, out_file)) - # TODO: this can be deleted, I believe as it has no effect + # this can be deleted, I believe as it has no effect gc.collect() return result -def process_pages( - pages: list[NewspaperPage], failed_log: str -) -> list[NewspaperPage]: +def process_pages(pages: list[NewspaperPage], failed_log: str) -> list[NewspaperPage]: """Given a list of pages, trigger the ``.parse()`` method of each page. Args: @@ -256,39 +244,44 @@ def import_issues( s3_bucket (str | None): Output s3 bucket for the json files. issue_class (Type[NewspaperIssue]): Newspaper issue class to import, (Child of ``NewspaperIssue``). - image_dirs (str | None): Directory of images for Olive format, + image_dirs (str | None): Directory of images for Olive format, (can be multiple). temp_dir (str | None): Temporary directory for extracting archives (applies only to importers make use of ``ZipArchive``). chunk_size (int | None): Chunk size in years used to process issues. """ - msg = f'Issues to import: {len(issues)}' + msg = f"Issues to import: {len(issues)}" logger.info(msg) failed_log_path = os.path.join( - out_dir, - f'failed-{strftime("%Y-%m-%d-%H-%M-%S")}.log' + out_dir, f'failed-{strftime("%Y-%m-%d-%H-%M-%S")}.log' ) if chunk_size is not None: csize = int(chunk_size) chunks = groupby( - sorted(issues, key=lambda x: x.date.year), - lambda x: x.date.year - (x.date.year % csize) + sorted(issues, key=lambda x: x.date.year), + lambda x: x.date.year - (x.date.year % csize), ) chunks = [(year, list(issues)) for year, issues in chunks] - logger.info(f"Dividing issues into chunks of {chunk_size} years " - f"({len(chunks)} chunks in total)") + msg = ( + f"Dividing issues into chunks of {chunk_size} years " + f"({len(chunks)} chunks in total)" + ) + logger.info(msg) for year, issue_chunk in chunks: - logger.info(f"Chunk of period {year} - {year + csize - 1} covers " - f"{len(issue_chunk)} issues") + msg = ( + f"Chunk of period {year} - {year + csize - 1} covers " + f"{len(issue_chunk)} issues" + ) + logger.info(msg) else: chunks = [(None, issues)] for year, issue_chunk in chunks: if year is None: - period = 'all years' + period = "all years" else: - period = f'{year} - {year + csize - 1}' + period = f"{year} - {year + csize - 1}" temp_issue_bag = db.from_sequence(issue_chunk, partition_size=20) @@ -297,27 +290,29 @@ def import_issues( issue_class=issue_class, failed_log=failed_log_path, image_dirs=image_dirs, - temp_dir=temp_dir + temp_dir=temp_dir, ).persist() - logger.info(f'Start compressing issues for {period}') + logger.info("Start compressing issues for %s", period) compressed_issue_files = ( - issue_bag.groupby(lambda i: (i.journal, i.date.year)) - .starmap(compress_issues, - output_dir=out_dir, - failed_log=failed_log_path) + issue_bag.groupby(lambda i: (i.journal, i.date.year)) + .starmap(compress_issues, output_dir=out_dir, failed_log=failed_log_path) .compute() ) - logger.info(f'Done compressing issues for {period}, updating the manifest...') + logger.info("Done compressing issues for %s, updating the manifest...", period) # Once the issues were written to the fs without issues, add their info to the manifest - for index, (np_year, filepath, yearly_stats) in enumerate(compressed_issue_files): - manifest.add_count_list_by_title_year(np_year.split('-')[0], np_year.split('-')[1], yearly_stats) + for index, (np_year, filepath, yearly_stats) in enumerate( + compressed_issue_files + ): + manifest.add_count_list_by_title_year( + np_year.split("-")[0], np_year.split("-")[1], yearly_stats + ) # remove the yearly stats from the filenames compressed_issue_files[index] = (np_year, filepath) - logger.info(f'Start uploading issues for {period}') + logger.info("Start uploading issues for %s", period) # NOTE: As a function of the partitioning size and the number of issues, # the issues of a single year may be assigned to different partitions. @@ -328,11 +323,14 @@ def import_issues( # TODO: The issues should be processed within a dask dataframe instead of bag # to get cleaner code while ensuring proper partitioning. - (db.from_sequence(set(compressed_issue_files)) - .starmap(upload_issues, bucket_name=s3_bucket, failed_log=failed_log_path) - .starmap(cleanup).compute()) + ( + db.from_sequence(set(compressed_issue_files)) + .starmap(upload_issues, bucket_name=s3_bucket, failed_log=failed_log_path) + .starmap(cleanup) + .compute() + ) - logger.info(f'Done uploading issues for {period}') + logger.info("Done uploading issues for %s", period) processed_issues = list(issue_bag) random.shuffle(processed_issues) @@ -340,7 +338,7 @@ def import_issues( chunks = chunk(processed_issues, 400) for chunk_n, chunk_of_issues in enumerate(chunks): - logger.info(f'Processing chunk {chunk_n} of pages for {period}') + logger.info("Processing chunk %s of pages for %s", chunk_n, period) pages_bag = ( db.from_sequence(chunk_of_issues, partition_size=2) @@ -350,42 +348,48 @@ def import_issues( .map_partitions(serialize_pages, output_dir=out_dir) ) - pages_out_dir = os.path.join(out_dir, 'pages') + pages_out_dir = os.path.join(out_dir, "pages") Path(pages_out_dir).mkdir(exist_ok=True) - logger.info(f'Start compressing and uploading pages ' - f'of chunk {chunk_n} for {period}') + logger.info( + "Start compressing and uploading pages of chunk %s for %s", + chunk_n, + period, + ) pages_bag = ( pages_bag.groupby( - lambda x: canonical_path( - x[0], path_type='dir' - ).replace('/', '-') + lambda x: canonical_path(x[0], path_type="dir").replace("/", "-") ) .starmap( - compress_pages, - suffix='pages', - output_dir=pages_out_dir, - failed_log=failed_log_path + compress_pages, + suffix="pages", + output_dir=pages_out_dir, + failed_log=failed_log_path, ) - .starmap(upload_pages,bucket_name=s3_bucket,failed_log=failed_log_path) - .starmap(cleanup) + .starmap( + upload_pages, bucket_name=s3_bucket, failed_log=failed_log_path + ) + .starmap(cleanup) .compute() ) - logger.info(f'Done compressing and uploading pages ' - f'of chunk {chunk_n} for {period}') + logger.info( + "Done compressing and uploading pages of chunk %s for %s", + chunk_n, + period, + ) - # free some dask memory + # free some dask memory if client: # if client is defined here - client.cancel(issue_bag) + client.cancel(issue_bag) else: del issue_bag remove_filelocks(out_dir) # finalize and compute the manifest - manifest.compute(export_to_git_and_s3 = True) + manifest.compute(export_to_git_and_s3=True) # manifest.validate_and_export_manifest(push_to_git=False) if temp_dir is not None and os.path.isdir(temp_dir): @@ -403,7 +407,7 @@ def compress_pages( json_files: list[str], output_dir: str, suffix: str = "", - failed_log: str | None = None + failed_log: str | None = None, ) -> Tuple[str, str]: """Merge a set of JSON line files into a single compressed archive. @@ -416,32 +420,29 @@ def compress_pages( Returns: Tuple[str, str]: Sorting key [0] and path to serialized file [1]. """ - newspaper, year, month, day, edition = key.split('-') + newspaper, year, month, day, edition = key.split("-") suffix_string = "" if suffix == "" else f"-{suffix}" - filename = ( - f'{newspaper}-{year}-{month}-{day}-{edition}' - f'{suffix_string}.jsonl.bz2' - ) + filename = f"{newspaper}-{year}-{month}-{day}-{edition}{suffix_string}.jsonl.bz2" filepath = os.path.join(output_dir, filename) - logger.info(f'Compressing {len(json_files)} JSON files into {filepath}') + logger.info("Compressing %s JSON files into %s", len(json_files), filepath) - with smart_open_function(filepath, 'wb') as fout: + with smart_open_function(filepath, "wb") as fout: writer = jsonlines.Writer(fout) items_count = 0 for issue, json_file in json_files: - with open(json_file, 'r') as inpf: + with open(json_file, "r", encoding="utf-8") as inpf: try: item = json.load(inpf) writer.write(item) items_count += 1 except JSONDecodeError as e: - logger.error(f'Reading data from {json_file} failed') + logger.error("Reading data from %s failed", json_file) logger.exception(e) write_error(filepath, e, failed_log) logger.info( - f'Written {items_count} docs from {json_file} to {filepath}' + "Written %s docs from %s to %s", items_count, json_file, filepath ) writer.close() @@ -461,9 +462,11 @@ def compress_issues( generated issues. The compressed ``.bz2`` output file is a JSON-line file, where each line corresponds to an individual and issue document in the canonical format. + Finally, yearly statistics are computed on the issues and included in the + returned values. Args: - key (Tuple[str, int]): Newspaper ID and year of input issues + key (Tuple[str, int]): Newspaper ID and year of input issues (e.g. `(GDL, 1900)`). issues (list[NewspaperIssue]): A list of `NewspaperIssue` instances. output_dir (str | None, optional): Output directory. Defaults to None. @@ -471,47 +474,45 @@ def compress_issues( instantiation was not successful. Defaults to None. Returns: - Tuple[str, str]: Label following the template `-` and - the path to the the compressed `.bz2` file. - TODO: add update + Tuple[str, str]: Label following the template `-`, the path to + the the compressed `.bz2` file, and the statistics computed on the issues. """ newspaper, year = key - filename = f'{newspaper}-{year}-issues.jsonl.bz2' + filename = f"{newspaper}-{year}-issues.jsonl.bz2" filepath = os.path.join(output_dir, filename) - logger.info(f'Compressing {len(issues)} JSON files into {filepath}') + logger.info("Compressing %s JSON files into %s", len(issues), filepath) # put a file lock to avoid the overwriting of files due to parallelization lock = FileLock(filepath + ".lock", timeout=13) items = [issue.issue_data for issue in issues] try: with lock: - with smart_open_function(filepath, 'ab') as fout: + with smart_open_function(filepath, "ab") as fout: writer = jsonlines.Writer(fout) - #items = [issue.issue_data for issue in issues] + # items = [issue.issue_data for issue in issues] writer.write_all(items) - logger.info(f'Written {len(items)} issues to {filepath}') + logger.info("Written %s issues to %s", len(items), filepath) writer.close() except Exception as e: - logger.error(f"Error for {filepath}") - logger.exception(e) + logger.error("Error for %s: %s", filepath, e) write_error(filepath, e, failed_log) # Once the issues were written without issues, add their info to the manifest yearly_stats = [] for i in items: yearly_stats.append(counts_for_canonical_issue(i)) - #manifest.add_by_title_year(newspaper, year, counts_for_canonical_issue(i)) + # manifest.add_by_title_year(newspaper, year, counts_for_canonical_issue(i)) - return f'{newspaper}-{year}', filepath, yearly_stats + return f"{newspaper}-{year}", filepath, yearly_stats def upload_issues( sort_key: str, filepath: str, bucket_name: str | None = None, - failed_log: str | None = None + failed_log: str | None = None, ) -> Tuple[bool, str]: """Upload an issues JSON-line file to a given S3 bucket. @@ -522,39 +523,36 @@ def upload_issues( filepath (str): Path of the file to upload to S3. bucket_name (str | None, optional): Name of S3 bucket where to upload the file. Defaults to None. - TODO: update docstring + failed_log (str | None, optional): Path to file where to log errors. + Returns: Tuple[bool, str]: Whether the upload was successful and the path to the uploaded file. """ # create connection with bucket # copy contents to s3 key - newspaper, year = sort_key.split('-') - key_name = "{}/{}/{}".format( - newspaper, - "issues", - os.path.basename(filepath) - ) + newspaper, _ = sort_key.split("-") + key_name = "{}/{}/{}".format(newspaper, "issues", os.path.basename(filepath)) s3 = get_s3_resource() if bucket_name is not None: try: bucket = s3.Bucket(bucket_name) bucket.upload_file(filepath, key_name) - logger.info(f'Uploaded {filepath} to {key_name}') + logger.info("Uploaded %s to %s", filepath, key_name) return True, filepath - except Exception as e: - logger.error(f'The upload of {filepath} failed with error {e}') + except BotoCoreError as e: + logger.error("The upload of %s failed with error %s", filepath, e) write_error(filepath, e, failed_log) else: - logger.info(f'Bucket name is None, not uploading issue {filepath}.') + logger.info("Bucket name is None, not uploading issue %s.", filepath) return False, filepath -def upload_pages( +def upload_pages( sort_key: str, filepath: str, bucket_name: str | None = None, - failed_log: str | None = None + failed_log: str | None = None, ) -> Tuple[bool, str]: """Upload a page JSON file to a given S3 bucket. @@ -563,31 +561,31 @@ def upload_pages( filepath (str): Path of the file to upload to S3. bucket_name (str | None, optional): Name of S3 bucket where to upload the file. Defaults to None. - TODO: update docstring + failed_log (str | None, optional): Path to file where to log errors. + Returns: Tuple[bool, str]: Whether the upload was successful and the path to the uploaded file. """ # create connection with bucket # copy contents to s3 key - newspaper, year, month, day, edition = sort_key.split('-') + newspaper, year, _, _, _ = sort_key.split("-") key_name = "{}/pages/{}/{}".format( - newspaper, - f'{newspaper}-{year}', - os.path.basename(filepath) + newspaper, f"{newspaper}-{year}", os.path.basename(filepath) ) s3 = get_s3_resource() if bucket_name is not None: try: bucket = s3.Bucket(bucket_name) bucket.upload_file(filepath, key_name) - logger.info(f'Uploaded {filepath} to {key_name}') + logger.info("Uploaded %s to %s", filepath, key_name) return True, filepath - except Exception as e: - logger.error(f'The upload of {filepath} failed with error {e}') + except BotoCoreError as e: + logger.error("The upload of %s failed with error %s", filepath, e) write_error(filepath, e, failed_log) else: - logger.info(f'Bucket name is None, not uploading page {filepath}.') + logger.info("Bucket name is None, not uploading page %s.", filepath) + return False, filepath @@ -604,4 +602,6 @@ def remove_filelocks(output_dir: str) -> None: if file.endswith(".lock"): os.remove(os.path.join(output_dir, file)) except FileNotFoundError as e: - logger.error("File %s could not be removed as it does not exist: %s.", file, e) + logger.error( + "File %s could not be removed as it does not exist: %s.", file, e + ) From ac0f903be46bf1b54a4fbba3a02c82555449b999 Mon Sep 17 00:00:00 2001 From: piconti Date: Mon, 11 Mar 2024 18:37:34 +0100 Subject: [PATCH 32/53] make progress in unification of code according to pylint --- text_importer/importers/classes.py | 58 +- text_importer/importers/lux/classes.py | 550 +++++++++---------- text_importer/importers/mets_alto/alto.py | 105 ++-- text_importer/importers/mets_alto/classes.py | 115 ++-- text_importer/importers/mets_alto/mets.py | 48 +- text_importer/importers/swa/classes.py | 92 ++-- text_importer/importers/tetml/classes.py | 21 +- 7 files changed, 489 insertions(+), 500 deletions(-) diff --git a/text_importer/importers/classes.py b/text_importer/importers/classes.py index d6ce27aa..cc0fc4c6 100644 --- a/text_importer/importers/classes.py +++ b/text_importer/importers/classes.py @@ -44,9 +44,9 @@ class NewspaperIssue(ABC): pages (list): List of :obj:`NewspaperPage` instances from this issue. rights (str): Access rights applicable to this issue. """ - + def __init__(self, issue_dir: IssueDir) -> None: - self.id = canonical_path(issue_dir, path_type='dir').replace('/', '-') + self.id = canonical_path(issue_dir, path_type="dir").replace("/", "-") self.edition = issue_dir.edition self.journal = issue_dir.journal self.path = issue_dir.path @@ -55,21 +55,19 @@ def __init__(self, issue_dir: IssueDir) -> None: self._notes = [] self.pages = [] self.rights = issue_dir.rights - + @abstractmethod def _find_pages(self) -> None: """Detect and create the issue pages using the relevant Alto XML files. Created :obj:`NewspaperPage` instances are added to :attr:`pages`. """ - pass - + @property def issuedir(self) -> IssueDir: - """`IssueDir`: IssueDirectory corresponding to this issue. - """ + """`IssueDir`: IssueDirectory corresponding to this issue.""" return IssueDir(self.journal, self.date, self.edition, self.path) - + def to_json(self) -> str: """Validate ``self.issue_data`` & serialize it to string. @@ -88,7 +86,7 @@ class NewspaperPage(ABC): Each text importer needs to define a subclass of ``NewspaperPage`` which specifies the logic to handle OCR data in a given format (e.g. Alto). - Args: + Args: _id (str): Canonical Page ID (e.g. ``GDL-1900-01-02-a-p0004``). number (int): Page number. @@ -98,13 +96,13 @@ class NewspaperPage(ABC): page_data (dict[str, Any]): Page data according to canonical format. issue (NewspaperIssue | None): Issue this page is from. """ - + def __init__(self, _id: str, number: int) -> None: self.id = _id self.number = number self.page_data = {} self.issue = None - + def to_json(self) -> str: """Validate ``self.page.data`` & serialize it to string. @@ -115,7 +113,7 @@ def to_json(self) -> str: """ page = Pageschema(**self.page_data) return page.serialize() - + @abstractmethod def add_issue(self, issue: NewspaperIssue) -> None: """Add to a page object its parent, i.e. the newspaper issue. @@ -126,8 +124,7 @@ def add_issue(self, issue: NewspaperIssue) -> None: Args: issue (NewspaperIssue): Newspaper issue containing this page. """ - pass - + @abstractmethod def parse(self) -> None: """Process the page XML file and transform into canonical Page format. @@ -137,12 +134,11 @@ def parse(self) -> None: upon creation of the page object, but only once the ``parse()`` method is called. """ - pass class ZipArchive(object): """Archive document to be temporarily unpacked. - + It is usually unpacked into a temp directory to avoid jamming the memory. Args: @@ -153,14 +149,14 @@ class ZipArchive(object): name_list (list[str]): List of filenames in the archive. dir (str): Path to directory in which archive contents are. """ - + def __init__(self, archive: ZipFile, temp_dir: str) -> None: - logger.debug(f"Extracting archive in {temp_dir}") + logger.debug("Extracting archive in %s", temp_dir) self.name_list = archive.namelist() self.dir = temp_dir self.extract_archive(archive) archive.close() - + def extract_archive(self, archive: ZipFile) -> None: """Recursively extract all files from the archive. @@ -168,23 +164,22 @@ def extract_archive(self, archive: ZipFile) -> None: archive (ZipFile): Archive to unpack. """ if not os.path.exists(self.dir): - logger.debug(f"Creating {self.dir}") + logger.debug("Creating %s", self.dir) try: os.makedirs(self.dir) - except FileExistsError as e: + except FileExistsError: pass for f in archive.filelist: if f.file_size > 0: try: archive.extract(f.filename, path=self.dir) - except FileExistsError as e: + except FileExistsError: pass - + def namelist(self) -> list[str]: - """list[str]: List of filenames in the archive. - """ + """list[str]: List of filenames in the archive.""" return self.name_list - + def read(self, file: str) -> bytes: """Read given file in binary mode. @@ -195,17 +190,16 @@ def read(self, file: str) -> bytes: bytes: File contents as bytes. """ path = os.path.join(self.dir, file) - with open(path, 'rb') as f: + with open(path, "rb") as f: f_bytes = f.read() return f_bytes - + def cleanup(self) -> None: - """Recursively delete the unpacked archive. - """ - logging.info(f"Deleting archive {self.dir}") + """Recursively delete the unpacked archive.""" + logging.info("Deleting archive %s", self.dir) shutil.rmtree(self.dir) prev_dir = os.path.split(self.dir)[0] while os.path.isdir(prev_dir) and len(os.listdir(prev_dir)) == 0: - logging.info(f"Deleting {prev_dir}") + logging.info("Deleting %s", prev_dir) os.rmdir(prev_dir) prev_dir = os.path.split(prev_dir)[0] diff --git a/text_importer/importers/lux/classes.py b/text_importer/importers/lux/classes.py index 5b9262d3..b566712f 100644 --- a/text_importer/importers/lux/classes.py +++ b/text_importer/importers/lux/classes.py @@ -15,22 +15,28 @@ from bs4 import BeautifulSoup from bs4.element import NavigableString, Tag -from text_importer.importers import (CONTENTITEM_TYPE_ADVERTISEMENT, - CONTENTITEM_TYPE_ARTICLE, - CONTENTITEM_TYPE_IMAGE, - CONTENTITEM_TYPE_OBITUARY, - CONTENTITEM_TYPE_TABLE, - CONTENTITEM_TYPE_WEATHER) -from text_importer.importers.lux.helpers import (convert_coordinates, - encode_ark, - section_is_article, - div_has_body, - find_section_articles, - remove_section_cis) +from text_importer.importers import ( + CONTENTITEM_TYPE_ADVERTISEMENT, + CONTENTITEM_TYPE_ARTICLE, + CONTENTITEM_TYPE_IMAGE, + CONTENTITEM_TYPE_OBITUARY, + CONTENTITEM_TYPE_TABLE, + CONTENTITEM_TYPE_WEATHER, +) +from text_importer.importers.lux.helpers import ( + convert_coordinates, + encode_ark, + section_is_article, + div_has_body, + find_section_articles, + remove_section_cis, +) from text_importer.importers.mets_alto.alto import parse_style -from text_importer.importers.mets_alto import (MetsAltoNewspaperIssue, - MetsAltoNewspaperPage, - parse_mets_amdsec) +from text_importer.importers.mets_alto import ( + MetsAltoNewspaperIssue, + MetsAltoNewspaperPage, + parse_mets_amdsec, +) from text_importer.utils import get_issue_schema, get_page_schema, get_reading_order IssueSchema = get_issue_schema() @@ -50,7 +56,7 @@ class LuxNewspaperPage(MetsAltoNewspaperPage): filename (str): Name of the Alto XML page file. basedir (str): Base directory where Alto files are located. encoding (str, optional): Encoding of XML file. Defaults to 'utf-8'. - + Attributes: id (str): Canonical Page ID (e.g. ``GDL-1900-01-02-a-p0004``). number (int): Page number. @@ -60,80 +66,75 @@ class LuxNewspaperPage(MetsAltoNewspaperPage): basedir (str): Base directory where Alto files are located. encoding (str, optional): Encoding of XML file. """ - + def _parse_font_styles(self) -> None: - """Parse section `` of the XML file to extract the fonts. - """ + """Parse section `` of the XML file to extract the fonts.""" style_divs = self.xml.findAll("TextStyle") - + styles = [] for d in style_divs: styles.append(parse_style(d)) - - self.page_data['s'] = styles - + + self.page_data["s"] = styles + def add_issue(self, issue: MetsAltoNewspaperIssue) -> None: self.issue = issue encoded_ark_id = encode_ark(self.issue.ark_id) - iiif_base_link = f'{IIIF_ENDPOINT_URI}/{encoded_ark_id}' - iiif_link = f'{iiif_base_link}%2fpages%2f{self.number}' - self.page_data['iiif_img_base_uri'] = iiif_link + iiif_base_link = f"{IIIF_ENDPOINT_URI}/{encoded_ark_id}" + iiif_link = f"{iiif_base_link}%2fpages%2f{self.number}" + self.page_data["iiif_img_base_uri"] = iiif_link self._parse_font_styles() - - def _convert_coordinates( - self, page_regions: list[dict] - ) -> tuple[bool, list[dict]]: + + def _convert_coordinates(self, page_regions: list[dict]) -> tuple[bool, list[dict]]: success = False try: img_props = self.issue.image_properties[self.number] - x_res = img_props['x_resolution'] - y_res = img_props['y_resolution'] - + x_res = img_props["x_resolution"] + y_res = img_props["y_resolution"] + for region in page_regions: - - x, y, w, h = region['c'] - region['c'] = convert_coordinates(x, y, w, h, - x_res, y_res) - - logger.debug(f"Page {self.number}: " - f"{x},{y},{w},{h} => {region['c']}") - - for paragraph in region['p']: - - x, y, w, h = paragraph['c'] - paragraph['c'] = convert_coordinates(x, y, w, h, - x_res, y_res) - - logger.debug(f"(para) Page {self.number}: " - f"{x},{y},{w},{h} => {paragraph['c']}") - - for line in paragraph['l']: - - x, y, w, h = line['c'] - line['c'] = convert_coordinates(x, y, w, h, - x_res, y_res) - - logger.debug(f"(line) Page {self.number}: " - f"{x},{y},{w},{h} => {paragraph['c']}") - - for token in line['t']: - x, y, w, h = token['c'] - token['c'] = convert_coordinates(x, y, w, h, - x_res, y_res) - logger.debug(f"(token) Page {self.number}: " - f"{x},{y},{w},{h} => {token['c']}") + + x, y, w, h = region["c"] + region["c"] = convert_coordinates(x, y, w, h, x_res, y_res) + + msg = f"Page {self.number}: {x},{y},{w},{h} => {region['c']}" + logger.debug(msg) + + for paragraph in region["p"]: + + x, y, w, h = paragraph["c"] + paragraph["c"] = convert_coordinates(x, y, w, h, x_res, y_res) + + msg = f"(para) Page {self.number}: {x},{y},{w},{h} => {paragraph['c']}" + logger.debug(msg) + + for line in paragraph["l"]: + + x, y, w, h = line["c"] + line["c"] = convert_coordinates(x, y, w, h, x_res, y_res) + + msg = f"(line) Page {self.number}: {x},{y},{w},{h} => {paragraph['c']}" + logger.debug(msg) + + for token in line["t"]: + x, y, w, h = token["c"] + token["c"] = convert_coordinates(x, y, w, h, x_res, y_res) + + msg = f"(token) Page {self.number}: {x},{y},{w},{h} => {token['c']}" + logger.debug(msg) success = True except Exception as e: - logger.error(f"Error {e} occurred when converting coordinates " - f"for {self.id}") - finally: - return success, page_regions + logger.error( + "Error %s occurred when converting coordinates for %s", e, self.id + ) + + return success, page_regions class LuxNewspaperIssue(MetsAltoNewspaperIssue): """Class representing an issue in BNL data. - All functions defined in this child class are specific to parsing BNL + All functions defined in this child class are specific to parsing BNL (Luxembourg National Library) Mets/Alto format. Args: @@ -152,7 +153,7 @@ class LuxNewspaperIssue(MetsAltoNewspaperIssue): coordinates to iiif format compliant ones. ark_id (int): Issue ARK identifier, for the issue's pages' iiif links. """ - + def _find_pages(self) -> None: """Detect and create the issue pages using the relevant Alto XML files. @@ -163,40 +164,41 @@ def _find_pages(self) -> None: """ # get the canonical names for pages in the newspaper issue by # visiting the `text` sub-folder with the alto XML files - text_path = os.path.join(self.path, 'text') + text_path = os.path.join(self.path, "text") page_file_names = [ file for file in os.listdir(text_path) - if not file.startswith('.') and '.xml' in file - ] - + if not file.startswith(".") and ".xml" in file + ] + page_numbers = [] - page_match_exp = r'(.*?)(\d{5})(.*)' - + page_match_exp = r"(.*?)(\d{5})(.*)" + for fname in page_file_names: g = re.match(page_match_exp, fname) page_no = g.group(2) page_numbers.append(int(page_no)) - + page_canonical_names = [ - "{}-p{}".format(self.id, str(page_n).zfill(4)) - for page_n in page_numbers + "{}-p{}".format(self.id, str(page_n).zfill(4)) for page_n in page_numbers ] - + self.pages = [] - for filename, page_no, page_id in zip(page_file_names, page_numbers, - page_canonical_names): + for filename, page_no, page_id in zip( + page_file_names, page_numbers, page_canonical_names + ): try: self.pages.append( LuxNewspaperPage(page_id, page_no, filename, text_path) ) except Exception as e: - logger.error( - f'Adding page {page_no} {page_id} {filename}', - f'raised following exception: {e}' - ) + msg = ( + f"Adding page {page_no} {page_id} {filename}", + f"raised following exception: {e}", + ) + logger.error(msg) raise e - + def _parse_mets_div(self, div: Tag) -> list[dict[str, str | int]]: """Parse the children of a content item div for its legacy `parts`. @@ -211,34 +213,34 @@ def _parse_mets_div(self, div: Tag) -> list[dict[str, str | int]]: content item (role, id, fileid, page) """ parts = [] - + for child in div.children: if isinstance(child, NavigableString): continue elif isinstance(child, Tag): - type_attr = child.get('TYPE') + type_attr = child.get("TYPE") comp_role = type_attr.lower() if type_attr else None - areas = child.findAll('area') + areas = child.findAll("area") for area in areas: - comp_id = area.get('BEGIN') - comp_fileid = area.get('FILEID') - comp_page_no = int(comp_fileid.replace('ALTO', '')) - + comp_id = area.get("BEGIN") + comp_fileid = area.get("FILEID") + comp_page_no = int(comp_fileid.replace("ALTO", "")) + parts.append( { - 'comp_role': comp_role, - 'comp_id': comp_id, - 'comp_fileid': comp_fileid, - 'comp_page_no': comp_page_no + "comp_role": comp_role, + "comp_id": comp_id, + "comp_fileid": comp_fileid, + "comp_page_no": comp_page_no, } ) return parts - + def _parse_dmdsec( self, mets_doc: BeautifulSoup ) -> tuple[list[dict[str, Any]], int]: """Parse `` tags of Mets file to find some content items. - + Only articles and pictures are in this section and are identified here. Those tags' `ID` attribute should contain `ARTICLE` or `PICT`. @@ -253,40 +255,37 @@ def _parse_dmdsec( track of the item numbers. """ content_items = [] - sections = mets_doc.findAll('dmdSec') - + sections = mets_doc.findAll("dmdSec") + # sort based on the ID string to pinpoint the generated canonical IDs - sections = sorted( - sections, - key=lambda elem: elem.get('ID').split("_")[1] - ) + sections = sorted(sections, key=lambda elem: elem.get("ID").split("_")[1]) counter = 1 for section in sections: - section_id = section.get('ID') - + section_id = section.get("ID") + if "ARTICLE" in section_id or "PICT" in section_id: # Get title Info - title_elements = section.find_all('titleInfo') + title_elements = section.find_all("titleInfo") item_title = ( - title_elements[0].getText().replace('\n', ' ').strip() - if len(title_elements) > 0 else None + title_elements[0].getText().replace("\n", " ").strip() + if len(title_elements) > 0 + else None ) - #TODO: case when len(title_elements)>1 ? - + # Prepare ci metadata metadata = { - 'id': "{}-i{}".format(self.id, str(counter).zfill(4)), - 'pp': [], - 'tp': ( - CONTENTITEM_TYPE_ARTICLE - if "ARTICLE" in section_id + "id": "{}-i{}".format(self.id, str(counter).zfill(4)), + "pp": [], + "tp": ( + CONTENTITEM_TYPE_ARTICLE + if "ARTICLE" in section_id else CONTENTITEM_TYPE_IMAGE - ) + ), } - + # Find the parts try: - item_div = mets_doc.findAll('div', {'DMDID':section_id})[0] + item_div = mets_doc.findAll("div", {"DMDID": section_id})[0] parts = self._parse_mets_div(item_div) except IndexError: err_msg = f"
not found {self.path}" @@ -294,33 +293,30 @@ def _parse_dmdsec( logger.error(err_msg) parts = [] item_div = None - + if item_title: - metadata['t'] = item_title - + metadata["t"] = item_title + # Finalize the item - item = { - "m": metadata, - "l": { - "id": section_id, - "parts": parts - } - } - + item = {"m": metadata, "l": {"id": section_id, "parts": parts}} + # TODO: keep language (there may be more than one) # TODO: how to get language information for these CIs ? - if item['m']['tp'] == CONTENTITEM_TYPE_ARTICLE: - lang = section.find_all('languageTerm')[0].getText() - item['m']['l'] = lang - + if item["m"]["tp"] == CONTENTITEM_TYPE_ARTICLE: + lang = section.find_all("languageTerm")[0].getText() + item["m"]["l"] = lang + # This has been added to not consider ads as pictures - if (not ((item_div is not None) and ("PICT" in section_id) and - (item_div.get("TYPE") == "ADVERTISEMENT"))): + if not ( + (item_div is not None) + and ("PICT" in section_id) + and (item_div.get("TYPE") == "ADVERTISEMENT") + ): content_items.append(item) counter += 1 - + return content_items, counter - + def _parse_structmap_divs( self, start_counter: int, mets_doc: BeautifulSoup ) -> tuple[list[dict[str, Any]], int]: @@ -336,165 +332,156 @@ def _parse_structmap_divs( """ content_items = [] counter = start_counter - element = mets_doc.find('structMap', {'TYPE': 'LOGICAL'}) - + element = mets_doc.find("structMap", {"TYPE": "LOGICAL"}) + allowed_types = ["ADVERTISEMENT", "DEATH_NOTICE", "WEATHER"] divs = [] - + for div_type in allowed_types: - divs += element.findAll('div', {'TYPE': div_type}) - - sorted_divs = sorted( - divs, key=lambda elem: elem.get('ID') - ) - + divs += element.findAll("div", {"TYPE": div_type}) + + sorted_divs = sorted(divs, key=lambda elem: elem.get("ID")) + for div in sorted_divs: - - div_type = div.get('TYPE').lower() - if div_type == 'advertisement': + + div_type = div.get("TYPE").lower() + if div_type == "advertisement": content_item_type = CONTENTITEM_TYPE_ADVERTISEMENT - elif div_type == 'weather': + elif div_type == "weather": content_item_type = CONTENTITEM_TYPE_WEATHER - elif div_type == 'death_notice': + elif div_type == "death_notice": content_item_type = CONTENTITEM_TYPE_OBITUARY else: continue - + # TODO: how to get language information for these CIs ? # The language of those CI should be in # the DMDSEC of their parent section. metadata = { - 'id': "{}-i{}".format(self.id, str(counter).zfill(4)), - 'tp': content_item_type, - 'pp': [], - 't': div.get('LABEL') + "id": "{}-i{}".format(self.id, str(counter).zfill(4)), + "tp": content_item_type, + "pp": [], + "t": div.get("LABEL"), } - + item = { "m": metadata, "l": { "parts": self._parse_mets_div(div), # Parse the parts - "id": div.get('ID') - } + "id": div.get("ID"), + }, } content_items.append(item) counter += 1 + return content_items, counter - - def _process_image_ci( - self, ci: dict[str, Any], mets_doc: BeautifulSoup - ) -> None: + + def _process_image_ci(self, ci: dict[str, Any], mets_doc: BeautifulSoup) -> None: """Process an image content item to complete its information. Args: ci (dict[str, Any]): Image content item to be processed. mets_doc (BeautifulSoup): Contents of Mets XML file. """ - item_div = mets_doc.findAll('div', {'DMDID': ci['l']['id']}) + item_div = mets_doc.findAll("div", {"DMDID": ci["l"]["id"]}) if len(item_div) > 0: item_div = item_div[0] else: return - legacy_id = item_div.get('ID') + legacy_id = item_div.get("ID") # Image is actually table - - if item_div.get('TYPE').lower() == "table": - ci['m']['tp'] = CONTENTITEM_TYPE_TABLE - for part in ci['l']['parts']: + + if item_div.get("TYPE").lower() == "table": + ci["m"]["tp"] = CONTENTITEM_TYPE_TABLE + for part in ci["l"]["parts"]: page_no = part["comp_page_no"] - if page_no not in ci['m']['pp']: - ci['m']['pp'].append(page_no) - - elif item_div.get('TYPE').lower() == "illustration": - + if page_no not in ci["m"]["pp"]: + ci["m"]["pp"].append(page_no) + + elif item_div.get("TYPE").lower() == "illustration": + # filter content item part that is the actual image # the other part is the caption try: part = [ - part - for part in ci['l']['parts'] - if part['comp_role'] == 'image' + part for part in ci["l"]["parts"] if part["comp_role"] == "image" ][0] except IndexError as e: - err_msg = f'{legacy_id} without image subpart' + err_msg = f"{legacy_id} without image subpart" err_msg += f"; {legacy_id} has {ci['l']['parts']}" logger.error(err_msg) self._notes.append(err_msg) logger.exception(e) return - + # for each "part" open the XML file of corresponding page # get the coordinates and convert them # some imgs are in fact tables (meaning they have text # recognized) - + # find the corresponding page where it's located curr_page = None for page in self.pages: - if page.number == part['comp_page_no']: + if page.number == part["comp_page_no"]: curr_page = page - + # add the page number to the content item assert curr_page is not None - if curr_page.number not in ci['m']['pp']: - ci['m']['pp'].append(curr_page.number) - + if curr_page.number not in ci["m"]["pp"]: + ci["m"]["pp"].append(curr_page.number) + try: # parse the Alto file to fetch the coordinates page_xml = curr_page.xml - - composed_block = page_xml.find('ComposedBlock', - {"ID": part['comp_id']}) - + + composed_block = page_xml.find("ComposedBlock", {"ID": part["comp_id"]}) + if composed_block: - graphic_el = composed_block.find('GraphicalElement') - + graphic_el = composed_block.find("GraphicalElement") + if graphic_el is None: - graphic_el = page_xml.find('Illustration') + graphic_el = page_xml.find("Illustration") else: - graphic_el = page_xml.find('Illustration', - {"ID": part['comp_id']}) + graphic_el = page_xml.find("Illustration", {"ID": part["comp_id"]}) - hpos = int(graphic_el.get('HPOS')) - vpos = int(graphic_el.get('VPOS')) - width = int(graphic_el.get('WIDTH')) - height = int(graphic_el.get('HEIGHT')) + hpos = int(graphic_el.get("HPOS")) + vpos = int(graphic_el.get("VPOS")) + width = int(graphic_el.get("WIDTH")) + height = int(graphic_el.get("HEIGHT")) img_props = self.image_properties[curr_page.number] - x_resolution = img_props['x_resolution'] - y_resolution = img_props['y_resolution'] + x_resolution = img_props["x_resolution"] + y_resolution = img_props["y_resolution"] # order should be: hpos, vpos, width, height - coordinates = convert_coordinates(hpos, vpos, width, height, - x_resolution, y_resolution) - encoded_ark_id = encode_ark(self.ark_id) - iiif_base_link = f'{IIIF_ENDPOINT_URI}/{encoded_ark_id}' - ci['m']['iiif_link'] = ( - f'{iiif_base_link}%2fpages%2f{curr_page.number}/info.json' + coordinates = convert_coordinates( + hpos, vpos, width, height, x_resolution, y_resolution ) - ci['c'] = list(coordinates) - del ci['l']['parts'] + encoded_ark_id = encode_ark(self.ark_id) + iiif_base_link = f"{IIIF_ENDPOINT_URI}/{encoded_ark_id}" + ci["m"][ + "iiif_link" + ] = f"{iiif_base_link}%2fpages%2f{curr_page.number}/info.json" + ci["c"] = list(coordinates) + del ci["l"]["parts"] except Exception as e: - err_msg = 'An error occurred with {}'.format( - os.path.join( - curr_page.basedir, - curr_page.filename - ) - ) + err_msg = "An error occurred with {}".format( + os.path.join(curr_page.basedir, curr_page.filename) + ) err_msg += f" @ID {part['comp_id']} not found" logger.error(err_msg) self._notes.append(err_msg) logger.exception(e) - + def _parse_section( - self, - section: Tag, - section_div: Tag, - content_items: list[dict[str, Any]], - counter: int + self, + section: Tag, + section_div: Tag, + content_items: list[dict[str, Any]], + counter: int, ) -> dict[str, Any]: """Reconstruct the section using the div and previously created CIs. - - In the `l` field of the ci, an additional field `canonical_parts` - points to articles that were added to this section. + + In the `l` field of the ci, an additional field `canonical_parts` + points to articles that were added to this section. (Bugfix done by Edoardo) Args: @@ -506,37 +493,40 @@ def _parse_section( Returns: dict[str, Any]: Content item of the reconstructed section. """ - title_elements = section.find_all('titleInfo') - - item_title = title_elements[0].getText().replace('\n', ' ') \ - .strip() if len(title_elements) > 0 else None - + title_elements = section.find_all("titleInfo") + + item_title = ( + title_elements[0].getText().replace("\n", " ").strip() + if len(title_elements) > 0 + else None + ) + metadata = { - 'id': "{}-i{}".format(self.id, str(counter).zfill(4)), - 'pp': [], - 'tp': CONTENTITEM_TYPE_ARTICLE - } + "id": "{}-i{}".format(self.id, str(counter).zfill(4)), + "pp": [], + "tp": CONTENTITEM_TYPE_ARTICLE, + } if item_title: - metadata['t'] = item_title - + metadata["t"] = item_title + parts = self._parse_mets_div(section_div) - + old_cis = find_section_articles(section_div, content_items) item = { "m": metadata, "l": { - "id": section_div.get('DMDID'), + "id": section_div.get("DMDID"), "parts": parts, - "canonical_parts": old_cis - } - } + "canonical_parts": old_cis, + }, + } return item - + def _parse_sections( - self, - content_items: list[dict[str, Any]], - start_counter: int, - mets_doc: BeautifulSoup + self, + content_items: list[dict[str, Any]], + start_counter: int, + mets_doc: BeautifulSoup, ) -> list[dict[str, Any]]: """Reconstruct all the sections from the METS file (bugfix by Edoardo). @@ -549,89 +539,87 @@ def _parse_sections( list[dict[str, Any]]: Updated content items """ counter = start_counter - sections = mets_doc.findAll('dmdSec') - - sections = sorted( - sections, - key=lambda elem: elem.get('ID').split("_")[1] - ) + sections = mets_doc.findAll("dmdSec") + + sections = sorted(sections, key=lambda elem: elem.get("ID").split("_")[1]) # First look for sections and get their ID new_sections = [] for section in sections: - section_id = section.get('ID') - if 'SECT' in section_id: - div = mets_doc.find('div', {"DMDID": section_id}) # Get div + section_id = section.get("ID") + if "SECT" in section_id: + div = mets_doc.find("div", {"DMDID": section_id}) # Get div if div is None: err_msg = f"
not found {self.path}" self._notes.append(err_msg) logger.error(err_msg) continue if div_has_body(div) and section_is_article(div): - new_section = self._parse_section(section, div, - content_items, counter) + new_section = self._parse_section( + section, div, content_items, counter + ) new_sections.append(new_section) counter += 1 return new_sections - + def _parse_mets(self) -> None: """Parse the Mets XML file corresponding to this issue. - Once the :attr:`issue_data` is created, containing all the relevant + Once the :attr:`issue_data` is created, containing all the relevant information in the canonical Issue format, the `LuxNewspaperIssue` instance is ready for serialization. TODO: correct parsing to prevent need of reconstruction (if possible). """ mets_doc = self.xml - + # explain self.image_properties = parse_mets_amdsec( - mets_doc, x_res='xOpticalResolution', y_res='yOpticalResolution' + mets_doc, x_res="xOpticalResolution", y_res="yOpticalResolution" ) - + # First find `ARTICLE` and `PICTURE` content items content_items, counter = self._parse_dmdsec(mets_doc) # Then find other content items new_cis, counter = self._parse_structmap_divs(counter, mets_doc) content_items += new_cis - + # Reconstruct sections section_cis = self._parse_sections(content_items, counter, mets_doc) # Remove cis that are contained in sections content_items, removed = remove_section_cis(content_items, section_cis) - - logger.debug("Removed {} as they are in sections".format(removed)) + + logger.debug("Removed %s as they are in sections", removed) # Add sections to CIs content_items += section_cis # Set ark_id - ark_link = mets_doc.find('mets').get('OBJID') - self.ark_id = ark_link.replace('https://persist.lu/ark:/', 'ark:') - + ark_link = mets_doc.find("mets").get("OBJID") + self.ark_id = ark_link.replace("https://persist.lu/ark:/", "ark:") + # compute the reading order for the issue's items reading_order_dict = get_reading_order(content_items) for ci in content_items: - + # ci['l']['parts'] = self._parse_mets_div(item_div) - - if ci['m']['tp'] == 'image': + + if ci["m"]["tp"] == "image": self._process_image_ci(ci, mets_doc) - elif ci['m']['tp']: - for part in ci['l']['parts']: + elif ci["m"]["tp"]: + for part in ci["l"]["parts"]: page_no = part["comp_page_no"] - if page_no not in ci['m']['pp']: - ci['m']['pp'].append(page_no) + if page_no not in ci["m"]["pp"]: + ci["m"]["pp"].append(page_no) # add the reading order - ci['m']['ro'] = reading_order_dict[ci['m']['id']] - + ci["m"]["ro"] = reading_order_dict[ci["m"]["id"]] + self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), "id": self.id, "i": content_items, "ar": self.rights, - "pp": [p.id for p in self.pages] + "pp": [p.id for p in self.pages], } - + if self._notes: self.issue_data["n"] = "\n".join(self._notes) diff --git a/text_importer/importers/mets_alto/alto.py b/text_importer/importers/mets_alto/alto.py index 7336b37b..06ad84ec 100644 --- a/text_importer/importers/mets_alto/alto.py +++ b/text_importer/importers/mets_alto/alto.py @@ -18,11 +18,11 @@ def distill_coordinates(element: Tag) -> list[int]: list[int]: An ordered list of coordinates (``x``, ``y``, ``width``, ``height``). """ - hpos = int(float(element.get('HPOS'))) - vpos = int(float(element.get('VPOS'))) - width = int(float(element.get('WIDTH'))) - height = int(float(element.get('HEIGHT'))) - + hpos = int(float(element.get("HPOS"))) + vpos = int(float(element.get("VPOS"))) + width = int(float(element.get("WIDTH"))) + height = int(float(element.get("HEIGHT"))) + # NB: these coordinates need to be converted return [hpos, vpos, width, height] @@ -35,110 +35,103 @@ def parse_textline(element: Tag) -> tuple[dict, list[str]]: Returns: tuple[dict, list[str]]: Parsed lines or text in the canonical format - and notes about potential missing token coordinates. + and notes about potential missing token coordinates. """ line = {} - line['c'] = distill_coordinates(element) + line["c"] = distill_coordinates(element) tokens = [] - + notes = [] for child in element.children: - + if isinstance(child, bs4.element.NavigableString): continue - - if child.name == 'String': - + + if child.name == "String": + # Here we do this in case coordinates are not found for this String try: coords = distill_coordinates(child) - except TypeError as e: + except TypeError: notes.append(f"Token {child.get('ID')} does not have coordinates") coords = None continue - token = { - 'c': coords, - 'tx': child.get('CONTENT') - } - - if child.get('SUBS_TYPE') == "HypPart1": + token = {"c": coords, "tx": child.get("CONTENT")} + + if child.get("SUBS_TYPE") == "HypPart1": # token['tx'] += u"\u00AD" - token['tx'] += "-" - token['hy'] = True - elif child.get('SUBS_TYPE') == "HypPart2": - token['nf'] = child.get('SUBS_CONTENT') - + token["tx"] += "-" + token["hy"] = True + elif child.get("SUBS_TYPE") == "HypPart2": + token["nf"] = child.get("SUBS_CONTENT") + tokens.append(token) - - line['t'] = tokens + + line["t"] = tokens return line, notes -def parse_printspace(element: Tag, mappings: dict[str, str] +def parse_printspace( + element: Tag, mappings: dict[str, str] ) -> tuple[list[dict], list[str]]: """Parse the ```` element of an ALTO XML document. This element contains all the OCR information about the content items of - a page, up to the lowest level of the hierarchy: the regions, paragraphs, + a page, up to the lowest level of the hierarchy: the regions, paragraphs, lines and tokens, each with their corresponding coordinates. - + Args: element (Tag): Input XML element (````). - mappings (dict[str, str]): Mapping from OCR component ids to their + mappings (dict[str, str]): Mapping from OCR component ids to their corresponding canonical Content Item ID. Returns: tuple[list[dict], list[str]]: List of page regions in the canonical - format and notes about potential parsing problems. + format and notes about potential parsing problems. """ - + regions = [] notes = [] # in case of a blank page, the PrintSpace element is not found thus # it will be none if element: for block in element.children: - + if isinstance(block, bs4.element.NavigableString): continue - - block_id = block.get('ID') + + block_id = block.get("ID") if block_id in mappings: part_of_contentitem = mappings[block_id] else: part_of_contentitem = None - + coordinates = distill_coordinates(block) - + tmp = [ parse_textline(line_element) - for line_element in block.findAll('TextLine') + for line_element in block.findAll("TextLine") ] - + if len(tmp) > 0: lines, new_notes = list(zip(*tmp)) new_notes = [i for n in new_notes for i in n] else: lines, new_notes = [], [] - - paragraph = { - "c": coordinates, - "l": lines - } - - region = { - "c": coordinates, - "p": [paragraph] - } - + + paragraph = {"c": coordinates, "l": lines} + + region = {"c": coordinates, "p": [paragraph]} + if part_of_contentitem: - region['pOf'] = part_of_contentitem - + region["pOf"] = part_of_contentitem + notes += new_notes regions.append(region) return regions, notes + def parse_style(style_div: Tag) -> dict[str, float | str]: """Parse the font-style information in the ALTO files (for BNL and BNF). @@ -152,14 +145,14 @@ def parse_style(style_div: Tag) -> dict[str, float | str]: font_size = style_div.get("FONTSIZE") font_style = style_div.get("FONTSTYLE") font_id = style_div.get("ID") - + font_name = font_family if font_style is not None: font_name = "{}-{}".format(font_name, font_style) - + style = { "id": font_id, - "fs": float(font_size) if font_size!='' else None, - "f": font_name + "fs": float(font_size) if font_size != "" else None, + "f": font_name, } return style diff --git a/text_importer/importers/mets_alto/classes.py b/text_importer/importers/mets_alto/classes.py index df2a0d5e..8b6a0367 100644 --- a/text_importer/importers/mets_alto/classes.py +++ b/text_importer/importers/mets_alto/classes.py @@ -28,7 +28,7 @@ class MetsAltoNewspaperPage(NewspaperPage): """Newspaper page in generic Alto format. - Note: + Note: New Mets/Alto importers should sub-classes this class and implement its abstract methods (i.e. :meth:`~MetsAltoNewspaperPage.add_issue()`). @@ -48,19 +48,25 @@ class MetsAltoNewspaperPage(NewspaperPage): basedir (str): Base directory where Alto files are located. encoding (str, optional): Encoding of XML file. """ - - def __init__(self, _id: str, number: int, filename: str, - basedir: str, encoding: str = 'utf-8') -> None: + + def __init__( + self, + _id: str, + number: int, + filename: str, + basedir: str, + encoding: str = "utf-8", + ) -> None: super().__init__(_id, number) self.filename = filename self.basedir = basedir self.encoding = encoding self.page_data = { - 'id': _id, - 'cdt': strftime("%Y-%m-%d %H:%M:%S"), - 'r': [] # here go the page regions + "id": _id, + "cdt": strftime("%Y-%m-%d %H:%M:%S"), + "r": [], # here go the page regions } - + @property def xml(self) -> BeautifulSoup: """Read Alto XML file of the page and create a BeautifulSoup object. @@ -69,26 +75,30 @@ def xml(self) -> BeautifulSoup: BeautifulSoup: BeautifulSoup object with Alto XML of the page. """ alto_xml_path = os.path.join(self.basedir, self.filename) - + # In case of I/O error, retry twice, tries = 3 for i in range(tries): try: - with open(alto_xml_path, 'r', encoding=self.encoding) as f: + with open(alto_xml_path, "r", encoding=self.encoding) as f: raw_xml = f.read() - - alto_doc = BeautifulSoup(raw_xml, 'xml') + + alto_doc = BeautifulSoup(raw_xml, "xml") return alto_doc except IOError as e: - if i < tries - 1: # i is zero indexed - logger.error(f"Caught error for {self.id}, retrying (up to {tries} times) to read xml file. Error: {e}.") + if i < tries - 1: # i is zero indexed + msg = ( + f"Caught error for {self.id}, retrying (up to {tries} " + f"times) to read xml file. Error: {e}." + ) + logger.error(msg) continue else: - logger.error(f"Reached maximum amount of errors for {self.id}.") + logger.error("Reached maximum amount of errors for %s.", self.id) raise e - - def _convert_coordinates(self, page_regions: list[dict[str, Any]] + def _convert_coordinates( + self, page_regions: list[dict[str, Any]] ) -> tuple[bool, list[dict[str, Any]]]: """Convert region coordinates to iiif format if possible. @@ -101,35 +111,35 @@ def _convert_coordinates(self, page_regions: list[dict[str, Any]] are in iiif format and page regions. """ return True, page_regions - + @abstractmethod def add_issue(self, issue: NewspaperIssue) -> None: pass - + def parse(self) -> None: doc = self.xml - + mappings = {} - for ci in self.issue.issue_data['i']: - ci_id = ci['m']['id'] - if 'parts' in ci['l']: - for part in ci['l']['parts']: - mappings[part['comp_id']] = ci_id - - pselement = doc.find('PrintSpace') + for ci in self.issue.issue_data["i"]: + ci_id = ci["m"]["id"] + if "parts" in ci["l"]: + for part in ci["l"]["parts"]: + mappings[part["comp_id"]] = ci_id + + pselement = doc.find("PrintSpace") page_regions, notes = alto.parse_printspace(pselement, mappings) - self.page_data['cc'], self.page_data["r"] = self._convert_coordinates( + self.page_data["cc"], self.page_data["r"] = self._convert_coordinates( page_regions ) # Add notes for missing coordinates in SWA if len(notes) > 0: - self.page_data['n'] = notes + self.page_data["n"] = notes class MetsAltoNewspaperIssue(NewspaperIssue): """Newspaper issue in generic Mets/Alto format. - Note: + Note: New Mets/Alto importers should sub-class this class and implement its abstract methods (i.e. ``_find_pages()``, ``_parse_mets()``). @@ -145,29 +155,27 @@ class MetsAltoNewspaperIssue(NewspaperIssue): issue_data (dict[str, Any]): Issue data according to canonical format. pages (list): list of :obj:`NewspaperPage` instances from this issue. rights (str): Access rights applicable to this issue. - image_properties (dict[str, Any]): metadata allowing to convert region + image_properties (dict[str, Any]): metadata allowing to convert region OCR/OLR coordinates to iiif format compliant ones. ark_id (int): Issue ARK identifier, for the issue's pages' iiif links. """ - + def __init__(self, issue_dir: IssueDir) -> None: super().__init__(issue_dir) # create the canonical issue id self.image_properties = {} self.ark_id = None - + self._find_pages() self._parse_mets() - + @abstractmethod def _find_pages(self) -> None: pass - + @abstractmethod def _parse_mets(self) -> None: - """Parse the Mets XML file corresponding to this issue. - """ - pass + """Parse the Mets XML file corresponding to this issue.""" @property def xml(self) -> BeautifulSoup: @@ -183,7 +191,7 @@ def xml(self) -> BeautifulSoup: `mets.xml` in its file name and located in the directory `self.path`. Individual importers can overwrite this behavior if necessary. - + Returns: BeautifulSoup: BeautifulSoup object with Mets XML of the issue. """ @@ -193,27 +201,28 @@ def xml(self) -> BeautifulSoup: mets_file = [ os.path.join(self.path, f) for f in os.listdir(self.path) - if 'mets.xml' in f.lower() + if "mets.xml" in f.lower() ] if len(mets_file) == 0: - logger.critical(f"Could not find METS file in {self.path}") + logger.critical("Could not find METS file in %s", self.path) tries = 1 - #return - + # return + mets_file = mets_file[0] - with open(mets_file, 'r', encoding="utf-8") as f: + with open(mets_file, "r", encoding="utf-8") as f: raw_xml = f.read() - - mets_doc = BeautifulSoup(raw_xml, 'xml') + + mets_doc = BeautifulSoup(raw_xml, "xml") return mets_doc except IOError as e: - if i < tries - 1: # i is zero indexed - logger.warning(f"Caught error for {self.id}, " - f"retrying (up to {tries} times) to read " - f"xml file or listing the dir. Error: {e}.") + if i < tries - 1: # i is zero indexed + msg = ( + f"Caught error for {self.id}, retrying (up to {tries} times) " + f"to read xml file or listing the dir. Error: {e}." + ) + logger.warning(msg) continue else: - logger.warning("Reached maximum amount of " - f"errors for {self.id}.") - raise e \ No newline at end of file + logger.warning("Reached maximum amount of errors for %s.", self.id) + raise e diff --git a/text_importer/importers/mets_alto/mets.py b/text_importer/importers/mets_alto/mets.py index 9c60061d..3d77bed3 100644 --- a/text_importer/importers/mets_alto/mets.py +++ b/text_importer/importers/mets_alto/mets.py @@ -21,26 +21,26 @@ def parse_mets_filegroup(mets_doc: BeautifulSoup) -> dict[int, str]: dict[int, str]: Mapping from page number to page image id. """ image_filegroup = mets_doc.findAll( - 'fileGrp', {"USE": lambda x: x and x.lower() == 'images'} + "fileGrp", {"USE": lambda x: x and x.lower() == "images"} )[0] return { int(child.get("SEQ")): child.get("ADMID") - for child in image_filegroup.findAll('file') + for child in image_filegroup.findAll("file") } def parse_mets_amdsec( - mets_doc: BeautifulSoup, - x_res: str, - y_res: str, - x_res_default: int = 300, - y_res_default: int = 300 + mets_doc: BeautifulSoup, + x_res: str, + y_res: str, + x_res_default: int = 300, + y_res_default: int = 300, ) -> dict: """Parse the ```` section of Mets XML to extract image properties. The ```` section contains administrative metadata about the OCR, in - particular information about the image resolution allowing the coordinates + particular information about the image resolution allowing the coordinates conversion to iiif format. Args: @@ -54,46 +54,46 @@ def parse_mets_amdsec( dict: Parsed image properties with default values if the field was not found in the document. """ - page_image_ids = parse_mets_filegroup(mets_doc) # Returns {page: im_id} - + page_image_ids = parse_mets_filegroup(mets_doc) # Returns {page: im_id} + amd_sections = { # Returns {page_id: amdsec} - image_id: mets_doc.findAll('amdSec', {'ID': image_id})[0] + image_id: mets_doc.findAll("amdSec", {"ID": image_id})[0] for image_id in page_image_ids.values() } - + image_properties_dict = {} for image_no, image_id in page_image_ids.items(): amd_sect = amd_sections[image_id] try: x_res_val = ( - int(amd_sect.find(x_res).text) - if amd_sect.find(x_res) is not None + int(amd_sect.find(x_res).text) + if amd_sect.find(x_res) is not None else x_res_default ) y_res_val = ( - int(amd_sect.find(x_res).text) - if amd_sect.find(x_res) is not None + int(amd_sect.find(y_res).text) + if amd_sect.find(y_res) is not None else x_res_default ) image_properties_dict[image_no] = { - 'x_resolution': x_res_val, - 'y_resolution': y_res_val + "x_resolution": x_res_val, + "y_resolution": y_res_val, } # if it fails it's because of value < 1 except Exception as e: - logger.debug(f'Error occured when parsing {e}') + logger.debug("Error occured when parsing %s", e) image_properties_dict[image_no] = { - 'x_resolution': x_res_default, - 'y_resolution': y_res_default + "x_resolution": x_res_default, + "y_resolution": y_res_default, } return image_properties_dict -def get_dmd_sec(mets_doc: BeautifulSoup, filter: str) -> Tag: +def get_dmd_sec(mets_doc: BeautifulSoup, _filter: str) -> Tag: """Extract the contents of a specific ```` from the Mets document. - The ```` section contains descriptive metadata. It's composed of + The ```` section contains descriptive metadata. It's composed of several different subsections each identified with string IDs. Args: @@ -103,4 +103,4 @@ def get_dmd_sec(mets_doc: BeautifulSoup, filter: str) -> Tag: Returns: Tag: Contents of the desired ```` of the Mets XML document. """ - return mets_doc.find("dmdSec", {"ID": filter}) + return mets_doc.find("dmdSec", {"ID": _filter}) diff --git a/text_importer/importers/swa/classes.py b/text_importer/importers/swa/classes.py index 89c3faef..b28b0fe3 100644 --- a/text_importer/importers/swa/classes.py +++ b/text_importer/importers/swa/classes.py @@ -29,7 +29,7 @@ class SWANewspaperPage(MetsAltoNewspaperPage): _id (str): Canonical page ID. number (int): Page number. alto_path (str): Full path to the Alto XML file. - + Attributes: id (str): Canonical Page ID (e.g. ``GDL-1900-01-02-a-p0004``). number (int): Page number. @@ -40,18 +40,17 @@ class SWANewspaperPage(MetsAltoNewspaperPage): encoding (str, optional): Encoding of XML file. iiif (str): The iiif URI to the newspaper page image. """ - + def __init__(self, _id: str, number: int, alto_path: str) -> None: self.alto_path = alto_path basedir, filename = os.path.split(alto_path) - super().__init__(_id, number, filename, basedir, - encoding=SWA_XML_ENCODING) - self.iiif = os.path.join(IIIF_IMG_BASE_URI, filename.split('.')[0]) - self.page_data['iiif_img_base_uri'] = self.iiif - + super().__init__(_id, number, filename, basedir, encoding=SWA_XML_ENCODING) + self.iiif = os.path.join(IIIF_IMG_BASE_URI, filename.split(".")[0]) + self.page_data["iiif_img_base_uri"] = self.iiif + def add_issue(self, issue: NewspaperIssue) -> None: self.issue = issue - + @property def ci_id(self) -> str: """Return the content item ID of the page. @@ -64,10 +63,10 @@ def ci_id(self) -> str: Returns: str: Content item id. """ - split = self.id.split('-') - split[-1] = split[-1].replace('p', 'i') + split = self.id.split("-") + split[-1] = split[-1].replace("p", "i") return "-".join(split) - + @property def file_exists(self) -> bool: """Check whether the Alto XML file exists for this page. @@ -75,24 +74,23 @@ def file_exists(self) -> bool: Returns: bool: True if the Alto XML file exists, False otherwise. """ - return (os.path.exists(self.alto_path) and - os.path.isfile(self.alto_path)) - + return os.path.exists(self.alto_path) and os.path.isfile(self.alto_path) + def parse(self) -> None: doc = self.xml - pselement = doc.find('PrintSpace') + pselement = doc.find("PrintSpace") ci_id = self.ci_id - - mappings = {k.get('ID'): ci_id for k in pselement.findAll('TextBlock')} + + mappings = {k.get("ID"): ci_id for k in pselement.findAll("TextBlock")} page_data, notes = parse_printspace(pselement, mappings) - - self.page_data['cc'], self.page_data['r'] = True, page_data - + + self.page_data["cc"], self.page_data["r"] = True, page_data + # Add notes to page data if len(notes) > 0: - self.page_data['n'] = notes + self.page_data["n"] = notes return notes - + def get_iiif_image(self) -> str: """Create the iiif URI to the full journal page image. @@ -105,7 +103,7 @@ def get_iiif_image(self) -> str: class SWANewspaperIssue(NewspaperIssue): """Newspaper issue in SWA Mets/Alto format. - Note: + Note: SWA is in ALTO format, but there isn't any Mets file. So in that case, issues are simply a collection of pages. @@ -130,31 +128,31 @@ class SWANewspaperIssue(NewspaperIssue): content_items (list[dict[str,Any]]): Content items from this issue. notes (list[str]): Notes of missing pages gathered while parsing. """ - + def __init__(self, issue_dir: SwaIssueDir, temp_dir: str) -> None: super().__init__(issue_dir) self.archive = self._parse_archive(temp_dir) self.temp_pages = issue_dir.pages self.content_items = [] - + self._notes = [] self._find_pages() self._find_content_items() - iiif_manifest = os.path.join(IIIF_PRES_BASE_URI, - f"{self.id}-issue", - IIIF_MANIFEST_SUFFIX) - + iiif_manifest = os.path.join( + IIIF_PRES_BASE_URI, f"{self.id}-issue", IIIF_MANIFEST_SUFFIX + ) + self.issue_data = { - 'id': self.id, - 'cdt': strftime("%Y-%m-%d %H:%M:%S"), - 'i': self.content_items, - 'ar': self.rights, - 'pp': [p.id for p in self.pages], - 'iiif_manifest_uri': iiif_manifest, - 'notes': self._notes + "id": self.id, + "cdt": strftime("%Y-%m-%d %H:%M:%S"), + "i": self.content_items, + "ar": self.rights, + "pp": [p.id for p in self.pages], + "iiif_manifest_uri": iiif_manifest, + "notes": self._notes, } - + def _parse_archive(self, temp_dir: str) -> ZipArchive: """Open and parse the Zip archive located at :attr:`path` if possible. @@ -172,7 +170,7 @@ def _parse_archive(self, temp_dir: str) -> ZipArchive: try: archive = ZipFile(self.path) logger.debug( - f"Contents of archive for {self.id}: {archive.namelist()}" + "Contents of archive for %s: %s", self.id, archive.namelist() ) return ZipArchive(archive, temp_dir) except Exception as e: @@ -181,7 +179,7 @@ def _parse_archive(self, temp_dir: str) -> ZipArchive: else: msg = f"Could not find archive {self.path} for {self.id}" raise ValueError(msg) - + def _find_pages(self) -> None: """Detect and create the issue pages using the relevant Alto XML files. @@ -194,16 +192,16 @@ def _find_pages(self) -> None: page_id, page_path = val page_path = os.path.join(self.archive.dir, page_path) page = SWANewspaperPage(page_id, n + 1, page_path) - + # Check page existence if not page.file_exists: self._notes.append(f"Alto file for {page_id} missing {page_path}") else: self.pages.append(page) - + if len(self.pages) == 0: raise ValueError(f"Could not find any page for {self.id}") - + def _find_content_items(self) -> None: """Create content items for the pages in this issue. @@ -212,12 +210,12 @@ def _find_content_items(self) -> None: """ for page in sorted(self.pages, key=lambda x: x.id): page_number = page.number - ci_id = self.id + '-i' + str(page_number).zfill(4) + ci_id = self.id + "-i" + str(page_number).zfill(4) ci = { - 'm': { - 'id': ci_id, - 'pp': [page_number], - 'tp': 'page', + "m": { + "id": ci_id, + "pp": [page_number], + "tp": "page", } } self.content_items.append(ci) diff --git a/text_importer/importers/tetml/classes.py b/text_importer/importers/tetml/classes.py index ab9ca0b9..58a92f91 100644 --- a/text_importer/importers/tetml/classes.py +++ b/text_importer/importers/tetml/classes.py @@ -15,6 +15,7 @@ IIIF_ENDPOINT_URI = "https://impresso-project.ch/api/proxy/iiif/" + class TetmlNewspaperPage(NewspaperPage): """Generic class representing a page in Tetml format. @@ -41,7 +42,7 @@ def parse(self): } if not self.page_data["r"]: - logger.warning(f"Page {self.id} has no OCR text") + logger.warning("Page %s has no OCR text", self.id) def add_issue(self, issue: NewspaperIssue): if issue is None: @@ -66,7 +67,7 @@ class TetmlNewspaperIssue(NewspaperIssue): def __init__(self, issue_dir: IssueDir): super().__init__(issue_dir) - logger.info(f"Starting to parse {self.id}") + logger.info("Starting to parse %s", self.id) # get all tetml files of this issue self.files = self._index_issue_files() @@ -75,7 +76,9 @@ def __init__(self, issue_dir: IssueDir): self.article_data = self.parse_articles() # using canonical ('m') and additional non-canonical ('meta') metadata - self.content_items = [{"m": art["m"], "meta": art["meta"]} for art in self.article_data] + self.content_items = [ + {"m": art["m"], "meta": art["meta"]} for art in self.article_data + ] # instantiate the individual pages self._find_pages() @@ -89,7 +92,7 @@ def __init__(self, issue_dir: IssueDir): "ar": self.rights, } - logger.info(f"Finished parsing {self.id}") + logger.info("Finished parsing %s", self.id) def _index_issue_files(self, suffix=".tetml"): """ @@ -110,7 +113,9 @@ def parse_articles(self): data = tetml_parser(fname) # canonical identifier - data["m"]["id"] = canonical_path(self.issuedir, name=f"i{i+1:04}", extension="") + data["m"]["id"] = canonical_path( + self.issuedir, name=f"i{i+1:04}", extension="" + ) # reference to content item per region for page in data["pages"]: @@ -127,7 +132,7 @@ def parse_articles(self): articles.append(data) except Exception as e: - logger.error(f"Parsing of {fname} failed for {self.id}") + logger.error("Parsing of %s failed for %s", fname, self.id) raise e return articles @@ -144,5 +149,7 @@ def _find_pages(self): for can_page, page_content in zip(can_pages, art["pages"]): can_id = f"{self.id}-p{can_page:04}" self.pages.append( - TetmlNewspaperPage(can_id, can_page, page_content, art["meta"]["tetml_path"]) + TetmlNewspaperPage( + can_id, can_page, page_content, art["meta"]["tetml_path"] + ) ) From d8bd7c05fb2c1406edcdbdfcb3fb3abd5e81a767 Mon Sep 17 00:00:00 2001 From: piconti Date: Mon, 11 Mar 2024 18:59:21 +0100 Subject: [PATCH 33/53] same for rest of classes --- text_importer/importers/bl/classes.py | 202 ++++++------ text_importer/importers/fedgaz/classes.py | 53 ++-- text_importer/importers/olive/classes.py | 315 +++++++++---------- text_importer/importers/rero/classes.py | 355 +++++++++++----------- 4 files changed, 484 insertions(+), 441 deletions(-) diff --git a/text_importer/importers/bl/classes.py b/text_importer/importers/bl/classes.py index 09899b2c..fc9c9926 100644 --- a/text_importer/importers/bl/classes.py +++ b/text_importer/importers/bl/classes.py @@ -11,9 +11,15 @@ from typing import Any from bs4.element import Tag -from text_importer.importers import (CONTENTITEM_TYPES, CONTENTITEM_TYPE_IMAGE, CONTENTITEM_TYPE_ADVERTISEMENT) -from text_importer.importers.mets_alto import (MetsAltoNewspaperIssue, - MetsAltoNewspaperPage) +from text_importer.importers import ( + CONTENTITEM_TYPES, + CONTENTITEM_TYPE_IMAGE, + CONTENTITEM_TYPE_ADVERTISEMENT, +) +from text_importer.importers.mets_alto import ( + MetsAltoNewspaperIssue, + MetsAltoNewspaperPage, +) from text_importer.utils import get_issue_schema, get_page_schema IssueSchema = get_issue_schema() @@ -36,7 +42,7 @@ class BlNewspaperPage(MetsAltoNewspaperPage): filename (str): Name of the Alto XML page file. basedir (str): Base directory where Alto files are located. encoding (str, optional): Encoding of XML file. Defaults to 'utf-8'. - + Attributes: id (str): Canonical Page ID (e.g. ``GDL-1900-01-02-a-p0004``). number (int): Page number. @@ -54,14 +60,13 @@ def add_issue(self, issue: MetsAltoNewspaperIssue) -> None: issue (MetsAltoNewspaperIssue): Issue this page is from """ self.issue = issue - self.page_data['iiif_img_base_uri'] = os.path.join(IIIF_ENDPOINT_URI, - self.id) + self.page_data["iiif_img_base_uri"] = os.path.join(IIIF_ENDPOINT_URI, self.id) class BlNewspaperIssue(MetsAltoNewspaperIssue): """Newspaper Issue in BL (Mets/Alto) format. - All functions defined in this child class are specific to parsing BL + All functions defined in this child class are specific to parsing BL Mets/Alto format. Args: @@ -81,7 +86,6 @@ class BlNewspaperIssue(MetsAltoNewspaperIssue): ark_id (int): Issue ARK identifier, for the issue's pages' iiif links. """ - def _find_pages(self) -> None: """Detect and create the issue pages using the relevant Alto XML files. @@ -91,33 +95,35 @@ def _find_pages(self) -> None: e: Creating a `BlNewspaperPage` raised an exception. """ page_file_names = [ - file for file in os.listdir(self.path) - if (not file.startswith('.') and - '.xml' in file and - 'mets' not in file) + file + for file in os.listdir(self.path) + if (not file.startswith(".") and ".xml" in file and "mets" not in file) ] page_numbers = [ - int(os.path.splitext(fname)[0].split('_')[-1]) - for fname in page_file_names + int(os.path.splitext(fname)[0].split("_")[-1]) for fname in page_file_names ] - + page_canonical_names = [ - "{}-p{}".format(self.id, str(page_n).zfill(4)) - for page_n in page_numbers + "{}-p{}".format(self.id, str(page_n).zfill(4)) for page_n in page_numbers ] - + self.pages = [] - for filename, page_no, page_id in zip(page_file_names, page_numbers, - page_canonical_names): + for filename, page_no, page_id in zip( + page_file_names, page_numbers, page_canonical_names + ): try: - self.pages.append(BlNewspaperPage(page_id, page_no, - filename, self.path)) + self.pages.append( + BlNewspaperPage(page_id, page_no, filename, self.path) + ) except Exception as e: - logger.error(f'Adding page {page_no} {page_id} {filename}', - f'raised following exception: {e}') + msg = ( + f"Adding page {page_no} {page_id} {filename}", + f"raised following exception: {e}", + ) + logger.error(msg) raise e - - def _get_part_dict(self, div: Tag, comp_role: str | None) -> dict[str,Any]: + + def _get_part_dict(self, div: Tag, comp_role: str | None) -> dict[str, Any]: """Construct the parts for a certain div entry of METS. Args: @@ -127,23 +133,23 @@ def _get_part_dict(self, div: Tag, comp_role: str | None) -> dict[str,Any]: Returns: dict[str, Any]: Parts dict for given div. """ - comp_fileid = div.find('area', {'BETYPE': 'IDREF'}).get('FILEID') - comp_id = div.get('ID') - comp_page_no = int(div.parent.get('ORDER')) + comp_fileid = div.find("area", {"BETYPE": "IDREF"}).get("FILEID") + comp_id = div.get("ID") + comp_page_no = int(div.parent.get("ORDER")) if comp_role is None: - type_attr = div.get('TYPE') + type_attr = div.get("TYPE") comp_role = type_attr.lower() if type_attr else None return { - 'comp_role': comp_role, - 'comp_id': comp_id, - 'comp_fileid': comp_fileid, - 'comp_page_no': int(comp_page_no) + "comp_role": comp_role, + "comp_id": comp_id, + "comp_fileid": comp_fileid, + "comp_page_no": int(comp_page_no), } - + def _parse_content_parts( self, item_div: Tag, phys_map: Tag, structlink: Tag - ) -> list[dict[str,Any]]: + ) -> list[dict[str, Any]]: """Parse parts of issue's physical structure relating to the given item. Args: @@ -156,36 +162,41 @@ def _parse_content_parts( """ # Find all parts and their IDS tag = f"#{item_div.get('ID')}" - linkgrp = structlink.find('smLocatorLink', {'xlink:href': tag}).parent - + linkgrp = structlink.find("smLocatorLink", {"xlink:href": tag}).parent + # Remove `#` from xlink:href parts_ids = [ - x.get('xlink:href')[1:] for x in linkgrp.findAll('smLocatorLink') - if x.get('xlink:href') != tag + x.get("xlink:href")[1:] + for x in linkgrp.findAll("smLocatorLink") + if x.get("xlink:href") != tag ] parts = [] for p in parts_ids: # Get element in physical map - div = phys_map.find('div', {'ID': p}) - type_attr = div.get('TYPE') + div = phys_map.find("div", {"ID": p}) + type_attr = div.get("TYPE") comp_role = type_attr.lower() if type_attr else None - + # In that case, need to add all parts - if comp_role == 'page': - for x in div.findAll('div'): + if comp_role == "page": + for x in div.findAll("div"): parts.append(self._get_part_dict(x, None)) else: parts.append(self._get_part_dict(div, comp_role)) - - return parts - def _parse_content_item(self, item_div: Tag, counter: int, - phys_structmap: Tag, structlink: Tag, - item_dmd_sec: Tag) -> dict[str, Any]: + return parts + def _parse_content_item( + self, + item_div: Tag, + counter: int, + phys_structmap: Tag, + structlink: Tag, + item_dmd_sec: Tag, + ) -> dict[str, Any]: """Parse the given content item. - - Doing this parsing means searching for all parts and + + Doing this parsing means searching for all parts and constructing unique IDs for each item. Args: @@ -198,49 +209,51 @@ def _parse_content_item(self, item_div: Tag, counter: int, Returns: dict[str, Any]: Canonical representation of the content item. """ - div_type = item_div.get('TYPE').lower() - + div_type = item_div.get("TYPE").lower() + if div_type == BL_PICTURE_TYPE: div_type = CONTENTITEM_TYPE_IMAGE elif div_type == BL_AD_TYPE: div_type = CONTENTITEM_TYPE_ADVERTISEMENT - + # Check if new content item is found (or if we need more translation) if div_type not in CONTENTITEM_TYPES: - logger.warning(f"Found new content item type: {div_type}") - + logger.warning("Found new content item type: %s", div_type) + metadata = { - 'id': "{}-i{}".format(self.id, str(counter).zfill(4)), - 'tp': div_type, - 'pp': [], + "id": "{}-i{}".format(self.id, str(counter).zfill(4)), + "tp": div_type, + "pp": [], } # Get content item's language - lang = item_dmd_sec.findChild('languageTerm') + lang = item_dmd_sec.findChild("languageTerm") if lang is not None: - metadata['l'] = lang.text - + metadata["l"] = lang.text + # Load physical struct map, and find all parts in physical map content_item = { "m": metadata, "l": { - "id": item_div.get('ID'), - "parts": self._parse_content_parts(item_div, - phys_structmap, - structlink) - } + "id": item_div.get("ID"), + "parts": self._parse_content_parts( + item_div, phys_structmap, structlink + ), + }, } - for p in content_item['l']['parts']: + for p in content_item["l"]["parts"]: pge_no = p["comp_page_no"] - if pge_no not in content_item['m']['pp']: - content_item['m']['pp'].append(pge_no) - + if pge_no not in content_item["m"]["pp"]: + content_item["m"]["pp"].append(pge_no) + # TODO: add coordinates for images as well as iiif_link # + update approach for handling images return content_item - + def _parse_content_items(self) -> list[dict[str, Any]]: """Extract content item elements from a Mets XML file. + # TODO add reading order + Returns: list[dict[str, Any]]: List of all content items and the relevant information in canonical format for each one. @@ -248,41 +261,44 @@ def _parse_content_items(self) -> list[dict[str, Any]]: mets_doc = self.xml content_items = [] # Get logical structure of issue - divs = (mets_doc.find('structMap', {'TYPE': 'LOGICAL'}) - .find('div', {'TYPE': 'ISSUE'}) - .findChildren('div')) - - # Sort to have same naming # TODO change sorting!! - sorted_divs = sorted(divs, key=lambda x: x.get('DMDID').lower()) - + divs = ( + mets_doc.find("structMap", {"TYPE": "LOGICAL"}) + .find("div", {"TYPE": "ISSUE"}) + .findChildren("div") + ) + + # Sort to have same naming + sorted_divs = sorted(divs, key=lambda x: x.get("DMDID").lower()) + # Get all CI types - found_types = set(x.get('TYPE') for x in sorted_divs) - - phys_structmap = mets_doc.find('structMap', {'TYPE': 'PHYSICAL'}) - structlink = mets_doc.find('structLink') - + found_types = set(x.get("TYPE") for x in sorted_divs) + + phys_structmap = mets_doc.find("structMap", {"TYPE": "PHYSICAL"}) + structlink = mets_doc.find("structLink") + counter = 1 for div in sorted_divs: # Parse Each contentitem - dmd_sec = mets_doc.find('dmdSec', {'ID': div.get('DMDID')}) + dmd_sec = mets_doc.find("dmdSec", {"ID": div.get("DMDID")}) content_items.append( - self._parse_content_item(div, counter, phys_structmap, - structlink, dmd_sec) + self._parse_content_item( + div, counter, phys_structmap, structlink, dmd_sec + ) ) counter += 1 return content_items - + def _parse_mets(self) -> None: # No image properties in BL data - + # Parse all the content items content_items = self._parse_content_items() - + self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), - "i": content_items, + "i": content_items, # TODO add reading order "id": self.id, "ar": self.rights, - "pp": [p.id for p in self.pages] + "pp": [p.id for p in self.pages], } diff --git a/text_importer/importers/fedgaz/classes.py b/text_importer/importers/fedgaz/classes.py index 48a35866..e925e656 100644 --- a/text_importer/importers/fedgaz/classes.py +++ b/text_importer/importers/fedgaz/classes.py @@ -53,7 +53,7 @@ def parse(self): } if not self.page_data["r"]: - logger.warning(f"Page {self.id} has no OCR text") + logger.warning("Page %s has no OCR text", self.id) class FedgazNewspaperIssue(TetmlNewspaperIssue): @@ -77,7 +77,7 @@ class FedgazNewspaperIssue(TetmlNewspaperIssue): def __init__(self, issue_dir: IssueDir): NewspaperIssue.__init__(self, issue_dir) - logger.info(f"Starting to parse {self.id}") + logger.info("Starting to parse %s", self.id) # get all tetml files of this issue self.files = self._index_issue_files() @@ -92,7 +92,9 @@ def __init__(self, issue_dir: IssueDir): self._heuristic_article_segmentation(candidates_only=True) # using canonical ('m') and additional non-canonical ('meta') metadata - self.content_items = [{"m": art["m"], "meta": art["meta"]} for art in self.article_data] + self.content_items = [ + {"m": art["m"], "meta": art["meta"]} for art in self.article_data + ] # instantiate the individual pages self._find_pages() @@ -106,7 +108,7 @@ def __init__(self, issue_dir: IssueDir): "ar": self.rights, } - logger.info(f"Finished parsing {self.id}") + logger.info("Finished parsing %s", self.id) def parse_articles(self): """ @@ -120,7 +122,9 @@ def parse_articles(self): data = tetml_parser(fname) # canonical identifier - data["m"]["id"] = canonical_path(self.issuedir, name=f"i{i+1:04}", extension="") + data["m"]["id"] = canonical_path( + self.issuedir, name=f"i{i+1:04}", extension="" + ) # reference to content item per region for page in data["pages"]: @@ -139,7 +143,7 @@ def parse_articles(self): articles.append(data) except Exception as e: - logger.error(f"Parsing of {fname} failed for {self.id}") + logger.error("Parsing of %s failed for %s", fname, self.id) raise e return articles @@ -162,7 +166,9 @@ def _find_pages(self): for can_page, page_content in zip(can_pages, art["pages"]): can_id = f"{self.id}-p{can_page:04}" self.pages.append( - TetmlNewspaperPage(can_id, can_page, page_content, art["meta"]["tetml_path"]) + TetmlNewspaperPage( + can_id, can_page, page_content, art["meta"]["tetml_path"] + ) ) def _parse_metadata(self, fname="metadata.tsv"): @@ -186,11 +192,12 @@ def _parse_metadata(self, fname="metadata.tsv"): date = pd.Timestamp(self.date) df = df[df["issue_date"] == date] - except FileNotFoundError: - raise FileNotFoundError( - f"File with additional metadata needs to be placed in \ - the top newspaper directory and named {fname}" + except FileNotFoundError as e: + msg = ( + "File with additional metadata needs to be placed in " + f"the top newspaper directory and named {fname}" ) + raise FileNotFoundError(msg) from e return df @@ -289,7 +296,13 @@ def _heuristic_article_segmentation(self, candidates_only: bool = True) -> None: max_cost_total = max(2, int(0.2 * len(title))) max_insert = int(0.3 * len(title)) # scaled by 3 to make insertions very cheap to account for bad OCR - fuzzy_cost = "{i<=" + str(max_insert) + ",1i+3d+3s<=" + str(max_cost_total * 3) + r"}" + fuzzy_cost = ( + "{i<=" + + str(max_insert) + + ",1i+3d+3s<=" + + str(max_cost_total * 3) + + r"}" + ) # fuzzy match article headline to locate (bestmatch flag) pattern = r"(?b)(" + title + r")" + fuzzy_cost @@ -301,9 +314,9 @@ def _heuristic_article_segmentation(self, candidates_only: bool = True) -> None: error_msg = ( f"Positive fuzzy match (sanity check):\n" - + f'\ttitle: {art["m"]["t"]}\n' - + f"\tpattern: {pattern}\n" - + f"\tmatch: {match}" + f'\ttitle: {art["m"]["t"]}\n' + f"\tpattern: {pattern}\n" + f"\tmatch: {match}" ) logger.info(error_msg) @@ -314,9 +327,9 @@ def _heuristic_article_segmentation(self, candidates_only: bool = True) -> None: except AttributeError: error_msg = ( f"Error while searching for the logical boundary.\n" - + f"The fuzzy match failed to match anything in the following article:\n" - + f"{art['meta']}\n" - + f"Pattern:\n{pattern}" + f"The fuzzy match failed to match anything in the following article:\n" + f"{art['meta']}\n" + f"Pattern:\n{pattern}" ) logger.error(error_msg) @@ -330,8 +343,8 @@ def _heuristic_article_segmentation(self, candidates_only: bool = True) -> None: self._set_new_article_boundary(bound) except Exception as e: error_msg = ( - f"Error while drawing a new article boundary at position {bound} in the issue {self.id}.\n" - + f"Error: {e}" + f"Error while drawing a new article boundary at position {bound} " + f"in the issue {self.id}.\n Error: {e}" ) logger.error(error_msg) diff --git a/text_importer/importers/olive/classes.py b/text_importer/importers/olive/classes.py index c39dda00..604c6db6 100644 --- a/text_importer/importers/olive/classes.py +++ b/text_importer/importers/olive/classes.py @@ -16,16 +16,20 @@ from impresso_commons.path.path_fs import canonical_path from text_importer.importers.classes import NewspaperIssue, NewspaperPage, ZipArchive -from text_importer.importers.olive.helpers import (combine_article_parts, - convert_image_coordinates, - convert_page_coordinates, - get_clusters, - recompose_page, - recompose_ToC) -from text_importer.importers.olive.parsers import (olive_image_parser, - olive_parser, - olive_toc_parser, - parse_styles) +from text_importer.importers.olive.helpers import ( + combine_article_parts, + convert_image_coordinates, + convert_page_coordinates, + get_clusters, + recompose_page, + recompose_ToC, +) +from text_importer.importers.olive.parsers import ( + olive_image_parser, + olive_parser, + olive_toc_parser, + parse_styles, +) from text_importer.utils import get_issue_schema, get_page_schema logger = logging.getLogger(__name__) @@ -54,16 +58,17 @@ class OliveNewspaperPage(NewspaperPage): page_xml (str): Path to the Olive XML file of the page. archive (ZipArchive): Archive of the issue this page is from. """ - - def __init__(self, _id: str, number: int, toc_data: dict, - image_info: dict, page_xml: str) -> None: + + def __init__( + self, _id: str, number: int, toc_data: dict, image_info: dict, page_xml: str + ) -> None: super().__init__(_id, number) self.toc_data = toc_data self.page_data = None self.image_info = image_info self.page_xml = page_xml self.archive = None - + def parse(self) -> None: """Process the page XML file and transform into canonical Page format. @@ -77,61 +82,57 @@ def parse(self) -> None: """ if self.issue is None: raise ValueError(f"No NewspaperIssue for {self.id}") - + element_ids = self.toc_data.keys() elements = { el["legacy"]["id"]: el for el in json.loads(self.issue.content_elements) if (el["legacy"]["id"] in element_ids) } - + self.page_data = recompose_page( - self.id, - self.toc_data, - elements, - self.issue.clusters + self.id, self.toc_data, elements, self.issue.clusters ) - - self.page_data['id'] = self.id - self.page_data['iiif_img_base_uri'] = os.path.join(IIIF_ENDPOINT_URI, - self.id) - - if len(self.page_data['r']) == 0: - logger.warning(f"Page {self.id} has not OCR text") - + + self.page_data["id"] = self.id + self.page_data["iiif_img_base_uri"] = os.path.join(IIIF_ENDPOINT_URI, self.id) + + if len(self.page_data["r"]) == 0: + logger.warning("Page %s has not OCR text", self.id) + self._convert_page_coords() - + # if all(p.page_data is not None for p in self.issue.pages): # # Means issue has been fully processed, can cleanup # self.archive.cleanup() - + def _convert_page_coords(self) -> None: """Convert page coordinates to the desired iiif format if possible. The conversion is attempted if `self.image_info` is defined, otherwise the page is simply skipped. """ - self.page_data['cc'] = False + self.page_data["cc"] = False if self.image_info is not None: try: - box_strategy = self.image_info['strat'] - image_name = self.image_info['s'] + box_strategy = self.image_info["strat"] + image_name = self.image_info["s"] was_converted = convert_page_coordinates( self.page_data, self.archive.read(self.page_xml), image_name, self.archive, box_strategy, - self.issue + self.issue, ) if was_converted: - self.page_data['cc'] = True + self.page_data["cc"] = True except Exception as e: - logger.error(f"Page {self.id} raised error: {e}") - logger.error(f"Couldn't convert coordinates in p. {self.id}") + logger.error("Page %s raised error: %s", self.id, e) + logger.error("Couldn't convert coordinates in p. %s", self.id) else: - logger.debug(f"Image {self.id} does not have image info") - + logger.debug("Image %s does not have image info", self.id) + def add_issue(self, issue: NewspaperIssue) -> None: self.issue = issue self.archive = issue.archive @@ -147,7 +148,7 @@ class OliveNewspaperIssue(NewspaperIssue): Args: issue_dir (IssueDir): Identifying information about the issue. - image_dirs (str): Path to the directory with the page images. + image_dirs (str): Path to the directory with the page images. Multiple paths should be separated by comma (","). temp_dir (str): Temporary directory to unpack ZipArchive objects. @@ -160,7 +161,7 @@ class OliveNewspaperIssue(NewspaperIssue): issue_data (dict[str, Any]): Issue data according to canonical format. pages (list): list of :obj:`NewspaperPage` instances from this issue. rights (str): Access rights applicable to this issue. - image_dirs (str): Path to the directory with the page images. + image_dirs (str): Path to the directory with the page images. Multiple paths should be separated by comma (","). archive (ZipArchive): ZipArchive for this issue. toc_data (dict): Table of contents (ToC) data for this issue. @@ -169,46 +170,44 @@ class OliveNewspaperIssue(NewspaperIssue): clusters (dict[str, list[str]]): Inverted index of legacy ids; values are clusters of articles, each indexed by one member. """ - + def __init__(self, issue_dir: IssueDir, image_dirs: str, temp_dir: str): super().__init__(issue_dir) self.image_dirs = image_dirs - logger.info(f"Starting to parse {self.id}") - + logger.info("Starting to parse %s", self.id) + # First parse the archive and return it self.archive = self._parse_archive(temp_dir) - + # Parse ToC self.toc_data = self._parse_toc() - + # Parse image xml files with olive_image_parser images = self._parse_image_xml_files() - + # Parse and recompose the ToC articles, self.content_elements = self._parse_articles() self.content_items = recompose_ToC(self.toc_data, articles, images) - + self.clusters = get_clusters(articles) - + # Work around to avoid non-pickle-able objects self.content_elements = json.dumps(self.content_elements) self._find_pages() - + styles = self._parse_styles_gallery() # Then parse the styles - + self.issue_data = { - "id": self.id, - "cdt": strftime("%Y-%m-%d %H:%M:%S"), - "s": styles, - "i": self.content_items, - "pp": [p.id for p in self.pages], - "ar": self.rights - } - logger.info(f"Finished parsing {self.id}") - - def _parse_archive( - self, temp_dir: str, file: str = "Document.zip" - ) -> ZipArchive: + "id": self.id, + "cdt": strftime("%Y-%m-%d %H:%M:%S"), + "s": styles, + "i": self.content_items, + "pp": [p.id for p in self.pages], + "ar": self.rights, + } + logger.info("Finished parsing %s", self.id) + + def _parse_archive(self, temp_dir: str, file: str = "Document.zip") -> ZipArchive: """Parse the archive containing the Olive OCR for this issue. Args: @@ -225,24 +224,22 @@ def _parse_archive( archive_path = os.path.join(self.path, file) if os.path.isfile(archive_path): archive_tmp_path = os.path.join( - temp_dir, - canonical_path(self.issuedir, path_type='dir') - ) - + temp_dir, canonical_path(self.issuedir, path_type="dir") + ) + try: archive = ZipFile(archive_path) - logger.debug(( - f"Contents of archive for {self.id}:" - f" {archive.namelist()}" - )) + logger.debug( + "Contents of archive for %s: %s", self.id, archive.namelist() + ) return ZipArchive(archive, archive_tmp_path) except Exception as e: msg = f"Bad Zipfile for {self.id}, failed with error : {e}" - raise ValueError(msg) + raise ValueError(msg) from e else: msg = f"Could not find archive {file} for {self.id}" raise ValueError(msg) - + def _get_page_xml_files(self) -> dict[int, str]: """Get all page XML files in `self.archive`, indexed by page number. @@ -252,14 +249,13 @@ def _get_page_xml_files(self) -> dict[int, str]: page_xml = None if self.archive is not None: page_xml = { - int(item.split("/")[0]): item - for item in self.archive.namelist() - if ".xml" in item and not item.startswith("._") - and "/Pg" in item - } - + int(item.split("/")[0]): item + for item in self.archive.namelist() + if ".xml" in item and not item.startswith("._") and "/Pg" in item + } + return page_xml - + def _parse_toc(self, file: str = "TOC.xml") -> dict[int, dict[str, dict]]: """Parse the XML file containing the issue's ToC. @@ -280,13 +276,14 @@ def _parse_toc(self, file: str = "TOC.xml") -> dict[int, dict[str, dict]]: try: toc_data = olive_toc_parser(toc_path, self.issuedir) logger.debug(toc_data) - except FileNotFoundError: - raise FileNotFoundError(f'Missing ToC.xml for {self.id}') + except FileNotFoundError as e: + msg = f"Missing ToC.xml for {self.id}" + raise FileNotFoundError(msg) from e except Exception as e: - logger.error(f'Corrupted ToC.xml for {self.id}') + logger.error("Corrupted ToC.xml for %s", self.id) raise e return toc_data - + def _parse_image_xml_files(self) -> list[dict[str, str]]: """Find image XML files and extract the image metadata. @@ -294,11 +291,11 @@ def _parse_image_xml_files(self) -> list[dict[str, str]]: list[dict[str, str]]: Metadata about all images in issue. """ image_xml_files = [ - item - for item in self.archive.namelist() - if ".xml" in item and not item.startswith("._") and "/Pc" in item - ] - + item + for item in self.archive.namelist() + if ".xml" in item and not item.startswith("._") and "/Pc" in item + ] + images = [] for image_file in image_xml_files: try: @@ -312,9 +309,9 @@ def _parse_image_xml_files(self) -> list[dict[str, str]]: logger.error(msg) logger.error(e) return images - + def _parse_styles_gallery( - self, file: str = 'styleGallery.txt' + self, file: str = "styleGallery.txt" ) -> list[dict[str, Any]]: """Parse the style file (plain text). @@ -329,16 +326,18 @@ def _parse_styles_gallery( try: styles = parse_styles(self.archive.read(file).decode()) except Exception as e: - logger.warning((f"Parsing styles file {file}" - f" for {self.id}, failed with error : {e}")) + msg = ( + f"Parsing styles file {file} for {self.id}, failed with error: {e}" + ) + logger.warning(msg) else: - logger.warning(f"Could not find styles {file} for {self.id}") + logger.warning("Could not find styles %s for %s", file, self.id) return styles - + def _parse_articles(self) -> tuple[list[dict], list[dict]]: """Parse the article and ad XML files for this issue. - Content elements are article parts, which are then grouped and + Content elements are article parts, which are then grouped and combined to create each article. Returns: @@ -351,60 +350,61 @@ def _parse_articles(self) -> tuple[list[dict], list[dict]]: article_parts = [] items = sorted( [ - item - for item in self.archive.namelist() - if ".xml" in item and not item.startswith("._") and - ("/Ar" in item or "/Ad" in item) + item + for item in self.archive.namelist() + if ".xml" in item + and not item.startswith("._") + and ("/Ar" in item or "/Ad" in item) ] ) - + while len(items) > 0: counter += 1 - + internal_deque = deque([items[0]]) items = items[1:] - + while len(internal_deque) > 0: item = internal_deque.popleft() try: - xml_data = self.archive.read(item).decode('windows-1252') + xml_data = self.archive.read(item).decode("windows-1252") new_data = olive_parser(xml_data) except Exception as e: - logger.error(f'Parsing of {item} failed for {self.id}') + logger.error("Parsing of %s failed for %s", item, self.id) raise e - + # check if it needs to be parsed later on - if new_data["legacy"]['continuation_from'] is not None: + if new_data["legacy"]["continuation_from"] is not None: target = new_data["legacy"]["continuation_from"] target = [x for x in items if target in x] if len(target) > 0: items.append(item) continue - + article_parts.append(new_data) - - if new_data["legacy"]['continuation_to'] is not None: + + if new_data["legacy"]["continuation_to"] is not None: next_id = new_data["legacy"]["continuation_to"] next_id = [x for x in items if next_id in x][0] internal_deque.append(next_id) items.remove(next_id) - + try: content_elements += article_parts combined_article = combine_article_parts(article_parts) - + if combined_article is not None: articles.append(combined_article) - + article_parts = [] except Exception as e: raise e return articles, content_elements - + def _get_image_info(self) -> dict[str, Any]: """Read `image-info.json` file for a given issue. - Go though all given image directories and only load the + Go though all given image directories and only load the contents of the file corresponding to this issue. Raises: @@ -415,35 +415,37 @@ def _get_image_info(self) -> dict[str, Any]: dict[str, Any]: Contents of this issue's `image-info.json` file. """ json_data = [] - for im_dir in self.image_dirs.split(','): + for im_dir in self.image_dirs.split(","): issue_dir = os.path.join( - im_dir, - self.journal, - str(self.date).replace("-", "/"), - self.edition + im_dir, self.journal, str(self.date).replace("-", "/"), self.edition + ) + + issue_w_images = IssueDir( + journal=self.journal, + date=self.date, + edition=self.edition, + path=issue_dir, ) - - issue_w_images = IssueDir(journal=self.journal, date=self.date, - edition=self.edition, path=issue_dir) - - image_info_name = canonical_path(issue_w_images, name="image-info", - extension=".json") - - image_info_path = os.path.join(issue_w_images.path, - image_info_name) - + + image_info_name = canonical_path( + issue_w_images, name="image-info", extension=".json" + ) + + image_info_path = os.path.join(issue_w_images.path, image_info_name) + if os.path.exists(image_info_path): - with open(image_info_path, 'r') as inp_file: + with open(image_info_path, "r") as inp_file: try: json_data = json.load(inp_file) if len(json_data) == 0: - logger.debug(f"Empty image info for {self.id}" - f"at {image_info_path}") + msg = f"Empty image info for {self.id} at {image_info_path}" + logger.debug(msg) else: return json_data except Exception as e: - logger.error(f"Decoding file {image_info_path}" - f"failed with '{e}'") + logger.error( + "Decoding file %s failed with '%s'", image_info_path, e + ) raise e if len(json_data) == 0: raise ValueError(f"Could not find image info for {self.id}") @@ -459,30 +461,29 @@ def _find_pages(self) -> None: pages_xml = self._get_page_xml_files() for page_n, data in self.toc_data.items(): can_id = "{}-p{}".format(self.id, str(page_n).zfill(4)) - image_info_records = [ - p for p in image_info - if int(p['pg']) == page_n - ] - + image_info_records = [p for p in image_info if int(p["pg"]) == page_n] + if len(image_info_records) == 0: image_info_record = None else: image_info_record = image_info_records[0] - + try: page_xml = pages_xml[page_n] - except Exception: - raise ValueError(f"Could not find page xml for {can_id}") - + except Exception as e: + raise ValueError(f"Could not find page xml for {can_id}") from e + self._convert_images(image_info_record, page_n, page_xml) - + self.pages.append( - OliveNewspaperPage(can_id, page_n, data, - image_info_record, page_xml) + OliveNewspaperPage( + can_id, page_n, data, image_info_record, page_xml + ) ) - - def _convert_images(self, image_info_record: dict[str, Any], - page_n: int, page_xml: dict[int, str]) -> None: + + def _convert_images( + self, image_info_record: dict[str, Any], page_n: int, page_xml: dict[int, str] + ) -> None: """Convert a page's image information to canonical format. The coordinates are also converted to a iiif-compliant format. @@ -493,15 +494,15 @@ def _convert_images(self, image_info_record: dict[str, Any], page_xml (dict[int, str]): Parsed contents of the page's XML. """ if image_info_record is not None: - box_strategy = image_info_record['strat'] - image_name = image_info_record['s'] + box_strategy = image_info_record["strat"] + image_name = image_info_record["s"] images_in_page = [ content_item for content_item in self.content_items - if content_item['m']['tp'] == "picture" and - page_n in content_item['m']['pp'] + if content_item["m"]["tp"] == "picture" + and page_n in content_item["m"]["pp"] ] - + for image in images_in_page: image = convert_image_coordinates( image, @@ -509,6 +510,6 @@ def _convert_images(self, image_info_record: dict[str, Any], image_name, self.archive, box_strategy, - self.issuedir + self.issuedir, ) - image['m']['tp'] = 'image' + image["m"]["tp"] = "image" diff --git a/text_importer/importers/rero/classes.py b/text_importer/importers/rero/classes.py index 73920838..d0de5481 100644 --- a/text_importer/importers/rero/classes.py +++ b/text_importer/importers/rero/classes.py @@ -14,9 +14,11 @@ from bs4.element import NavigableString, Tag from text_importer.importers import CONTENTITEM_TYPE_IMAGE, CONTENTITEM_TYPES -from text_importer.importers.mets_alto import (MetsAltoNewspaperIssue, - MetsAltoNewspaperPage, - parse_mets_amdsec) +from text_importer.importers.mets_alto import ( + MetsAltoNewspaperIssue, + MetsAltoNewspaperPage, + parse_mets_amdsec, +) from text_importer.utils import get_issue_schema, get_page_schema IssueSchema = get_issue_schema() @@ -25,8 +27,8 @@ logger = logging.getLogger(__name__) IIIF_ENDPOINT_URI = "https://impresso-project.ch/api/proxy/iiif/" -IIIF_SUFFIX = 'info.json' -IIIF_IMAGE_SUFFIX = 'full/full/0/default.jpg' +IIIF_SUFFIX = "info.json" +IIIF_IMAGE_SUFFIX = "full/full/0/default.jpg" # Types used in RERO2/RERO3 that are not in impresso schema SECTION_TYPE = "section" @@ -35,9 +37,7 @@ def convert_coordinates( - coords: list[float], - resolution: dict[str, float], - page_width: float + coords: list[float], resolution: dict[str, float], page_width: float ) -> list[int]: """Convert the coordinates using true and coordinate system resolutions. @@ -48,16 +48,16 @@ def convert_coordinates( Args: coords (list[float]): List of coordinates to convert - resolution (dict[str, float]): True resolution of the images (keys + resolution (dict[str, float]): True resolution of the images (keys `x_resolution` and `y_resolution` of the dict). page_width (float): The page width used for the coordinate system. Returns: list[int]: The coordinates rescaled to match the true image resolution. """ - if resolution['x_resolution'] == 0 or resolution['y_resolution'] == 0: + if resolution["x_resolution"] == 0 or resolution["y_resolution"] == 0: return coords - factor = page_width / resolution['x_resolution'] + factor = page_width / resolution["x_resolution"] return [int(c / factor) for c in coords] @@ -69,7 +69,7 @@ class ReroNewspaperPage(MetsAltoNewspaperPage): number (int): Page number. filename (str): Name of the Alto XML page file. basedir (str): Base directory where Alto files are located. - + Attributes: id (str): Canonical Page ID (e.g. ``GDL-1900-01-02-a-p0004``). number (int): Page number. @@ -80,69 +80,75 @@ class ReroNewspaperPage(MetsAltoNewspaperPage): encoding (str, optional): Encoding of XML file. Defaults to 'utf-8'. page_width (float): The page width used for the coordinate system. """ - - def __init__(self, _id: str, number: int, - filename: str, basedir: str) -> None: + + def __init__(self, _id: str, number: int, filename: str, basedir: str) -> None: super().__init__(_id, number, filename, basedir) - - page_tag = self.xml.find('Page') - self.page_width = float(page_tag.get('WIDTH')) - + + page_tag = self.xml.find("Page") + self.page_width = float(page_tag.get("WIDTH")) + def add_issue(self, issue: MetsAltoNewspaperIssue) -> None: self.issue = issue - self.page_data['iiif_img_base_uri'] = os.path.join(IIIF_ENDPOINT_URI, - self.id) - + self.page_data["iiif_img_base_uri"] = os.path.join(IIIF_ENDPOINT_URI, self.id) + # no coordinate conversion needed, but keeping it here for now - def _convert_coordinates( - self, page_regions: list[dict] - ) -> tuple[bool, list[dict]]: + def _convert_coordinates(self, page_regions: list[dict]) -> tuple[bool, list[dict]]: """Convert region coordinates to iiif format if possible. - Note: + Note: Currently, no conversion of coordinates is needed. Args: - page_regions (list[dict[str, Any]]): Page regions from canonical + page_regions (list[dict[str, Any]]): Page regions from canonical page format. Returns: - tuple[bool, list[dict[str, Any]]]: Whether the region coordinates + tuple[bool, list[dict[str, Any]]]: Whether the region coordinates are in iiif format and page regions. """ if self.issue is None: logger.critical("Cannot convert coordinates if issue is unknown") - + # Get page real resolution image_properties = self.issue.image_properties[self.number] - x_res = image_properties['x_resolution'] - y_res = image_properties['y_resolution'] - + x_res = image_properties["x_resolution"] + y_res = image_properties["y_resolution"] + # Those fields are 0 For RERO2 if x_res == 0 or y_res == 0: return True, page_regions - + # Then convert coordinates of all regions/paragraphs/lines/tokens success = False try: for region in page_regions: - region['c'] = convert_coordinates(region['c'], image_properties, self.page_width) - for paragraph in region['p']: - paragraph['c'] = convert_coordinates(paragraph['c'], image_properties, self.page_width) - for line in paragraph['l']: - line['c'] = convert_coordinates(line['c'], image_properties, self.page_width) - for token in line['t']: - token['c'] = convert_coordinates(token['c'], image_properties, self.page_width) + region["c"] = convert_coordinates( + region["c"], image_properties, self.page_width + ) + for paragraph in region["p"]: + paragraph["c"] = convert_coordinates( + paragraph["c"], image_properties, self.page_width + ) + for line in paragraph["l"]: + line["c"] = convert_coordinates( + line["c"], image_properties, self.page_width + ) + for token in line["t"]: + token["c"] = convert_coordinates( + token["c"], image_properties, self.page_width + ) success = True except Exception as e: - logger.error(f"Error {e} occurred when converting coordinates for {self.id}") + logger.error( + "Error %s occurred when converting coordinates for %s", e, self.id + ) return success, page_regions class ReroNewspaperIssue(MetsAltoNewspaperIssue): """Newspaper Issue in RERO (Mets/Alto) format. - All functions defined in this child class are specific to parsing RERO + All functions defined in this child class are specific to parsing RERO Mets/Alto format. Args: @@ -161,7 +167,7 @@ class ReroNewspaperIssue(MetsAltoNewspaperIssue): OCR/OLR coordinates to iiif format compliant ones. ark_id (int): Issue ARK identifier, for the issue's pages' iiif links. """ - + def _find_pages(self) -> None: """Detect and create the issue pages using the relevant Alto XML files. @@ -170,41 +176,41 @@ def _find_pages(self) -> None: Raises: e: Instantiation of a page or adding it to :attr:`pages` failed. """ - alto_path = os.path.join(self.path, 'ALTO') + alto_path = os.path.join(self.path, "ALTO") page_file_names = self._find_alto_files_or_retry(alto_path) - + page_numbers = [] - + for fname in page_file_names: - page_no = fname.split('.')[0] + page_no = fname.split(".")[0] page_numbers.append(int(page_no)) - + page_canonical_names = [ - "{}-p{}".format(self.id, str(page_n).zfill(4)) - for page_n in page_numbers + "{}-p{}".format(self.id, str(page_n).zfill(4)) for page_n in page_numbers ] - + self.pages = [] - for filename, page_no, page_id in zip(page_file_names, - page_numbers, - page_canonical_names): + for filename, page_no, page_id in zip( + page_file_names, page_numbers, page_canonical_names + ): try: self.pages.append( ReroNewspaperPage(page_id, page_no, filename, alto_path) ) except Exception as e: - logger.error( - f'Adding page {page_no} {page_id} {filename}', - f'raised following exception: {e}' + msg = ( + f"Adding page {page_no} {page_id} {filename}", + f"raised following exception: {e}", ) + logger.error(msg) raise e - + def _find_alto_files_or_retry(self, alto_path: str) -> list[str]: """List XML files present in given page file dir, retry up to 3 times. During the processing, some IO errors can randomly happen when listing - the contents of the ALTO directory, preventing the correct parsing of + the contents of the ALTO directory, preventing the correct parsing of the issue. The error is raised after the third try. If the Alto directory does not exist, only try once. @@ -219,7 +225,7 @@ def _find_alto_files_or_retry(self, alto_path: str) -> list[str]: list[str]: List of paths of the pages' Alto XML files. """ if not os.path.exists(alto_path): - logger.critical(f"Could not find pages for {self.id}") + logger.critical("Could not find pages for %s", self.id) tries = 1 tries = 3 @@ -228,20 +234,21 @@ def _find_alto_files_or_retry(self, alto_path: str) -> list[str]: page_file_names = [ file for file in os.listdir(alto_path) - if not file.startswith('.') and '.xml' in file + if not file.startswith(".") and ".xml" in file ] return page_file_names except IOError as e: - if i < tries - 1: # i is zero indexed - logger.warning(f"Caught error for {self.id}, " - f"retrying (up to {tries} times) " - f"to find pages. Error: {e}.") + if i < tries - 1: # i is zero indexed + msg = ( + f"Caught error for {self.id}, retrying (up to {tries} times) " + f"to find pages. Error: {e}." + ) + logger.warning(msg) continue else: - logger.warning("Reached maximum amount " - f"of errors for {self.id}.") + logger.warning("Reached maximum amount of errors for %s.", self.id) raise e - + def _parse_content_parts(self, div: Tag) -> list[dict[str, str | int]]: """Parse the children of a content item div for its legacy `parts`. @@ -252,34 +259,36 @@ def _parse_content_parts(self, div: Tag) -> list[dict[str, str | int]]: div (Tag): The div containing the content item Returns: - list[dict[str, str | int]]: information on different parts for the + list[dict[str, str | int]]: information on different parts for the content item (role, id, fileid, page) """ parts = [] for child in div.children: - + if isinstance(child, NavigableString): continue elif isinstance(child, Tag): - type_attr = child.get('TYPE') + type_attr = child.get("TYPE") comp_role = type_attr.lower() if type_attr else None - areas = child.findAll('area') + areas = child.findAll("area") for area in areas: - comp_id = area.get('BEGIN') - comp_fileid = area.get('FILEID') - comp_page_no = int(comp_fileid.replace('ALTO', '')) - - parts.append({ - 'comp_role': comp_role, - 'comp_id': comp_id, - 'comp_fileid': comp_fileid, - 'comp_page_no': comp_page_no - }) + comp_id = area.get("BEGIN") + comp_fileid = area.get("FILEID") + comp_page_no = int(comp_fileid.replace("ALTO", "")) + + parts.append( + { + "comp_role": comp_role, + "comp_id": comp_id, + "comp_fileid": comp_fileid, + "comp_page_no": comp_page_no, + } + ) return parts - + def _get_ci_language(self, dmdid: str, mets_doc: BeautifulSoup) -> str | None: """Find the language code of the content item with given a `DMDID`. - + Languages are usually in a `` at the beginning of a METS file, corresponding to the descriptive metadata. @@ -297,8 +306,10 @@ def _get_ci_language(self, dmdid: str, mets_doc: BeautifulSoup) -> str | None: if lang is None: return None return lang.text - - def _parse_content_item(self, item_div: Tag, counter: int, mets_doc: BeautifulSoup) -> dict[str,Any]: + + def _parse_content_item( + self, item_div: Tag, counter: int, mets_doc: BeautifulSoup + ) -> dict[str, Any]: """Parse a content item div and create the dictionary representing it. The dictionary corresponding to a content item needs to be of a precise @@ -313,51 +324,52 @@ def _parse_content_item(self, item_div: Tag, counter: int, mets_doc: BeautifulSo Returns: dict[str, Any]: Content item in canonical format. """ - div_type = item_div.get('TYPE').lower() - + div_type = item_div.get("TYPE").lower() + if div_type == PICTURE_TYPE or div_type == ILLUSTRATION_TYPE: div_type = CONTENTITEM_TYPE_IMAGE - + # Check if new content item is found (or if we need more translation) if div_type not in CONTENTITEM_TYPES: - logger.warning(f"Found new content item type: {div_type}") - + logger.warning("Found new content item type: %s", div_type) + metadata = { - 'id': "{}-i{}".format(self.id, str(counter).zfill(4)), - 'tp': div_type, - 'pp': [], - 't': item_div.get('LABEL') + "id": "{}-i{}".format(self.id, str(counter).zfill(4)), + "tp": div_type, + "pp": [], + "t": item_div.get("LABEL"), } - + # Get CI language - language = self._get_ci_language(item_div.get('DMDID'), mets_doc) + language = self._get_ci_language(item_div.get("DMDID"), mets_doc) if language is not None: - metadata['l'] = language - + metadata["l"] = language + content_item = { "m": metadata, "l": { - "id": item_div.get('ID'), - "parts": self._parse_content_parts(item_div) - } + "id": item_div.get("ID"), + "parts": self._parse_content_parts(item_div), + }, } - for p in content_item['l']['parts']: + for p in content_item["l"]["parts"]: pge_no = p["comp_page_no"] - if pge_no not in content_item['m']['pp']: - content_item['m']['pp'].append(pge_no) - - if content_item['m']['tp'] == CONTENTITEM_TYPE_IMAGE: - (content_item['c'], - content_item['m']['iiif_link']) = self._get_image_info(content_item) + if pge_no not in content_item["m"]["pp"]: + content_item["m"]["pp"].append(pge_no) + + if content_item["m"]["tp"] == CONTENTITEM_TYPE_IMAGE: + (content_item["c"], content_item["m"]["iiif_link"]) = self._get_image_info( + content_item + ) return content_item - + def _decompose_section(self, div: Tag) -> list[Tag]: """Recursively decompose `Section` tags into a flat list of div tags. - In RERO3, sometimes textblocks and images are withing `Section` tags. - Those need to be recursively decomposed for all the content items to + In RERO3, sometimes textblocks and images are withing `Section` tags. + Those need to be recursively decomposed for all the content items to be parsed. - + Args: div (Tag): Tag of type `Section` containing other divs to extract. @@ -366,28 +378,27 @@ def _decompose_section(self, div: Tag) -> list[Tag]: """ logger.info("Decomposing section type") # Only consider those with DMDID - section_divs = [d for d in div.findAll("div") if - d.get('DMDID') is not None] + section_divs = [d for d in div.findAll("div") if d.get("DMDID") is not None] # Sort to get same IDS - section_divs = sorted(section_divs, key=lambda x: x.get('ID').lower()) - + section_divs = sorted(section_divs, key=lambda x: x.get("ID").lower()) + final_divs = [] # Now do it recursively for d in section_divs: - d_type = d.get('TYPE') + d_type = d.get("TYPE") if d_type is not None: if d_type.lower() == SECTION_TYPE: # Recursively decompose if contents are also of Sections. - final_divs += self._decompose_section(d) + final_divs += self._decompose_section(d) else: final_divs.append(d) return final_divs - - def _parse_content_items( - self, mets_doc: BeautifulSoup - ) -> list[dict[str, Any]]: + + def _parse_content_items(self, mets_doc: BeautifulSoup) -> list[dict[str, Any]]: """Extract content item elements from the issue's Mets XML file. + TODO add reading order + Args: mets_doc (BeautifulSoup): Mets document as BeautifulSoup object. @@ -395,19 +406,17 @@ def _parse_content_items( list[dict[str, Any]]: Issue's content items in canonical format. """ content_items = [] - divs = mets_doc.find('div', {'TYPE': 'CONTENT'}).findChildren( - 'div', - recursive=False + divs = mets_doc.find("div", {"TYPE": "CONTENT"}).findChildren( + "div", recursive=False ) # Children of "Content" tag - - # Sort to have same naming - # TODO MAYBE fix sorting to be based on int ID and not str. - sorted_divs = sorted(divs, key=lambda x: x.get('ID').lower()) - + + # Sort to have same naming + sorted_divs = sorted(divs, key=lambda x: x.get("ID").lower()) + counter = 1 for div in sorted_divs: # Parse Each contentitem - div_type = div.get('TYPE') + div_type = div.get("TYPE") # To parse divs of type SECTION, need to get sub types with DMDID if div_type is not None and (div_type.lower() == SECTION_TYPE): section_divs = self._decompose_section(div) @@ -417,39 +426,38 @@ def _parse_content_items( else: content_items.append(self._parse_content_item(div, counter, mets_doc)) counter += 1 + return content_items - + def _parse_mets(self) -> None: """Parse the Mets XML file corresponding to this issue. - Once the :attr:`issue_data` is created, containing all the relevant + Once the :attr:`issue_data` is created, containing all the relevant information in the canonical Issue format, the `ReroNewspaperIssue` instance is ready for serialization. """ mets_doc = self.xml - + self.image_properties = parse_mets_amdsec( mets_doc, - x_res='ImageWidth', - y_res='ImageLength', + x_res="ImageWidth", + y_res="ImageLength", x_res_default=0, y_res_default=0, ) # Parse the resolution of page images - + # Parse all the content items content_items = self._parse_content_items(mets_doc) - + self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), - "i": content_items, + "i": content_items, # TODO add reading order "id": self.id, "ar": self.rights, - "pp": [p.id for p in self.pages] + "pp": [p.id for p in self.pages], } - - def _get_image_info( - self, content_item: dict[str, Any] - ) -> tuple[list[int], str]: + + def _get_image_info(self, content_item: dict[str, Any]) -> tuple[list[int], str]: """Recover the coordinates and iiif link for an image content item. The iiif link is embedded with the coordinates to directly crop the @@ -462,47 +470,52 @@ def _get_image_info( tuple[list[int], str]: Coordinates on the page and iiif link """ # Images cannot be on multiple pages - num_pages = len(content_item['m']['pp']) + num_pages = len(content_item["m"]["pp"]) assert num_pages == 1, "Image is on more than one page" - - page_nb = content_item['m']['pp'][0] + + page_nb = content_item["m"]["pp"][0] page = [p for p in self.pages if p.number == page_nb][0] - parts = content_item['l']['parts'] - + parts = content_item["l"]["parts"] + assert len(parts) >= 1, f"No parts for image {content_item['m']['id']}" - + if len(parts) > 1: - logger.info("Found multiple parts for image " - f"{content_item['m']['id']}, selecting largest one") - + logger.info( + "Found multiple parts for image %s, selecting largest one", + content_item["m"]["id"], + ) + coords = None max_area = 0 # Image can have multiple parts, choose largest one (with max area) for part in parts: - comp_id = part['comp_id'] - - elements = page.xml.findAll(["ComposedBlock", "TextBlock"], - {"ID": comp_id}) - assert_msg="Image comp_id matches multiple TextBlock tags" + comp_id = part["comp_id"] + + elements = page.xml.findAll(["ComposedBlock", "TextBlock"], {"ID": comp_id}) + assert_msg = "Image comp_id matches multiple TextBlock tags" assert len(elements) <= 1, assert_msg if len(elements) == 0: continue - + element = elements[0] - hpos, vpos = element.get('HPOS'), element.get('VPOS') - width, height = element.get('WIDTH'), element.get('HEIGHT') - + hpos, vpos = element.get("HPOS"), element.get("VPOS") + width, height = element.get("WIDTH"), element.get("HEIGHT") + # Select largest image area = int(float(width)) * int(float(height)) if area > max_area: max_area = area - coords = [int(float(hpos)), int(float(vpos)), - int(float(width)), int(float(height))] - - coords = convert_coordinates(coords, - self.image_properties[page.number], - page.page_width) - + coords = [ + int(float(hpos)), + int(float(vpos)), + int(float(width)), + int(float(height)), + ] + + coords = convert_coordinates( + coords, self.image_properties[page.number], page.page_width + ) + iiif_link = os.path.join(IIIF_ENDPOINT_URI, page.id, IIIF_SUFFIX) - + return coords, iiif_link From 320901822a4acbfeaf334a7ee87018fcc706edf9 Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 12 Mar 2024 12:20:23 +0100 Subject: [PATCH 34/53] Continue pylint cleaning fof BL, BNF --- text_importer/importers/bl/classes.py | 14 +- text_importer/importers/bl/detect.py | 170 +++++++------- text_importer/importers/bnf/classes.py | 312 +++++++++++++------------ 3 files changed, 252 insertions(+), 244 deletions(-) diff --git a/text_importer/importers/bl/classes.py b/text_importer/importers/bl/classes.py index fc9c9926..27a83f4a 100644 --- a/text_importer/importers/bl/classes.py +++ b/text_importer/importers/bl/classes.py @@ -20,7 +20,7 @@ MetsAltoNewspaperIssue, MetsAltoNewspaperPage, ) -from text_importer.utils import get_issue_schema, get_page_schema +from text_importer.utils import get_issue_schema, get_page_schema, get_reading_order IssueSchema = get_issue_schema() Pageschema = get_page_schema() @@ -252,8 +252,6 @@ def _parse_content_item( def _parse_content_items(self) -> list[dict[str, Any]]: """Extract content item elements from a Mets XML file. - # TODO add reading order - Returns: list[dict[str, Any]]: List of all content items and the relevant information in canonical format for each one. @@ -286,6 +284,14 @@ def _parse_content_items(self) -> list[dict[str, Any]]: ) ) counter += 1 + + # compute the reading order for the issue's items + reading_order_dict = get_reading_order(content_items) + + for ci in content_items: + # add the reading order + ci["m"]["ro"] = reading_order_dict[ci["m"]["id"]] + return content_items def _parse_mets(self) -> None: @@ -297,7 +303,7 @@ def _parse_mets(self) -> None: self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), - "i": content_items, # TODO add reading order + "i": content_items, "id": self.id, "ar": self.rights, "pp": [p.id for p in self.pages], diff --git a/text_importer/importers/bl/detect.py b/text_importer/importers/bl/detect.py index 6993c1c6..3366eec5 100644 --- a/text_importer/importers/bl/detect.py +++ b/text_importer/importers/bl/detect.py @@ -1,38 +1,25 @@ """This module contains helper functions to find BL OCR data to import. """ + import logging import os from collections import namedtuple from datetime import date -from typing import List, Optional import zipfile +from glob import glob from dask import bag as db from impresso_commons.path.path_fs import _apply_datefilter -from glob import glob -from text_importer.utils import get_access_right from bs4 import BeautifulSoup logger = logging.getLogger(__name__) -EDITIONS_MAPPINGS = { - 1: 'a', - 2: 'b', - 3: 'c', - 4: 'd', - 5: 'e' - } +EDITIONS_MAPPINGS = {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"} BlIssueDir = namedtuple( - "IssueDirectory", [ - 'journal', - 'date', - 'edition', - 'path', - 'rights' - ] - ) + "IssueDirectory", ["journal", "date", "edition", "path", "rights"] +) """A light-weight data structure to represent a newspaper issue. This named tuple contains basic metadata about a newspaper issue. They @@ -64,27 +51,27 @@ BL_ACCESS_RIGHTS = "closed" -def _get_single_subdir(_dir: str) -> Optional[str]: +def _get_single_subdir(_dir: str) -> str | None: """Check if the given dir only has one directory and return its basename. Args: _dir (str): Directory to check. Returns: - Optional[str]: Subdirectory's basename if it's unique, None otherwise. + str | None: Subdirectory's basename if it's unique, None otherwise. """ sub_dirs = [x for x in os.listdir(_dir) if os.path.isdir(os.path.join(_dir, x))] - + if len(sub_dirs) == 0: - logger.warning(f"Could not find issue in BLIP: {_dir}") + logger.warning("Could not find issue in BLIP: %s", _dir) return None - elif len(sub_dirs) > 1: - logger.warning(f"Found more than one issue in BLIP: {_dir}") + if len(sub_dirs) > 1: + logger.warning("Found more than one issue in BLIP: %s", _dir) return None return sub_dirs[0] -def _get_journal_name(issue_path: str, blip_id: str) -> Optional[str]: +def _get_journal_name(issue_path: str, blip_id: str) -> str | None: """Find the Journal name from within the Mets file. For BL, the journal name is not present in the directory structure. @@ -96,37 +83,36 @@ def _get_journal_name(issue_path: str, blip_id: str) -> Optional[str]: blip_id (str): BLIP ID of the issue. Returns: - Optional[str]: The name of the journal, or None if not found. + str | None: The name of the journal, or None if not found. """ mets_file = [ os.path.join(issue_path, f) for f in os.listdir(issue_path) - if 'mets.xml' in f.lower() + if "mets.xml" in f.lower() ] if len(mets_file) == 0: - logger.critical(f"Could not find METS file in {issue_path}") + logger.critical("Could not find METS file in %s", issue_path) return None - + mets_file = mets_file[0] - - with open(mets_file, 'r', encoding="utf-8") as f: + + with open(mets_file, "r", encoding="utf-8") as f: raw_xml = f.read() - - mets_doc = BeautifulSoup(raw_xml, 'xml') - + + mets_doc = BeautifulSoup(raw_xml, "xml") + dmd_sec = [ - x for x in mets_doc.findAll('dmdSec') - if x.get('ID') and blip_id in x.get('ID') + x for x in mets_doc.findAll("dmdSec") if x.get("ID") and blip_id in x.get("ID") ] if len(dmd_sec) != 1: - logger.critical(f"Could not get journal name for {issue_path}") + logger.critical("Could not get journal name for %s", issue_path) return None - - contents = dmd_sec[0].find('title').contents + + contents = dmd_sec[0].find("title").contents if len(contents) != 1: - logger.critical(f"Could not get journal name for {issue_path}") + logger.critical("Could not get journal name for %s", issue_path) return None - + title = contents[0] acronym = [x[0] for x in title.split(" ")] @@ -140,18 +126,18 @@ def _extract_all(archive_dir: str, destination: str) -> None: archive_dir (str): Directory containing all archives to extract. destination (str): Destination directory. """ - + archive_files = glob(os.path.join(archive_dir, "*.zip")) - logger.info(f"Found {len(archive_files)} files to extract") - + logger.info("Found %s files to extract", len(archive_files)) + for archive in archive_files: - with zipfile.ZipFile(archive, 'r') as zip_ref: + with zipfile.ZipFile(archive, "r") as zip_ref: zip_ref.extractall(destination) -def dir2issue(path: str) -> Optional[BlIssueDir]: +def dir2issue(path: str) -> BlIssueDir | None: """Given the BLIP directory of an issue, create the `BlIssueDir` object. - + TODO: update handling of rights and edition with full data. Args: @@ -160,27 +146,30 @@ def dir2issue(path: str) -> Optional[BlIssueDir]: Returns: Optional[BlIssueDir]: The corresponding Issue """ - split = path.split('/') + split = path.split("/") journal, year, month_day = split[-3], int(split[-2]), split[-1] month, day = int(month_day[:2]), int(month_day[2:]) - - return BlIssueDir(journal=journal, date=date(year, month, day), - edition='a', path=path, rights=BL_ACCESS_RIGHTS) + + return BlIssueDir( + journal=journal, + date=date(year, month, day), + edition="a", + path=path, + rights=BL_ACCESS_RIGHTS, + ) -def detect_issues( - base_dir: str, access_rights: str, tmp_dir: str -) -> list[BlIssueDir]: +def detect_issues(base_dir: str, access_rights: str, tmp_dir: str) -> list[BlIssueDir]: """Detect newspaper issues to import within the filesystem. This function expects the directory structure that the BL used to organize the dump of Mets/Alto OCR data. Args: - base_dir (str): Path to the base directory of newspaper data, + base_dir (str): Path to the base directory of newspaper data, this directory should contain `zip` files. - access_rights (str): Not used for this imported, but argument is - kept for normality. + access_rights (str): Not used for this importer, but argument is + kept for uniformity. tmp_dir (str): Temporary directory to unzip archives. Returns: @@ -188,41 +177,40 @@ def detect_issues( """ # Extract all zips to tmp_dir _extract_all(base_dir, tmp_dir) - + # base_dir becomes extracted archives dir base_dir = tmp_dir - + # Get all BLIP dirs (named with NLP ID) blip_dirs = [ - x for x in os.listdir(base_dir) - if os.path.isdir(os.path.join(base_dir, x)) + x for x in os.listdir(base_dir) if os.path.isdir(os.path.join(base_dir, x)) ] issues = [] - + for blip in blip_dirs: blip_path = os.path.join(base_dir, blip) dir_path, journal_dirs, files = next(os.walk(blip_path)) - + # First iterate on all journals in BLIP dir for journal in journal_dirs: journal_path = os.path.join(blip_path, journal) - dir_path, year_dirs, files = next(os.walk(journal_path)) - + _, year_dirs, _ = next(os.walk(journal_path)) + # Then on years for year in year_dirs: year_path = os.path.join(journal_path, year) - dir_path, month_day_dirs, files = next(os.walk(year_path)) + _, month_day_dirs, _ = next(os.walk(year_path)) # Then on each issue for month_day in month_day_dirs: path = os.path.join(year_path, month_day) issues.append(dir2issue(path)) - + return issues def select_issues( base_dir: str, config: dict, access_rights: str, tmp_dir: str -) -> Optional[list[BlIssueDir]]: +) -> list[BlIssueDir] | None: """SDetect selectively newspaper issues to import. The behavior is very similar to :func:`detect_issues` with the only @@ -237,35 +225,39 @@ def select_issues( tmp_dir (str): Temporary directory to unzip archives. Returns: - Optional[list[BlIssueDir]]: List of `BlIssueDir` instances to import. + list[BlIssueDir] | None: List of `BlIssueDir` instances to import. """ - + # read filters from json configuration (see config.example.json) try: filter_dict = config["newspapers"] exclude_list = config["exclude_newspapers"] year_flag = config["year_only"] - + except KeyError: - logger.critical(f"The key [newspapers|exclude_newspapers|year_only] " - "is missing in the config file.") - return - + logger.critical( + "The key [newspapers|exclude_newspapers|year_only] " + "is missing in the config file." + ) + return None + issues = detect_issues(base_dir, access_rights, tmp_dir) issue_bag = db.from_sequence(issues) - selected_issues = ( - issue_bag.filter( - lambda i: ( - len(filter_dict) == 0 or i.journal in filter_dict.keys() - ) and i.journal not in exclude_list - ).compute() - ) - + selected_issues = issue_bag.filter( + lambda i: (len(filter_dict) == 0 or i.journal in filter_dict.keys()) + and i.journal not in exclude_list + ).compute() + exclude_flag = False if not exclude_list else True - filtered_issues = _apply_datefilter( - filter_dict, selected_issues, year_only=year_flag - ) if not exclude_flag else selected_issues - logger.info(f"{len(filtered_issues)} newspaper issues remained " - f"after applying filter: {filtered_issues}") - + filtered_issues = ( + _apply_datefilter(filter_dict, selected_issues, year_only=year_flag) + if not exclude_flag + else selected_issues + ) + logger.info( + "%s newspaper issues remained after applying filter: %s", + len(filtered_issues), + filtered_issues, + ) + return filtered_issues diff --git a/text_importer/importers/bnf/classes.py b/text_importer/importers/bnf/classes.py index 17401c7f..535da77a 100644 --- a/text_importer/importers/bnf/classes.py +++ b/text_importer/importers/bnf/classes.py @@ -15,15 +15,21 @@ from impresso_commons.path import IssueDir from text_importer.importers import CONTENTITEM_TYPE_IMAGE -from text_importer.importers.bnf.helpers import (BNF_CONTENT_TYPES, - add_div, type_translation) -from text_importer.importers.bnf.parsers import (parse_div_parts, - parse_embedded_cis, - parse_printspace) -from text_importer.importers.mets_alto import (MetsAltoNewspaperIssue, - MetsAltoNewspaperPage) -from text_importer.importers.mets_alto.alto import (distill_coordinates, - parse_style) +from text_importer.importers.bnf.helpers import ( + BNF_CONTENT_TYPES, + add_div, + type_translation, +) +from text_importer.importers.bnf.parsers import ( + parse_div_parts, + parse_embedded_cis, + parse_printspace, +) +from text_importer.importers.mets_alto import ( + MetsAltoNewspaperIssue, + MetsAltoNewspaperPage, +) +from text_importer.importers.mets_alto.alto import distill_coordinates, parse_style from text_importer.utils import get_issue_schema, get_page_schema, get_reading_order IssueSchema = get_issue_schema() @@ -32,9 +38,8 @@ logger = logging.getLogger(__name__) IIIF_ENDPOINT_URI = "https://gallica.bnf.fr/iiif" -IIIF_MANIFEST_SUFFIX = 'manifest.json' +IIIF_MANIFEST_SUFFIX = "manifest.json" IIIF_SUFFIX = "info.json" -IIIF_IMAGE_SUFFIX = "full/0/default.jpg" # TODO remove class BnfNewspaperPage(MetsAltoNewspaperPage): @@ -45,7 +50,7 @@ class BnfNewspaperPage(MetsAltoNewspaperPage): number (int): Page number. filename (str): Name of the Alto XML page file. basedir (str): Base directory where Alto files are located. - + Attributes: id (str): Canonical Page ID (e.g. ``GDL-1900-01-02-a-p0004``). number (int): Page number. @@ -57,48 +62,45 @@ class BnfNewspaperPage(MetsAltoNewspaperPage): is_gzip (bool): Whether the page's corresponding file is in .gzip. ark_link (str): IIIF Ark identifier for this page. """ - - def __init__(self, _id: str, number: int, - filename: str, basedir: str) -> None: - + + def __init__(self, _id: str, number: int, filename: str, basedir: str) -> None: + self.is_gzip = filename.endswith("gz") super().__init__(_id, number, filename, basedir) self.ark_link = self.xml.find("fileIdentifier").getText() - + def _parse_font_styles(self) -> None: - """Parse the styles at the page level. - """ + """Parse the styles at the page level.""" style_divs = self.xml.findAll("TextStyle") - + styles = [] for d in style_divs: styles.append(parse_style(d)) - - self.page_data['s'] = styles - + + self.page_data["s"] = styles + def add_issue(self, issue: MetsAltoNewspaperIssue) -> None: self.issue = issue - self.page_data['iiif_img_base_uri'] = os.path.join(IIIF_ENDPOINT_URI, - self.ark_link) + self.page_data["iiif_img_base_uri"] = os.path.join( + IIIF_ENDPOINT_URI, self.ark_link + ) self._parse_font_styles() - + def parse(self) -> None: doc = self.xml - + mappings = {} - for ci in self.issue.issue_data['i']: - ci_id = ci['m']['id'] - if 'parts' in ci['l']: - for part in ci['l']['parts']: - mappings[part['comp_id']] = ci_id - - pselement = doc.find('PrintSpace') + for ci in self.issue.issue_data["i"]: + ci_id = ci["m"]["id"] + if "parts" in ci["l"]: + for part in ci["l"]["parts"]: + mappings[part["comp_id"]] = ci_id + + pselement = doc.find("PrintSpace") page_data, notes = parse_printspace(pselement, mappings) - self.page_data['cc'], self.page_data["r"] = self._convert_coordinates( - page_data - ) + self.page_data["cc"], self.page_data["r"] = self._convert_coordinates(page_data) if len(notes) > 0: - self.page_data['n'] = notes + self.page_data["n"] = notes @property def xml(self) -> BeautifulSoup: @@ -113,17 +115,17 @@ def xml(self) -> BeautifulSoup: return super(BnfNewspaperPage, self).xml else: alto_xml_path = os.path.join(self.basedir, self.filename) - with gzip.open(alto_xml_path, 'r') as f: + with gzip.open(alto_xml_path, "r") as f: raw_xml = f.read() - - alto_doc = BeautifulSoup(raw_xml, 'xml') + + alto_doc = BeautifulSoup(raw_xml, "xml") return alto_doc class BnfNewspaperIssue(MetsAltoNewspaperIssue): """Newspaper Issue in BNF (Mets/Alto) format. - All functions defined in this child class are specific to parsing BNF + All functions defined in this child class are specific to parsing BNF Mets/Alto format. Args: @@ -144,12 +146,12 @@ class BnfNewspaperIssue(MetsAltoNewspaperIssue): issue_uid (str): Basename of the Mets XML file of this issue. secondary_date (datetime.date): Potential secondary date of issue. """ - + def __init__(self, issue_dir: IssueDir) -> None: self.issue_uid = os.path.basename(issue_dir.path) self.secondary_date = issue_dir.secondary_date super().__init__(issue_dir) - + @property def xml(self) -> BeautifulSoup: """Read Mets XML file of the issue and create a BeautifulSoup object. @@ -160,15 +162,15 @@ def xml(self) -> BeautifulSoup: mets_regex = os.path.join(self.path, "toc", f"*{self.issue_uid}.xml") mets_file = glob(mets_regex) if len(mets_file) == 0: - logger.critical(f"Could not find METS file in {self.path}") + logger.critical("Could not find METS file in %s", self.path) return None mets_file = mets_file[0] - with open(mets_file, 'r', encoding="utf-8") as f: + with open(mets_file, "r", encoding="utf-8") as f: raw_xml = f.read() - - mets_doc = BeautifulSoup(raw_xml, 'xml') + + mets_doc = BeautifulSoup(raw_xml, "xml") return mets_doc - + def _find_pages(self) -> None: """Detect and create the issue pages using the relevant Alto XML files. @@ -178,40 +180,46 @@ def _find_pages(self) -> None: e: Instantiation of a page or adding it to :attr:`pages` failed. """ ocr_path = os.path.join(self.path, "ocr") # Pages in `ocr` folder - + pages = [ - (file, int(file.split('.')[0][1:])) + (file, int(file.split(".")[0][1:])) for file in os.listdir(ocr_path) - if not file.startswith('.') and '.xml' in file + if not file.startswith(".") and ".xml" in file ] - + page_filenames, page_numbers = zip(*pages) - + page_canonical_names = [ - "{}-p{}".format(self.id, str(page_n).zfill(4)) - for page_n in page_numbers + "{}-p{}".format(self.id, str(page_n).zfill(4)) for page_n in page_numbers ] - + self.pages = {} - for filename, page_no, page_id in zip(page_filenames, page_numbers, - page_canonical_names): + for filename, page_no, page_id in zip( + page_filenames, page_numbers, page_canonical_names + ): try: - self.pages[page_no] = BnfNewspaperPage(page_id, page_no, - filename, ocr_path) + self.pages[page_no] = BnfNewspaperPage( + page_id, page_no, filename, ocr_path + ) except Exception as e: logger.error( - f'Adding page {page_no} {page_id} {filename}', - f'raised following exception: {e}' + "Adding page %s %s %s raised following exception: %s", + page_no, + page_id, + filename, + e, ) raise e - - def _get_divs_by_type(self, mets: BeautifulSoup) -> dict[str, list[tuple[str, str]]]: + + def _get_divs_by_type( + self, mets: BeautifulSoup + ) -> dict[str, list[tuple[str, str]]]: """Parse `div` tags, flatten them and sort them by type. First, parse the `dmdSec` tags, and sort them by type. - Then, search for `div` tags in the `content` of the `structMap` that + Then, search for `div` tags in the `content` of the `structMap` that don't have the `DMDID` attribute, and for which the type is in - `BNF_CONTENT_TYPES`. + `BNF_CONTENT_TYPES`. Finally, flatten the sections into what they actually contain, and add the flattened sections to the return dict. @@ -225,47 +233,42 @@ def _get_divs_by_type(self, mets: BeautifulSoup) -> dict[str, list[tuple[str, st dmd_sections = [x for x in mets.findAll("dmdSec") if x.find("mods")] struct_map = mets.find("structMap", {"TYPE": "logical"}) struct_content = struct_map.find("div", {"TYPE": "CONTENT"}) - + by_type = {} - + # First parse DMD section and keep DIV IDs of referenced items for s in dmd_sections: # Iterate on the DMD section divs = struct_map.findAll("div", {"DMDID": s.get("ID")}) - + if len(divs) > 1: # Means this DMDID is a class of objects if s.find("mods:classification") is not None: _type = s.find("mods:classification").getText().lower() for d in divs: - by_type = add_div(by_type, _type, d.get("ID"), - d.get("LABEL")) + by_type = add_div(by_type, _type, d.get("ID"), d.get("LABEL")) else: - logger.warning( - f"MultiDiv with no classification for {self.id}" - ) + logger.warning("MultiDiv with no classification for %s", self.id) else: div = divs[0] _type = div.get("TYPE").lower() - by_type = add_div(by_type, _type, div.get("ID"), - div.get("LABEL")) - - # Parse div sections that are direct children of CONTENT in the + by_type = add_div(by_type, _type, div.get("ID"), div.get("LABEL")) + + # Parse div sections that are direct children of CONTENT in the # logical structMap, and keep the ones without DMDID for c in struct_content.findChildren("div", recursive=False): if c.get("DMDID") is None and c.get("TYPE") is not None: _type = c.get("TYPE").lower() - by_type = add_div(by_type, _type, c.get("ID"), - c.get("LABEL")) - - if 'section' in by_type: + by_type = add_div(by_type, _type, c.get("ID"), c.get("LABEL")) + + if "section" in by_type: by_type = self._flatten_sections(by_type, struct_content) - + return by_type - + def _flatten_sections( self, by_type: dict, struct_content ) -> dict[str, list[tuple[str, str]]]: """Flatten the sections of the issue. - + This means making the children parts standalone CIs. Args: @@ -276,26 +279,35 @@ def _flatten_sections( dict[str, list[tuple[str, str]]]: _description_ """ # Flatten the sections - for div_id, lab in by_type['section']: + for div_id, lab in by_type["section"]: # Get all divs of this section - div = struct_content.find("div", {"ID": div_id}) + div = struct_content.find("div", {"ID": div_id}) for d in div.findChildren("div", recursive=False): dmdid = d.get("DMDID") div_id = d.get("ID") ci_type = d.get("TYPE").lower() d_label = d.get("LABEL") # This div needs to be added to the content items - if dmdid is None and ci_type in BNF_CONTENT_TYPES: + if dmdid is None and ci_type in BNF_CONTENT_TYPES: by_type = add_div(by_type, ci_type, div_id, d_label or lab) elif dmdid is None: - logging.debug(f" {self.id}: {div_id} of type {ci_type} " - "within section is not in CONTENT_TYPES") - del by_type['section'] + logging.debug( + " %s: %s of type %s within section is not in CONTENT_TYPES", + self.id, + div_id, + ci_type, + ) + del by_type["section"] return by_type - - def _parse_div(self, div_id: str, div_type: str, - label: str, item_counter: int, - mets_doc: BeautifulSoup) -> tuple[list[dict], int]: + + def _parse_div( + self, + div_id: str, + div_type: str, + label: str, + item_counter: int, + mets_doc: BeautifulSoup, + ) -> tuple[list[dict], int]: """Parse the given `div_id` from the `structMap` of the METS file. Args: @@ -310,40 +322,34 @@ def _parse_div(self, div_id: str, div_type: str, """ article_div = mets_doc.find("div", {"ID": div_id}) # Get the tag # Try to get the body if there is one (we discard headings) - article_div = article_div.find("div", {"TYPE": "BODY"}) or article_div + article_div = article_div.find("div", {"TYPE": "BODY"}) or article_div parts = parse_div_parts(article_div) # Parse the parts of the tag - metadata = None + metadata, ci = None, None # If parts were found, create content item for this DIV - if len(parts) > 0: + if len(parts) > 0: article_id = "{}-i{}".format(self.id, str(item_counter).zfill(4)) metadata = { - 'id': article_id, - 'tp': type_translation[div_type], - 'pp': [], + "id": article_id, + "tp": type_translation[div_type], + "pp": [], } if label is not None: - metadata['t'] = label - ci = { - 'm': metadata, - 'l': { - 'parts': parts - } - } + metadata["t"] = label + ci = {"m": metadata, "l": {"parts": parts}} item_counter += 1 else: # Otherwise, only parse embedded CIs article_id = None - embedded, item_counter = parse_embedded_cis(article_div, label, - self.id, article_id, - item_counter) - + embedded, item_counter = parse_embedded_cis( + article_div, label, self.id, article_id, item_counter + ) + if metadata is not None: embedded.append(ci) + return embedded, item_counter - - def _get_image_iiif_link( - self, ci_id: str, parts: list - ) -> tuple[list[int], str]: + + def _get_image_iiif_link(self, ci_id: str, parts: list) -> tuple[list[int], str]: """Get the image coordinates and iiif info uri given the ID of the CI. Args: ci_id (str): The ID of the image CI @@ -352,75 +358,79 @@ def _get_image_iiif_link( tuple[list[int], str]: The image coordinated and iiif uri to the info.json for the page's image. """ - image_part = [ - p for p in parts - if p['comp_role'] == CONTENTITEM_TYPE_IMAGE - ] + image_part = [p for p in parts if p["comp_role"] == CONTENTITEM_TYPE_IMAGE] iiif_link, coords = None, None if len(image_part) == 0: - message = (f"Content item {ci_id} of type " - f"{CONTENTITEM_TYPE_IMAGE} does not have image part.") + message = ( + f"Content item {ci_id} of type " + f"{CONTENTITEM_TYPE_IMAGE} does not have image part." + ) logger.warning(message) elif len(image_part) > 1: - message = (f"Content item {ci_id} of type " - f"{CONTENTITEM_TYPE_IMAGE} has multiple image parts.") + message = ( + f"Content item {ci_id} of type " + f"{CONTENTITEM_TYPE_IMAGE} has multiple image parts." + ) logger.warning(message) else: - - image_part_id = image_part[0]['comp_id'] - page = self.pages[image_part[0]['comp_page_no']] + + image_part_id = image_part[0]["comp_id"] + page = self.pages[image_part[0]["comp_page_no"]] block = page.xml.find("Illustration", {"ID": image_part_id}) if block is None: - logger.warning("Could not find image " - f"{image_part_id} for CI {ci_id}") + logger.warning( + "Could not find image %s for CI %s", image_part_id, ci_id + ) else: coords = distill_coordinates(block) iiif_link = os.path.join(IIIF_ENDPOINT_URI, page.ark_link, IIIF_SUFFIX) - + return coords, iiif_link - + def _parse_mets(self) -> None: """Parse the Mets XML file corresponding to this issue. - Once the :attr:`issue_data` is created, containing all the relevant + Once the :attr:`issue_data` is created, containing all the relevant information in the canonical Issue format, the `BnfNewspaperIssue` instance is ready for serialization. """ - + mets_doc = self.xml # First get all the divs by type by_type = self._get_divs_by_type(mets_doc) item_counter = 1 content_items = [] - + # Then start parsing them for div_type, divs in by_type.items(): for div_id, div_label in divs: - cis, item_counter = self._parse_div(div_id, div_type, div_label, - item_counter, mets_doc) + cis, item_counter = self._parse_div( + div_id, div_type, div_label, item_counter, mets_doc + ) content_items += cis - - # Finally add the pages and iiif link + + # Finally add the pages and iiif link for x in content_items: - x['m']['pp'] = list(set(c['comp_page_no'] for c in x['l']['parts'])) - if x['m']['tp'] == CONTENTITEM_TYPE_IMAGE: - x['c'], x['m']['iiif_link'] = self._get_image_iiif_link( - x['m']['id'], x['l']['parts'] + x["m"]["pp"] = list(set(c["comp_page_no"] for c in x["l"]["parts"])) + if x["m"]["tp"] == CONTENTITEM_TYPE_IMAGE: + x["c"], x["m"]["iiif_link"] = self._get_image_iiif_link( + x["m"]["id"], x["l"]["parts"] ) # once the pages are added to the metadata, compute & add the reading order reading_order_dict = get_reading_order(content_items) for item in content_items: - item['m']['ro'] = reading_order_dict[item['m']['id']] - + item["m"]["ro"] = reading_order_dict[item["m"]["id"]] self.pages = list(self.pages.values()) - + # Issue manifest iiif URI is in format {iiif_prefix}/{ark_id}/manifest.json # By default, the ark id contains the page number, - iiif_manifest = os.path.join(IIIF_ENDPOINT_URI, - os.path.dirname(self.pages[0].ark_link), - IIIF_MANIFEST_SUFFIX) + iiif_manifest = os.path.join( + IIIF_ENDPOINT_URI, + os.path.dirname(self.pages[0].ark_link), + IIIF_MANIFEST_SUFFIX, + ) self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), @@ -428,8 +438,8 @@ def _parse_mets(self) -> None: "i": content_items, "ar": self.rights, "pp": [p.id for p in self.pages], - "iiif_manifest_uri": iiif_manifest + "iiif_manifest_uri": iiif_manifest, } # Note for newspapers with two dates (197 cases) if self.secondary_date is not None: - self.issue_data['n'] = [f"Secondary date {self.secondary_date}"] + self.issue_data["n"] = [f"Secondary date {self.secondary_date}"] From 3cee237417f0f6bc37d994e45307e7f87b94e63e Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 12 Mar 2024 18:56:08 +0100 Subject: [PATCH 35/53] continue the cleaning --- text_importer/importers/bnf_en/detect.py | 160 +++++++++++------------ text_importer/importers/lux/detect.py | 77 +++++------ text_importer/importers/lux/helpers.py | 46 +++---- 3 files changed, 133 insertions(+), 150 deletions(-) diff --git a/text_importer/importers/bnf_en/detect.py b/text_importer/importers/bnf_en/detect.py index 371e882e..46bd5900 100644 --- a/text_importer/importers/bnf_en/detect.py +++ b/text_importer/importers/bnf_en/detect.py @@ -1,39 +1,26 @@ """This module contains helper functions to find BNF-EN OCR data to import. """ + import logging import os from collections import namedtuple from datetime import datetime, timedelta from string import ascii_lowercase -from typing import Dict, List, Optional import requests from bs4 import BeautifulSoup from dask import bag as db from impresso_commons.path.path_fs import _apply_datefilter from tqdm import tqdm -from multiprocessing import Pool, cpu_count +from multiprocessing import Pool logger = logging.getLogger(__name__) -EDITIONS_MAPPINGS = { - 1: 'a', - 2: 'b', - 3: 'c', - 4: 'd', - 5: 'e' - } +EDITIONS_MAPPINGS = {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"} BnfEnIssueDir = namedtuple( - "IssueDirectory", [ - 'journal', - 'date', - 'edition', - 'path', - 'rights', - 'ark_link' - ] - ) + "IssueDirectory", ["journal", "date", "edition", "path", "rights", "ark_link"] +) """A light-weight data structure to represent a newspaper issue in BNF Europeana This named tuple contains basic metadata about a newspaper issue. They @@ -72,14 +59,12 @@ } -def get_api_id( - journal: str, api_issue: tuple[str, datetime.date], edition: str -) -> str: +def get_api_id(journal: str, api_issue: tuple[str, datetime.date], edition: str) -> str: """Construct an ID given a journal name, date and edition. Args: journal (str): Journal name - api_issue (tuple[str, datetime.date]): Tuple of information fetched + api_issue (tuple[str, datetime.date]): Tuple of information fetched from the Gallica API. edition (str): Edition of the issue. @@ -87,16 +72,17 @@ def get_api_id( str: Canonical issue Id composed of journal name, date and edition. """ date = api_issue[1] - return "{}-{}-{:02}-{:02}-{}".format(journal, date.year, date.month, - date.day, ascii_lowercase[edition]) + return "{}-{}-{:02}-{:02}-{}".format( + journal, date.year, date.month, date.day, ascii_lowercase[edition] + ) def get_issues_iiif_arks(journal_ark: tuple[str, str]) -> list[tuple[str, str]]: """Given a journal name and Ark, fetch its issues' Ark in the Gallica API. - Each fo the Europeana journals have a journal-level Ark id, as well as - issue-level IIIF Ark ids that can be fetched from the Gallica API using - the journal Ark. + Each fo the Europeana journals have a journal-level Ark id, as well as + issue-level IIIF Ark ids that can be fetched from the Gallica API using + the journal Ark. The API also provides the day of the year for the corresponding issue. Using both information, this function recreates all the issue canonical for each collection and maps them to their respective issue IIIF Ark ids. @@ -108,7 +94,7 @@ def get_issues_iiif_arks(journal_ark: tuple[str, str]) -> list[tuple[str, str]]: list[tuple[str, str]]: Pairs of issue canonical Ids and IIIF Ark Ids. """ journal, ark = journal_ark - + def get_date(dayofyear: str, year: int) -> datetime.date: """Return the date corresponding to a day of year. @@ -121,24 +107,23 @@ def get_date(dayofyear: str, year: int) -> datetime.date: """ start_date = datetime(year=year, month=1, day=1) return start_date + timedelta(days=int(dayofyear) - 1) - - print("Fetching for {}".format(journal)) - r = requests.get(API_JOURNAL_URL.format(ark=ark)) + + print(f"Fetching for {journal}") + r = requests.get(API_JOURNAL_URL.format(ark=ark), timeout=60) years = BeautifulSoup(r.content, "lxml").findAll("year") years = [int(x.contents[0]) for x in years] - + links = [] for year in tqdm(years): # API requrest url = API_ISSUE_URL.format(ark=API_MAPPING[journal], year=year) - r = requests.get(url) + r = requests.get(url, timeout=60) api_issues = BeautifulSoup(r.content, "lxml").findAll("issue") # Parse dates and editions api_issues = [ - (i.get("ark"), get_date(i.get("dayofyear"), year)) - for i in api_issues + (i.get("ark"), get_date(i.get("dayofyear"), year)) for i in api_issues ] - + editions = [] for i, issue in enumerate(api_issues): if i == 0: @@ -149,9 +134,9 @@ def get_date(dayofyear: str, year: int) -> datetime.date: editions.append(editions[-1] + 1) else: editions.append(0) - + api_issues = [ - (get_api_id(journal, i, edition), i[0]) + (get_api_id(journal, i, edition), i[0]) for i, edition in zip(api_issues, editions) ] links += api_issues @@ -166,7 +151,7 @@ def construct_iiif_arks() -> dict[str, str]: """ with Pool(4) as p: results = p.map(get_issues_iiif_arks, list(API_MAPPING.items())) - + iiif_arks = [] for i in results: iiif_arks += i @@ -184,8 +169,9 @@ def get_id(journal: str, date: datetime.date, edition: str) -> str: Returns: str: Resulting issue canonical Id. """ - return "{}-{}-{:02}-{:02}-{}".format(journal, date.year, date.month, - date.day, edition) + return "{}-{}-{:02}-{:02}-{}".format( + journal, date.year, date.month, date.day, edition + ) def parse_dir(_dir: str, journal: str) -> str: @@ -198,9 +184,9 @@ def parse_dir(_dir: str, journal: str) -> str: Returns: str: Issue canonical id. """ - date_edition = _dir.split('\\')[-1].split('_') + date_edition = _dir.split("\\")[-1].split("_") if len(date_edition) == 1: - edition = 'a' + edition = "a" date = date_edition[0] else: date = date_edition[0] @@ -211,7 +197,7 @@ def parse_dir(_dir: str, journal: str) -> str: def dir2issue( path: str, access_rights: dict, iiif_arks: dict[str, str] -) -> Optional[BnfEnIssueDir]: +) -> BnfEnIssueDir | None: """Create a `BnfEnIssueDir` object from a directory path. Note: @@ -220,29 +206,35 @@ def dir2issue( Args: path (str): Path of issue. access_rights (dict): Access rights (for conformity). - iiif_arks (dict): Mapping from issue canonical ids to iiif ark ids. + iiif_arks (dict): Mapping from issue canonical ids to iiif ark ids. Returns: - Optional[BnfEnIssueDir]: `BnfEnIssueDir` for given issue if the ark id + BnfEnIssueDir | None: `BnfEnIssueDir` for given issue if the ark id was found on the Gallica API, None otherwise. """ - journal, issue = path.split('/')[-2:] - - date, edition = issue.split('_')[:2] - date = datetime.strptime(date, '%Y%m%d').date() - journal = journal.lower().replace('-', '').strip() + journal, issue = path.split("/")[-2:] + + date, edition = issue.split("_")[:2] + date = datetime.strptime(date, "%Y%m%d").date() + journal = journal.lower().replace("-", "").strip() edition = EDITIONS_MAPPINGS[int(edition)] - + id_ = get_id(journal, date, edition) - + if id_ not in iiif_arks: return None - - return BnfEnIssueDir(journal=journal, date=date, edition=edition, path=path, - rights="open-public", ark_link=iiif_arks[id_]) + + return BnfEnIssueDir( + journal=journal, + date=date, + edition=edition, + path=path, + rights="open-public", + ark_link=iiif_arks[id_], + ) -def detect_issues(base_dir: str, access_rights: str) -> List[BnfEnIssueDir]: +def detect_issues(base_dir: str, access_rights: str) -> list[BnfEnIssueDir]: """Detect newspaper issues to import within the filesystem. This function expects the directory structure that BNF-EN used to @@ -253,7 +245,7 @@ def detect_issues(base_dir: str, access_rights: str) -> List[BnfEnIssueDir]: access_rights (str): Not used for this importer (kept for conformity). Returns: - List[BnfEnIssueDir]: List of `BnfEnIssueDir` instances to import. + list[BnfEnIssueDir]: List of `BnfEnIssueDir` instances to import. """ dir_path, dirs, files = next(os.walk(base_dir)) journal_dirs = [os.path.join(dir_path, _dir) for _dir in dirs] @@ -262,20 +254,20 @@ def detect_issues(base_dir: str, access_rights: str) -> List[BnfEnIssueDir]: for journal in journal_dirs for _dir in os.listdir(journal) ] - + iiif_arks = construct_iiif_arks() issue_dirs = [dir2issue(_dir, None, iiif_arks) for _dir in issue_dirs] - + initial_length = len(issue_dirs) issue_dirs = [i for i in issue_dirs if i is not None] - logger.info(f"Removed {initial_length-len(issue_dirs)} problematic issues") + logger.info("Removed %s problematic issues", initial_length - len(issue_dirs)) return issue_dirs def select_issues( base_dir: str, config: dict, access_rights: str -) -> Optional[List[BnfEnIssueDir]]: +) -> list[BnfEnIssueDir] | None: """Detect selectively newspaper issues to import. The behavior is very similar to :func:`detect_issues` with the only @@ -289,33 +281,37 @@ def select_issues( access_rights (str): Not used for this importer (kept for conformity). Returns: - Optional[List[BnfEnIssueDir]]: `BnfEnIssueDir` instances to import. + list[BnfEnIssueDir] | None: `BnfEnIssueDir` instances to import. """ try: filter_dict = config["newspapers"] exclude_list = config["exclude_newspapers"] year_flag = config["year_only"] - + except KeyError: - logger.critical(f"The key [newspapers|exclude_newspapers|year_only] " - "is missing in the config file.") - return - + logger.critical( + "The key [newspapers|exclude_newspapers|year_only] " + "is missing in the config file." + ) + return None + issues = detect_issues(base_dir, access_rights) issue_bag = db.from_sequence(issues) - selected_issues = ( - issue_bag.filter( - lambda i: ( - len(filter_dict) == 0 or i.journal in filter_dict.keys() - ) and i.journal not in exclude_list - ).compute() - ) - + selected_issues = issue_bag.filter( + lambda i: (len(filter_dict) == 0 or i.journal in filter_dict.keys()) + and i.journal not in exclude_list + ).compute() + exclude_flag = False if not exclude_list else True - filtered_issues = _apply_datefilter( - filter_dict, selected_issues, year_only=year_flag - ) if not exclude_flag else selected_issues - logger.info(f"{len(filtered_issues)} newspaper issues remained " - f"after applying filter: {filtered_issues}") - + filtered_issues = ( + _apply_datefilter(filter_dict, selected_issues, year_only=year_flag) + if not exclude_flag + else selected_issues + ) + msg = ( + f"{len(filtered_issues)} newspaper issues remained " + f"after applying filter: {filtered_issues}" + ) + logger.info(msg) + return filtered_issues diff --git a/text_importer/importers/lux/detect.py b/text_importer/importers/lux/detect.py index 9bd16919..58a6c1a2 100644 --- a/text_importer/importers/lux/detect.py +++ b/text_importer/importers/lux/detect.py @@ -11,22 +11,10 @@ logger = logging.getLogger(__name__) -EDITIONS_MAPPINGS = { - 1: 'a', - 2: 'b', - 3: 'c', - 4: 'd', - 5: 'e' -} +EDITIONS_MAPPINGS = {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"} LuxIssueDir = namedtuple( - "IssueDirectory", [ - 'journal', - 'date', - 'edition', - 'path', - 'rights' - ] + "IssueDirectory", ["journal", "date", "edition", "path", "rights"] ) """A light-weight data structure to represent a newspaper issue. @@ -63,23 +51,19 @@ def dir2issue(path: str) -> LuxIssueDir: Rero2IssueDir: New `LuxIssueDir` object matching the path and rights. """ issue_dir = os.path.basename(path) - local_id = issue_dir.split('_')[2] - issue_date = issue_dir.split('_')[3] - year, month, day = issue_date.split('-') - rights = 'open_public' if 'public_domain' in path else 'closed' - - if len(issue_dir.split('_')) == 4: - edition = 'a' - elif len(issue_dir.split('_')) == 5: - edition = issue_dir.split('_')[4] + local_id = issue_dir.split("_")[2] + issue_date = issue_dir.split("_")[3] + year, month, day = issue_date.split("-") + rights = "open_public" if "public_domain" in path else "closed" + + if len(issue_dir.split("_")) == 4: + edition = "a" + elif len(issue_dir.split("_")) == 5: + edition = issue_dir.split("_")[4] edition = EDITIONS_MAPPINGS[int(edition)] return LuxIssueDir( - local_id, - date(int(year), int(month), int(day)), - edition, - path, - rights + local_id, date(int(year), int(month), int(day)), edition, path, rights ) @@ -97,13 +81,13 @@ def detect_issues(base_dir: str, ar: str = None) -> list[LuxIssueDir]: Returns: list[LuxIssueDir]: List of `LuxIssueDir` instances, to be imported. """ - dir_path, dirs, files = next(os.walk(base_dir)) + dir_path, dirs, _ = next(os.walk(base_dir)) batches_dirs = [os.path.join(dir_path, dir) for dir in dirs] issue_dirs = [ os.path.join(batch_dir, dir) for batch_dir in batches_dirs for dir in os.listdir(batch_dir) - if 'newspaper' in dir + if "newspaper" in dir ] return [dir2issue(_dir) for _dir in issue_dirs] @@ -133,25 +117,28 @@ def select_issues( year_flag = config["year_only"] except KeyError: - logger.critical(f"The key [newspapers|exclude_newspapers|year_only] " - "is missing in the config file.") + logger.critical( + "The key [newspapers|exclude_newspapers|year_only] " + "is missing in the config file." + ) return issues = detect_issues(base_dir, access_rights) issue_bag = db.from_sequence(issues) - selected_issues = ( - issue_bag.filter( - lambda i: ( - len(filter_dict) == 0 or i.journal in filter_dict.keys() - ) and i.journal not in exclude_list - ).compute() - ) + selected_issues = issue_bag.filter( + lambda i: (len(filter_dict) == 0 or i.journal in filter_dict.keys()) + and i.journal not in exclude_list + ).compute() exclude_flag = False if not exclude_list else True - filtered_issues = _apply_datefilter( - filter_dict, selected_issues, year_only=year_flag - ) if not exclude_flag else selected_issues - logger.info(f"{len(filtered_issues)} newspaper issues remained " - f"after applying filter: {filtered_issues}") + filtered_issues = ( + _apply_datefilter(filter_dict, selected_issues, year_only=year_flag) + if not exclude_flag + else selected_issues + ) + msg = ( + f"{len(filtered_issues)} newspaper issues remained " + f"after applying filter: {filtered_issues}" + ) + logger.info(msg) return filtered_issues - diff --git a/text_importer/importers/lux/helpers.py b/text_importer/importers/lux/helpers.py index c162255d..5c38c51c 100644 --- a/text_importer/importers/lux/helpers.py +++ b/text_importer/importers/lux/helpers.py @@ -5,8 +5,9 @@ NON_ARTICLE = ["advertisement", "death_notice"] -def convert_coordinates(hpos: int, vpos: int, width: int, height: int, - x_res: float, y_res: float) -> list[int]: +def convert_coordinates( + hpos: int, vpos: int, width: int, height: int, x_res: float, y_res: float +) -> list[int]: """Convert the coordinates to iiif-compliant ones using the resolution. - x = (coordinate['xResolution']/254.0) * coordinate['hpos'] @@ -31,6 +32,7 @@ def convert_coordinates(hpos: int, vpos: int, width: int, height: int, h = (y_res / 254) * height return [int(x), int(y), int(w), int(h)] + def encode_ark(ark: str) -> str: """Replaces (encodes) backslashes in the Ark identifier. @@ -40,10 +42,10 @@ def encode_ark(ark: str) -> str: Returns: str: New ark identifier with encoded backslashes. """ - return ark.replace('/', '%2f') + return ark.replace("/", "%2f") -def div_has_body(div: Tag, body_type='body') -> bool: +def div_has_body(div: Tag, body_type="body") -> bool: """Checks if the given `div` has a body in it's direct children. Args: @@ -54,8 +56,8 @@ def div_has_body(div: Tag, body_type='body') -> bool: bool: True if one or more of `div`'s direct children have a body. """ children_types = set() - for i in div.findChildren('div', recursive=False): - child_type = i.get('TYPE') + for i in div.findChildren("div", recursive=False): + child_type = i.get("TYPE") if child_type is not None: children_types.add(child_type.lower()) return body_type in children_types @@ -74,9 +76,9 @@ def section_is_article(section_div: Tag) -> bool: bool: True if given `div` is an article section. """ types = [] - for c in section_div.findChildren('div'): - _type = c.get('TYPE').lower() - if not (_type == 'body' or _type == 'body_content'): + for c in section_div.findChildren("div"): + _type = c.get("TYPE").lower() + if not (_type == "body" or _type == "body_content"): types.append(_type) return not all(t in NON_ARTICLE for t in types) @@ -86,7 +88,7 @@ def find_section_articles( ) -> list[str]: """Parse the articles inside the section div and get their content item ID. - Recover the content item canonical ID corresponding to each article using + Recover the content item canonical ID corresponding to each article using the legacy ID (from the OCR) of the articles found in `div`'s children. Args: @@ -98,16 +100,16 @@ def find_section_articles( """ articles_lid = [] for d in section_div.findChildren("div", {"TYPE": "ARTICLE"}): - article_id = d.get('DMDID') + article_id = d.get("DMDID") if article_id is not None: articles_lid.append(article_id) - + children_art = [] # Then search for corresponding content item for i in articles_lid: for ci in content_items: - if i == ci['l']['id']: - children_art.append(ci['m']['id']) + if i == ci["l"]["id"]: + children_art.append(ci["m"]["id"]) return children_art @@ -117,7 +119,7 @@ def remove_section_cis( """Remove undesired content items based on the formed sections. Some content items are contained within a section and should not be in the - content items. Given the recovered section content items, they can be + content items. Given the recovered section content items, they can be removed. Args: @@ -125,21 +127,19 @@ def remove_section_cis( sections (list[dict[str, Any]]): Formed section content items. Returns: - tuple[list[dict[str, Any]], list[dict[str, Any]]]: Filtered + tuple[list[dict[str, Any]], list[dict[str, Any]]]: Filtered content items and ones that were removed. """ - to_remove = [j for i in sections for j in i['l']['canonical_parts']] + to_remove = [j for i in sections for j in i["l"]["canonical_parts"]] if len(to_remove) == 0: return content_items, [] - + to_remove = set(to_remove) new_cis = [] removed = [] for ci in content_items: - if ci['m']['id'] not in to_remove or ci['m']['tp'] == CONTENTITEM_TYPE_IMAGE: + if ci["m"]["id"] not in to_remove or ci["m"]["tp"] == CONTENTITEM_TYPE_IMAGE: new_cis.append(ci) - removed.append(ci['m']['id']) - - return new_cis, list(to_remove) - + removed.append(ci["m"]["id"]) + return new_cis, list(to_remove) From 6b26cc4056c18c48c68a261ad0282600fbdfa39e Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 13 Mar 2024 20:27:51 +0100 Subject: [PATCH 36/53] Add the patching script for patch #5, RERO 2 + 3 data --- .../patching/canonical_patch_5_rero.py | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 text_importer/scripts/patching/canonical_patch_5_rero.py diff --git a/text_importer/scripts/patching/canonical_patch_5_rero.py b/text_importer/scripts/patching/canonical_patch_5_rero.py new file mode 100644 index 00000000..61cbf46e --- /dev/null +++ b/text_importer/scripts/patching/canonical_patch_5_rero.py @@ -0,0 +1,140 @@ +"""Command-line script to perform the patch #5 on the RERO 2 & 3 canonical data. + +Usage: + canonical_patch_5_rero.py --input-bucket= --output-bucket= --canonical-repo-path= --temp-dir= --log-file= --error-log= --patch-outputs-filename= + +Options: + +--input-bucket= S3 input bucket. +--output-bucket= S3 output bucket. +--canonical-repo-path= Path to the local impresso-text-acquisition git repository. +--temp-dir= Temporary directory to write files in. +--log-file= Path to log file. +--error-log= Path to error log file. +--patch-outputs-filename= Filename of the .txt file containing the output of the patches. +""" + +import os +from docopt import docopt +import logging +from impresso_commons.utils import s3 +from impresso_commons.path.path_s3 import fetch_files +from impresso_commons.versioning.compute_manifest import create_manifest +import dask.bag as db +from typing import Any +from text_importer.utils import init_logger +import copy +from collections import defaultdict +from text_importer.scripts.patching.canonical_patch_1_uzh import write_jsonlines_file, title_year_pair_to_issues, write_upload_issues, to_issue_id_pages_dict, nzz_write_upload_pages + +IMPRESSO_STORAGEOPT = s3.get_storage_options() +logger = logging.getLogger() + +def get_reading_order(items: list[dict[str, Any]]) -> dict[str, int]: + """Generate a reading order for items based on their id and the pages they span. + + This reading order can be used to display the content items properly in a table + of contents without skipping form page to page. + + Args: + items (list[dict[str, Any]]): List of items to reorder for the ToC. + + Returns: + dict[str, int]: A dictionary mapping item IDs to their reading order. + """ + items_copy = copy.deepcopy(items) + ids_and_pages = [(i['m']['id'], i['m']['pp']) for i in items_copy] + sorted_ids = sorted( + sorted(ids_and_pages, key=lambda x: int(x[0].split('-i')[-1])), + key=lambda x: x[1] + ) + return {t[0]: index+1 for index, t in enumerate(sorted_ids)} + +def add_ro_to_items(issue: dict[str, Any]) -> list[dict[str, Any]]: + reading_order_dict = get_reading_order(issue['i']) + for ci in issue['i']: + ci["m"]["ro"] = reading_order_dict[ci["m"]["id"]] + return issue + +def main(): + arguments = docopt(__doc__) + s3_input_bucket = arguments["--input-bucket"] + s3_output_bucket = arguments["--output-bucket"] + canonical_repo_path = arguments["--canonical-repo-path"] + temp_dir = arguments["--temp-dir"] + log_file = arguments["--log-file"] + error_log = arguments["--error-log"] + patch_outputs = arguments["--patch-outputs-filename"] + + init_logger(logger, logging.INFO, log_file) + + RERO_2_3_TITLES = ['BLB', 'BNN', 'DFS', 'DVF', 'EZR', 'FZG', 'HRV', 'LAB', 'LLE', 'MGS', 'NTS', 'NZG', 'SGZ', 'SRT', 'WHD', 'ZBT', 'CON', 'DTT', 'FCT', 'GAV', 'GAZ', 'LLS', 'OIZ', 'SAX', 'SDT', 'SMZ', 'VDR', 'VHT'] + PROP_NAME = 'ro' + final_patches_output_path = os.path.join(os.path.dirname(log_file), patch_outputs) + + logger.info("Patching titles %s: adding %s property at page level", RERO_2_3_TITLES, PROP_NAME) + logger.info("Input arguments: %s", arguments) + + #empty_folder(temp_dir) + + logger.info("Fetching the page and issues files from S3...") + # download the issues of interest for this patch + rero_issues, rero_pages = fetch_files('canonical-data', False, 'both', RERO_2_3_TITLES) + + logger.info("Updating the issues files and uploading them to s3...") + rero_patched_issues = ( + rero_issues + .map_partitions( + lambda yearly_issue: [add_ro_to_items(issue) for issue in yearly_issue] + ) + .map_partitions(title_year_pair_to_issues) + .map_partitions( + lambda issues: write_upload_issues( + issues[0], issues[1], + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, + ) + ) + ).compute() + + # free the memory allocated + del rero_issues + + logger.info("Uploading the page files to the new bucket") + rero_page_files = ( + rero_pages + .map_partitions(lambda pages: [p for p in pages]) + .map_partitions(to_issue_id_pages_dict) + .map_partitions(lambda issue_to_pages: nzz_write_upload_pages( + issue_to_pages, + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, + ) + ) + .flatten() + ).compute() + + # create the config for the manifest computation + manifest_config = { + "data_stage": "canonical", + "output_bucket": s3_output_bucket, + "input_bucket": s3_input_bucket, + "git_repository": canonical_repo_path, + "newspapers": RERO_2_3_TITLES, + "temp_directory": temp_dir, + "previous_mft_s3_path": None, + "is_staging": True, + "is_patch": True, + "patched_fields": [PROP_NAME], + "push_to_git": True, + "file_extensions": "issues.jsonl.bz2", + "log_file": log_file, + "notes": "Patching RERO 2 & 3 data to add the reading order." + } + # create and upload the manifest + create_manifest(manifest_config) + +if __name__ == "__main__": + main() From 351d3ee8d268226b8ae41b7c867dcacdeca2eaa4 Mon Sep 17 00:00:00 2001 From: piconti Date: Fri, 22 Mar 2024 11:54:30 +0100 Subject: [PATCH 37/53] Add the patching scripts for Patch 5 & 7 --- text_importer/impresso-schemas | 2 +- .../scripts/patching/canonical_patch_1_uzh.py | 2 +- .../patching/canonical_patch_5_rero.py | 140 ++++++++ .../patching/canonical_patch_7_find_issues.py | 272 +++++++++++++++ .../patching/canonical_patch_7_rero_olive.py | 328 ++++++++++++++++++ 5 files changed, 742 insertions(+), 2 deletions(-) create mode 100644 text_importer/scripts/patching/canonical_patch_5_rero.py create mode 100644 text_importer/scripts/patching/canonical_patch_7_find_issues.py create mode 100644 text_importer/scripts/patching/canonical_patch_7_rero_olive.py diff --git a/text_importer/impresso-schemas b/text_importer/impresso-schemas index 708ba747..f44c8ae3 160000 --- a/text_importer/impresso-schemas +++ b/text_importer/impresso-schemas @@ -1 +1 @@ -Subproject commit 708ba747ab3143de4a0b63f11c7216d02bb5235b +Subproject commit f44c8ae3be910d1d0a3495bdd5b648249506bb85 diff --git a/text_importer/scripts/patching/canonical_patch_1_uzh.py b/text_importer/scripts/patching/canonical_patch_1_uzh.py index fd2df866..9ac43966 100644 --- a/text_importer/scripts/patching/canonical_patch_1_uzh.py +++ b/text_importer/scripts/patching/canonical_patch_1_uzh.py @@ -262,7 +262,7 @@ def to_issue_id_pages_dict(pages: list[dict[str, Any]]) -> dict[str, list[dict[s if len(issues_present)!=1: logger.warning("Did not find exactly one issue in the pages; issue(s): %s", issues_present) - print("Did not find exactly one issue in the pages; issue(s): %s", issues_present) + print("Did not find exactly one issue in the pages; issue(s): %s", issues_present.keys()) if len(issues_present) >1: pairs = [((k, [p['id'] for p in ps]) for k,ps in issues_present)] print(f"Here are the specific contents of the pages: {pairs}") diff --git a/text_importer/scripts/patching/canonical_patch_5_rero.py b/text_importer/scripts/patching/canonical_patch_5_rero.py new file mode 100644 index 00000000..61cbf46e --- /dev/null +++ b/text_importer/scripts/patching/canonical_patch_5_rero.py @@ -0,0 +1,140 @@ +"""Command-line script to perform the patch #5 on the RERO 2 & 3 canonical data. + +Usage: + canonical_patch_5_rero.py --input-bucket= --output-bucket= --canonical-repo-path= --temp-dir= --log-file= --error-log= --patch-outputs-filename= + +Options: + +--input-bucket= S3 input bucket. +--output-bucket= S3 output bucket. +--canonical-repo-path= Path to the local impresso-text-acquisition git repository. +--temp-dir= Temporary directory to write files in. +--log-file= Path to log file. +--error-log= Path to error log file. +--patch-outputs-filename= Filename of the .txt file containing the output of the patches. +""" + +import os +from docopt import docopt +import logging +from impresso_commons.utils import s3 +from impresso_commons.path.path_s3 import fetch_files +from impresso_commons.versioning.compute_manifest import create_manifest +import dask.bag as db +from typing import Any +from text_importer.utils import init_logger +import copy +from collections import defaultdict +from text_importer.scripts.patching.canonical_patch_1_uzh import write_jsonlines_file, title_year_pair_to_issues, write_upload_issues, to_issue_id_pages_dict, nzz_write_upload_pages + +IMPRESSO_STORAGEOPT = s3.get_storage_options() +logger = logging.getLogger() + +def get_reading_order(items: list[dict[str, Any]]) -> dict[str, int]: + """Generate a reading order for items based on their id and the pages they span. + + This reading order can be used to display the content items properly in a table + of contents without skipping form page to page. + + Args: + items (list[dict[str, Any]]): List of items to reorder for the ToC. + + Returns: + dict[str, int]: A dictionary mapping item IDs to their reading order. + """ + items_copy = copy.deepcopy(items) + ids_and_pages = [(i['m']['id'], i['m']['pp']) for i in items_copy] + sorted_ids = sorted( + sorted(ids_and_pages, key=lambda x: int(x[0].split('-i')[-1])), + key=lambda x: x[1] + ) + return {t[0]: index+1 for index, t in enumerate(sorted_ids)} + +def add_ro_to_items(issue: dict[str, Any]) -> list[dict[str, Any]]: + reading_order_dict = get_reading_order(issue['i']) + for ci in issue['i']: + ci["m"]["ro"] = reading_order_dict[ci["m"]["id"]] + return issue + +def main(): + arguments = docopt(__doc__) + s3_input_bucket = arguments["--input-bucket"] + s3_output_bucket = arguments["--output-bucket"] + canonical_repo_path = arguments["--canonical-repo-path"] + temp_dir = arguments["--temp-dir"] + log_file = arguments["--log-file"] + error_log = arguments["--error-log"] + patch_outputs = arguments["--patch-outputs-filename"] + + init_logger(logger, logging.INFO, log_file) + + RERO_2_3_TITLES = ['BLB', 'BNN', 'DFS', 'DVF', 'EZR', 'FZG', 'HRV', 'LAB', 'LLE', 'MGS', 'NTS', 'NZG', 'SGZ', 'SRT', 'WHD', 'ZBT', 'CON', 'DTT', 'FCT', 'GAV', 'GAZ', 'LLS', 'OIZ', 'SAX', 'SDT', 'SMZ', 'VDR', 'VHT'] + PROP_NAME = 'ro' + final_patches_output_path = os.path.join(os.path.dirname(log_file), patch_outputs) + + logger.info("Patching titles %s: adding %s property at page level", RERO_2_3_TITLES, PROP_NAME) + logger.info("Input arguments: %s", arguments) + + #empty_folder(temp_dir) + + logger.info("Fetching the page and issues files from S3...") + # download the issues of interest for this patch + rero_issues, rero_pages = fetch_files('canonical-data', False, 'both', RERO_2_3_TITLES) + + logger.info("Updating the issues files and uploading them to s3...") + rero_patched_issues = ( + rero_issues + .map_partitions( + lambda yearly_issue: [add_ro_to_items(issue) for issue in yearly_issue] + ) + .map_partitions(title_year_pair_to_issues) + .map_partitions( + lambda issues: write_upload_issues( + issues[0], issues[1], + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, + ) + ) + ).compute() + + # free the memory allocated + del rero_issues + + logger.info("Uploading the page files to the new bucket") + rero_page_files = ( + rero_pages + .map_partitions(lambda pages: [p for p in pages]) + .map_partitions(to_issue_id_pages_dict) + .map_partitions(lambda issue_to_pages: nzz_write_upload_pages( + issue_to_pages, + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, + ) + ) + .flatten() + ).compute() + + # create the config for the manifest computation + manifest_config = { + "data_stage": "canonical", + "output_bucket": s3_output_bucket, + "input_bucket": s3_input_bucket, + "git_repository": canonical_repo_path, + "newspapers": RERO_2_3_TITLES, + "temp_directory": temp_dir, + "previous_mft_s3_path": None, + "is_staging": True, + "is_patch": True, + "patched_fields": [PROP_NAME], + "push_to_git": True, + "file_extensions": "issues.jsonl.bz2", + "log_file": log_file, + "notes": "Patching RERO 2 & 3 data to add the reading order." + } + # create and upload the manifest + create_manifest(manifest_config) + +if __name__ == "__main__": + main() diff --git a/text_importer/scripts/patching/canonical_patch_7_find_issues.py b/text_importer/scripts/patching/canonical_patch_7_find_issues.py new file mode 100644 index 00000000..da358aeb --- /dev/null +++ b/text_importer/scripts/patching/canonical_patch_7_find_issues.py @@ -0,0 +1,272 @@ +"""Command-line script to perform the patch #1 on the UZH canonical data (FedGaz, NZZ). + +Usage: + canonical_patch_7_find_issues.py [--img-base-path= --og-data-path= --local-path= --log-file=] + +Options: + +--img-base-path= S3 input bucket. +--og-data-path= S3 output bucket. +--local-path= Path to the local impresso-text-acquisition git repository. +--log-file= Path to log file. +""" + +import os +import json +import logging +import jsonlines +from impresso_commons.utils import s3 +from impresso_commons.path.path_s3 import fetch_files +from impresso_commons.versioning.data_manifest import DataManifest +from text_importer.importers.core import remove_filelocks +import dask.bag as db +from typing import Any, Callable +import git +from text_importer.utils import init_logger +import copy +from docopt import docopt +from collections import defaultdict +import shutil +from zipfile import ZipFile, BadZipFile + +IMPRESSO_STORAGEOPT = s3.get_storage_options() +UZH_TITLES = ["FedGazDe", "FedGazFr", "NZZ"] +IMPRESSO_IIIF_BASE_URI = "https://impresso-project.ch/api/proxy/iiif/" +PROP_NAME = "iiif_img_base_uri" + +logger = logging.getLogger() + + +def add_property( + object_dict: dict[str, Any], + prop_name: str, + prop_function: Callable[[str], str], + function_input: str, +): + object_dict[prop_name] = prop_function(function_input) + logger.debug( + "%s -> Added property %s: %s", + object_dict["id"], + prop_name, + object_dict[prop_name], + ) + return object_dict + + +def empty_folder(dir_path: str) -> None: + """Empty a directoy given its path if it exists. + + Args: + dir_path (str): Path to the directory to empty. + """ + if os.path.exists(dir_path): + shutil.rmtree(dir_path) + logger.info("Emptied directory at %s", dir_path) + os.mkdir(dir_path) + + +def write_error( + thing_id: str, origin_function: str, error: Exception, failed_log: str +) -> None: + """Write the given error of a failed import to the `failed_log` file. + + Adapted from `impresso-text-acquisition/text_importer/importers/core.py` to allow + using a issue or page id, and provide the function in which the error took place. + + Args: + thing_id (str): Canonical ID of the object/file for which the error occurred. + origin_function (str): Function in which the exception occured. + error (Exception): Error that occurred and should be logged. + failed_log (str): Path to log file for failed imports. + """ + note = f"Error in {origin_function} for {thing_id}: {error}" + logger.exception(note) + with open(failed_log, "a+") as f: + f.write(note + "\n") + + +def extract_zip_contents(zip_path): + + zip_contents = ZipFile(zip_path).namelist() + + pg_res_files = [f for f in zip_contents if "Img" in f and "Pg" in f] + pg_res = [f for f in pg_res_files if "_" in f] + # for f in pg_res_files: + # if "_" in f: + # pg = int(f.split("/")[0]) + # res = int(os.path.basename(f).split(".")[0].split("_")[1]) + # if pg in pg_res: + # pg_res[pg].append(res) + # else: + # pg_res[pg] = [res] + return pg_res_files, pg_res + +def load_json(f_path: str) -> dict: + with open(f_path, mode="r", encoding="utf-8") as f_in: + file = json.load(f_in) + return file + +def fetch_needed_info_for_title(title, og_data_path, img_data_path, out_path): + + # resume a listing in the middle + if os.path.exists(out_path): + title_info = load_json(out_path) + logger.info("Continuing to fetch for %s, restarting from %a issues", title, len(title_info)) + else: + title_info = {} + + msg = f"- Fetching info for: {title}" + print(msg) + logger.info(msg) + missing_img_info_files = [] + mltp_img_info_files = [] + + for dir_path, sub_dirs, files in os.walk(os.path.join(img_data_path, title)): + # only consider the cases where we are in an issue directory + if len(sub_dirs) == 0 : + issue_sub_path = dir_path.replace(img_data_path, "") + issue_id = issue_sub_path.replace("/", "-") + if title != 'LCE' or issue_id not in title_info: + + # add the image info + img_info_file = [f for f in files if f.endswith("image-info.json")] + if len(img_info_file) == 1: + img_info_file_path = os.path.join(dir_path, img_info_file[0]) + title_info[issue_id] = { + "img": { + "file_present": True, + "img_info_file": img_info_file_path, + } + } + + img_info = load_json(img_info_file_path) + title_info[issue_id]["img"]["info_f_contents"] = {} + for p, p_info in enumerate(img_info): + title_info[issue_id]["img"]["info_f_contents"][p] = { + "source_used": p_info["s"], + "strat": p_info["strat"], + "s_dim": p_info["s_dim"], + "d_dim": p_info["d_dim"], + } + + elif len(img_info_file) == 0: + print(f"Warining: Missing image-info file for {issue_id}: {dir_path}") + title_info[issue_id] = { + "img": {"file_present": False, "img_info_file": dir_path} + } + missing_img_info_files.append(dir_path) + else: + print( + f"Warning: Mone than 1 image-info file for {issue_id}: {dir_path}" + ) + mltp_img_info_files.append(dir_path) + + # fetch list of formats + # create the path in the original data: there is no edition, so the final '/a' should be removed + og_data_dir_path = dir_path.replace(img_data_path, og_data_path)[:-2] + # if "Document.zip" in os.listdir(og_data_dir_path): + doc_zip_path = os.path.join(og_data_dir_path, "Document.zip") + if os.path.exists(doc_zip_path): + try: + title_info[issue_id]["original"] = {"zip_doc_path": doc_zip_path} + pg_res_files, pg_res = extract_zip_contents( + title_info[issue_id]["original"]["zip_doc_path"] + ) + title_info[issue_id]["original"]["zip_img_contents"] = pg_res_files + if len(pg_res) != 0: + title_info[issue_id]["original"]["resolutions"] = pg_res + except BadZipFile as e: + msg = f"Error: Problem with zip {doc_zip_path}: {e}!" + logger.error(msg) + print(msg) + else: + msg = f"Warning: No 'Document.zip' found in {og_data_dir_path}!" + logger.info(msg) + print(msg) + + if len(title_info) % 50 == 0: + logger.info( + "Currently on issue %s, done %s issues.", issue_id, len(title_info) + ) + if len(title_info) % 500 == 0: + logger.info("Done 500 issues, saving file temporarily.") + with open(out_path, "w", encoding="utf-8") as f_out: + json.dump(title_info, f_out, ensure_ascii=False, indent=4) + + return title_info, missing_img_info_files, mltp_img_info_files + + +def main(): + arguments = docopt(__doc__) + images_base_path = ( + arguments["--img-base-path"] + if arguments["--img-base-path"] + else "/mnt/project_impresso/images/" + ) + og_data_base_path = ( + arguments["--og-data-path"] + if arguments["--og-data-path"] + else "/mnt/project_impresso/original/RERO/" + ) + local_base_path = ( + arguments["--local-path"] + if arguments["--local-path"] + else "/scratch/piconti/impresso/patch_7" + ) + log_file = ( + arguments["--log-file"] + if arguments["--log-file"] + else f"{local_base_path}/find_issues.log" + ) + + init_logger(logger, logging.INFO, log_file) + logger.info("Arguments: \n %s", arguments) + + all_titles_info = {} + _, rero_journal_dirs, _ = next(os.walk(og_data_base_path)) + + # set some titles at the front of the line as priority + rero_titles = ["LCG", "DLE", "LNF", "LBP", "LSE", "EXP"] + rero_titles.extend(rero_journal_dirs) + #rero_titles = rero_journal_dirs + + logger.info("Will process titles: %s", rero_titles) + + for idx, journal in enumerate(rero_titles): + logger.info("Title %s/%s:", idx, len(rero_journal_dirs)) + + img_info_paths_file = f"{local_base_path}/{journal}_img_res_info.json" + + if not os.path.exists(img_info_paths_file) or journal == 'LCE': + title_info, missing_info_files, mltp_if = fetch_needed_info_for_title( + journal, og_data_base_path, images_base_path, img_info_paths_file + ) + + all_titles_info[journal] = title_info + + with open(img_info_paths_file, "w", encoding="utf-8") as f_out: + json.dump(title_info, f_out, ensure_ascii=False, indent=4) + + logger.info( + "%s issues are missing their image-info.json file: ", + len(missing_info_files), + ) + if len(missing_info_files) > 0: + with open( + f"{local_base_path}/{journal}_missing_info_issues.json", + "w", + encoding="utf-8", + ) as f_out: + json.dump(title_info, f_out, ensure_ascii=False, indent=4) + logger.info(missing_info_files) + logger.info( + "%s issues are have more than 1 image-info.json files: ", + len(mltp_if), + ) + logger.info(mltp_if) + else: + logger.info("Skipping %s as it's already been processed", journal) + + +if __name__ == "__main__": + main() diff --git a/text_importer/scripts/patching/canonical_patch_7_rero_olive.py b/text_importer/scripts/patching/canonical_patch_7_rero_olive.py new file mode 100644 index 00000000..9cd25c39 --- /dev/null +++ b/text_importer/scripts/patching/canonical_patch_7_rero_olive.py @@ -0,0 +1,328 @@ +"""Command-line script to perform the patch #7 on the RERO 1 (Olive) canonical data. + +Usage: + canonical_patch_7_rero_olive.py [--local-path= --log-file= --input-bucket= --output-bucket= --canonical-repo-path= --error-log=] + +Options: + +--local-path= Path to the local impresso-text-acquisition git repository. +--log-file= Path to log file. +--input-bucket= S3 input bucket. +--output-bucket= S3 output bucket. +--canonical-repo-path= Path to the local impresso-text-acquisition git repository. +--error-log= Path to error log file. +""" + +import os +import json +import logging +from impresso_commons.utils import s3 +from impresso_commons.path.path_s3 import fetch_files +from impresso_commons.versioning.data_manifest import DataManifest +from impresso_commons.versioning.compute_manifest import create_manifest +import dask.bag as db +from typing import Any, Callable +from text_importer.utils import init_logger +import copy +from docopt import docopt +import shutil +from text_importer.scripts.patching.canonical_patch_1_uzh import (write_jsonlines_file, + title_year_pair_to_issues, + write_upload_issues, + to_issue_id_pages_dict, + nzz_write_upload_pages) + +IMPRESSO_STORAGEOPT = s3.get_storage_options() + +logger = logging.getLogger() + +def empty_folder(dir_path: str) -> None: + """Empty a directoy given its path if it exists. + + Args: + dir_path (str): Path to the directory to empty. + """ + if os.path.exists(dir_path): + shutil.rmtree(dir_path) + logger.info("Emptied directory at %s", dir_path) + os.mkdir(dir_path) + +def write_error( + thing_id: str, origin_function: str, error: Exception, failed_log: str +) -> None: + """Write the given error of a failed import to the `failed_log` file. + + Adapted from `impresso-text-acquisition/text_importer/importers/core.py` to allow + using a issue or page id, and provide the function in which the error took place. + + Args: + thing_id (str): Canonical ID of the object/file for which the error occurred. + origin_function (str): Function in which the exception occured. + error (Exception): Error that occurred and should be logged. + failed_log (str): Path to log file for failed imports. + """ + note = f"Error in {origin_function} for {thing_id}: {error}" + logger.exception(note) + with open(failed_log, "a+") as f: + f.write(note + "\n") + + +def scale_coords(coords: list[int], curr_res: str | int, des_res: str | int) -> list[int]: + return [int(c*int(des_res)/int(curr_res)) for c in coords] + +def convert_issue_coords(issue: dict[str, Any], res: dict[str, int | list]) -> tuple[dict[str, Any], bool]: + # Convert the coordinates present in 1 issue if they are present + scaled = False + for i in issue['i']: + if 'c' in i['m']: + i['m']['c'] = scale_coords(i['m']['c'], res['curr_res'], res['dest_res']) + scaled = True + elif 'c' in i: + i['c'] = scale_coords(i['c'], res['curr_res'], res['dest_res']) + scaled = True + elif 'iiif_link' in i['m'] or 'iiif_link' in i: + iiif = i['m']['iiif_link'] if 'iiif_link' in i['m'] else i['iiif_link'] + logger.warning("%s: No coordinates but a IIIF link for item %s: %s", issue['id'], i['m']['id'], iiif) + # return the issue as-is once it's been scaled + return issue, scaled + +def convert_page_coords(page: dict[str, Any], res: dict[str, int | list]) -> tuple[dict[str, Any], bool]: + # Convert the coordinates present in 1 page + scaled = 0 + # count the expected number of coordinates to rescale on page + coords_count = len(page['r']) + for region in page['r']: + region['c'] = scale_coords(region['c'], res['curr_res'], res['dest_res']) + scaled += 1 + for para in region["p"]: + coords_count += len(para["l"]) + for line in para["l"]: + line['c'] = scale_coords(line['c'], res['curr_res'], res['dest_res']) + scaled += 1 + coords_count += len(line["t"]) + for token in line['t']: + token['c'] = scale_coords(token['c'], res['curr_res'], res['dest_res']) + scaled += 1 + return page, scaled==coords_count + + +def find_convert_coords( + elem: dict[str, Any], + title: str, + to_patch: dict[str, dict], + to_inv: dict[str, dict], + is_issue: bool = True +) -> tuple[dict[str, Any], dict[str, Any]]: + # fetch convert info if issue/page needs conversion, and save info + if is_issue: + issue_id = elem['id'] + key = 'issue_patching_done' + patch_info = {'issue_id': issue_id, key: False, 'num_pages':len(elem['pp'])} + else: + issue_id = '-'.join(elem['id'].split('-')[:-1]) + key = 'page_patching_done' + patch_info = {'issue_id': issue_id, key: False, 'page_id':elem['id']} + + + # for LCG, only years later than 1891 need to be fixed + if title != 'LCG' or issue_id.split('-')[1]>1891: + if issue_id in to_patch: + res = to_patch[issue_id] + # keep trace of whether or not we fetched the information from the image info file + res['used_image_info_file'] = True + elif issue_id in to_inv: + res = to_inv[issue_id] + res['used_image_info_file'] = False + else: + return elem, patch_info + + if is_issue: + elem, scaled = convert_issue_coords(elem, res) + # there may be no coordinated to scale in an issue + res['scaled'] = scaled + else: + elem, scaled = convert_page_coords(elem, res) + # sanity check that number of regions+lines+tokens=coords scaled + res['all_scaled'] = scaled + + # keep trace of information about the patching performed. + patch_info[key] = True + patch_info.update(res) + + return elem, patch_info + + +def main(): + arguments = docopt(__doc__) + local_base_path = ( + arguments["--local-path"] + if arguments["--local-path"] + else "/scratch/piconti/impresso/patch_7" + ) + final_patches_output_path = os.path.join(local_base_path, 'final_patch') + log_file = ( + arguments["--log-file"] + if arguments["--log-file"] + else f"{final_patches_output_path}/patch_7_rero.log" + ) + error_log = ( + arguments["--error-log"] + if arguments["--error-log"] + else f"{final_patches_output_path}/patch_7_rero_errors.log" + ) + s3_input_bucket = ( + arguments["--input-bucket"] + if arguments["--input-bucket"] + else "canonical-data" + ) + s3_output_bucket = ( + arguments["--output-bucket"] + if arguments["--output-bucket"] + else "canonical-staging" + ) + canonical_repo_path = ( + arguments["--canonical-repo-path"] + if arguments["--canonical-repo-path"] + else "/home/piconti/impresso-text-acquisition" + ) + + init_logger(logger, logging.INFO, log_file) + logger.info("Arguments: \n %s", arguments) + + RERO_1_TITLES = ['LCG', 'LBP', 'LTF', 'DLE'] + PROP_NAME = 'c' + temp_dir = os.path.join(local_base_path, 'temp_dir') + empty_folder(temp_dir) + + logger.info("Patching titles %s: rescaling %s property at issue and page level", RERO_1_TITLES, PROP_NAME) + + logger.info(f"Fetching the list of titles to patch") + all_to_patch_path = os.path.join(local_base_path, "all_issues_to_patch_4.json") + all_to_inv_path = os.path.join(local_base_path, "all_issues_to_investigate_4.json") + + with open(all_to_patch_path, mode ='r', encoding='utf-8') as f: + all_to_patch = json.load(f) + + with open(all_to_inv_path, mode ='r', encoding='utf-8') as f: + all_to_inv = json.load(f) + + logger.info("Fetching the page and issues files from S3...") + # download the issues of interest for this patch + rero_issues, rero_pages = fetch_files(s3_input_bucket, False, 'both', RERO_1_TITLES) + + #### PERFORMING THE ACTUAL PATCHING + + logger.info("Fetched the page and issues files from S3, starting the patching...") + + # extract the title and convert the coordinates + logger.info("Converting the coordinates inside the issues...") + patched_rero_issues = ( + rero_issues + .map_partitions(lambda i_list: [(i, i['id'].split('-')[0]) for i in i_list]) + .map_partitions( + lambda i_list: [ + find_convert_coords(i, np, all_to_patch[np], all_to_inv[np]) + for (i, np) in i_list + ] + ) + ).persist() + + logger.info("Converting the coordinates inside the pages...") + patched_rero_pages = ( + rero_pages + .map_partitions(lambda p_list: [(p, p['id'].split('-')[0]) for p in p_list]) + .map_partitions( + lambda p_list: [ + find_convert_coords(p, np, all_to_patch[np], all_to_inv[np], is_issue=False) + for (p, np) in p_list + ] + ) + ).persist() + + #### WRITING THE OUTPUT TO S3 + logger.info("Uploading the updated issues to s3 bucket %s...", s3_output_bucket) + rero_issue_files = ( + patched_rero_issues + .map_partitions(lambda i_l: [i[0] for i in i_l]) + .map_partitions(title_year_pair_to_issues) + .map_partitions( + lambda issues: write_upload_issues( + issues[0], issues[1], + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, + ) + ) + ).compute() + + # free the memory allocated + del rero_issue_files + + logger.info("Uploading the updated page files to s3 bucket %s...", s3_output_bucket) + rero_page_files = ( + patched_rero_pages + .map_partitions(lambda pages: [p[0] for p in pages]) + .map_partitions(to_issue_id_pages_dict) + .map_partitions(lambda issue_to_pages: nzz_write_upload_pages( + issue_to_pages, + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, + ) + ) + .flatten() + ).compute() + + # free the memory allocated + del rero_page_files + + #### KEEPING TRACK OF THE RESULTS: OUTPUT AND MANIFEST + logger.info("Aggregating the patching information of issues for future reference...") + # extract only the "patch_info" dict to keep track of which issue/page has been correctly patched + patch_info_issues = ( + patched_rero_issues + .map_partitions(lambda i_l: [i[1] for i in i_l]) + .to_dataframe() + ).compute() + + logger.info("Aggregating the patching information of pages for future reference...") + patch_info_pages = ( + patched_rero_pages + .map_partitions(lambda i_l: [i[1] for i in i_l]) + .to_dataframe() + .groupby(by=['issue_id', 'page_patching_done', 'dest_res', + 'curr_res', 'used_image_info_file', 'all_scaled']) + .agg({'page_id': 'count'}) + .rename(columns={'page_id': 'num_pages'}) + .reset_index() + ).compute() + + logger.info("Merging the two and writing the dataframe to disk.") + patched_info_merged_df = patch_info_issues.merge(patch_info_pages, how='outer') + patched_info_merged_df.to_csv(os.path.join(final_patches_output_path, 'all_patched_issues.csv')) + + + logger.info("Finished with the patching, creating the manifest...") + + # create the config for the manifest computation + manifest_config = { + "data_stage": "canonical", + "output_bucket": s3_output_bucket, + "input_bucket": s3_input_bucket, + "git_repository": canonical_repo_path, + "newspapers": RERO_1_TITLES, + "temp_directory": temp_dir, + "previous_mft_s3_path": None, + "is_staging": True, + "is_patch": True, + "patched_fields": [PROP_NAME], + "push_to_git": False, + "file_extensions": "issues.jsonl.bz2", + "log_file": log_file, + "notes": f"Patching RERO 1 data ({RERO_1_TITLES}) to rescale their coordinates (patch_7)." + } + # create and upload the manifest + create_manifest(manifest_config) + +if __name__ == "__main__": + main() From 3e300256f09da0f0f8e900f5b5a69684edbee11a Mon Sep 17 00:00:00 2001 From: piconti Date: Fri, 22 Mar 2024 12:00:50 +0100 Subject: [PATCH 38/53] minor bugfix --- text_importer/scripts/patching/canonical_patch_7_rero_olive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_importer/scripts/patching/canonical_patch_7_rero_olive.py b/text_importer/scripts/patching/canonical_patch_7_rero_olive.py index 9cd25c39..03c32dfb 100644 --- a/text_importer/scripts/patching/canonical_patch_7_rero_olive.py +++ b/text_importer/scripts/patching/canonical_patch_7_rero_olive.py @@ -125,7 +125,7 @@ def find_convert_coords( # for LCG, only years later than 1891 need to be fixed - if title != 'LCG' or issue_id.split('-')[1]>1891: + if title != 'LCG' or int(issue_id.split('-')[1])>1891: if issue_id in to_patch: res = to_patch[issue_id] # keep trace of whether or not we fetched the information from the image info file From 020cbd683f9f48b6fe8e22ae07e6e2bb9faf7532 Mon Sep 17 00:00:00 2001 From: piconti Date: Fri, 22 Mar 2024 13:55:32 +0100 Subject: [PATCH 39/53] Add meta to dataframe conversion to prevent type errors --- .../scripts/patching/canonical_patch_7_rero_olive.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/text_importer/scripts/patching/canonical_patch_7_rero_olive.py b/text_importer/scripts/patching/canonical_patch_7_rero_olive.py index 03c32dfb..3d8d19ab 100644 --- a/text_importer/scripts/patching/canonical_patch_7_rero_olive.py +++ b/text_importer/scripts/patching/canonical_patch_7_rero_olive.py @@ -282,14 +282,20 @@ def main(): patch_info_issues = ( patched_rero_issues .map_partitions(lambda i_l: [i[1] for i in i_l]) - .to_dataframe() + .to_dataframe(meta={'issue_id': str, 'issue_patching_done': bool, + 'num_pages': "Int64", 'dest_res': "Int64", + "curr_res": "Int64", 'zip_contents': str, + 'used_image_info_file': bool}) ).compute() logger.info("Aggregating the patching information of pages for future reference...") patch_info_pages = ( patched_rero_pages .map_partitions(lambda i_l: [i[1] for i in i_l]) - .to_dataframe() + .to_dataframe(meta={'issue_id': str, 'page_patching_done': bool, + 'page_id': str, 'dest_res': "Int64", + "curr_res": "Int64", 'zip_contents': str, + 'used_image_info_file': bool, "all_scaled": bool}) .groupby(by=['issue_id', 'page_patching_done', 'dest_res', 'curr_res', 'used_image_info_file', 'all_scaled']) .agg({'page_id': 'count'}) @@ -316,7 +322,7 @@ def main(): "is_staging": True, "is_patch": True, "patched_fields": [PROP_NAME], - "push_to_git": False, + "push_to_git": True, "file_extensions": "issues.jsonl.bz2", "log_file": log_file, "notes": f"Patching RERO 1 data ({RERO_1_TITLES}) to rescale their coordinates (patch_7)." From 04872558d52c07342b12e3626249828b10650739 Mon Sep 17 00:00:00 2001 From: piconti Date: Fri, 22 Mar 2024 15:40:15 +0100 Subject: [PATCH 40/53] add dev notebook to identify patch 7 exact issues --- notebooks/patch_7_find_titles.ipynb | 1239 +++++++++++++++++++++++++++ 1 file changed, 1239 insertions(+) create mode 100644 notebooks/patch_7_find_titles.ipynb diff --git a/notebooks/patch_7_find_titles.ipynb b/notebooks/patch_7_find_titles.ipynb new file mode 100644 index 00000000..7e26960c --- /dev/null +++ b/notebooks/patch_7_find_titles.ipynb @@ -0,0 +1,1239 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dev notebook for patch 7: Rescaling the coordinates of RERO 1 canonical data\n", + "\n", + "Patch 7 [link](https://docs.google.com/spreadsheets/d/1m-EaqsYpclDuUzE4vcl2DRgyrPQ6aauoMUAwpthx8Y4/edit?pli=1#gid=1323940846) concerns the coordinates of the various regions for some of the RERO 1 (Olive) data.\n", + "\n", + "This is in particular due to the conversion of images to jp2 formats, were sometimes the \"png_highest\" strategy used did not work as intended, leaving a mismatch between the expected and actual dimensions of the image, leading to incorrect coordinates conversion.\n", + "\n", + "The information about such conversion was logged in files named image-info.json for each issue, which can be used to identify which ones had an incorrect conversion. \n", + "\n", + "At the time of the identification of this issue, the 2 steps proposed fix was:\n", + "1. identifiying all the issues concerned with this issue (aka the source image used for jp2 conversion is not the largest one available)\n", + "2. patching concerned issues by rescaling all coordinates by factor (dest_res/curr_res) where dest_res is the smaller one (one of the jp2 files) and curr_res is the largest resolution which should have been selected initially.\n", + "\n", + "This notebook aims at identifying which issues need patching, and subsequently correcting the coordinates in all the necessary issue and page files." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from bs4 import BeautifulSoup, element\n", + "import os\n", + "from text_importer.importers.mets_alto import alto, mets\n", + "from text_importer.importers.bl import classes, detect\n", + "from IPython.display import display\n", + "import cv2 as cv\n", + "from PIL import Image, ImageDraw, ImageFont\n", + "import json\n", + "import jsonlines\n", + "import git\n", + "import dask.bag as db\n", + "from zipfile import ZipFile\n", + "import logging\n", + "from text_importer.utils import init_logger\n", + "from impresso_commons.images import img_utils\n", + "from collections import defaultdict\n", + "import re\n", + "from impresso_commons.utils.s3 import fixed_s3fs_glob, IMPRESSO_STORAGEOPT, alternative_read_text\n", + "from impresso_commons.path.path_s3 import fetch_files\n", + "from text_importer.scripts.patching.canonical_patch_1_uzh import write_jsonlines_file, title_year_pair_to_issues, write_upload_issues, to_issue_id_pages_dict, nzz_write_upload_pages" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def read_json(file_path):\n", + " lines = []\n", + " with open(file_path, \"r\") as file:\n", + " for line in file:\n", + " lines.append(json.loads(line))\n", + " return lines" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def coords_to_xy(coords):\n", + " return [coords[0], coords[1], coords[0]+coords[2], coords[1]+coords[3]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def draw_box_on_img(base_img_path, coords_xy, img = None, width=10):\n", + " if not img:\n", + " img = Image.open(base_img_path) \n", + " ImageDraw.Draw(img).rectangle(coords_xy, outline =\"red\", width=width)\n", + " return img" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def read_xml(file_path):\n", + " with open(file_path, 'rb') as f:\n", + " raw_xml = f.read()\n", + "\n", + " return BeautifulSoup(raw_xml, 'xml')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def scale_coords(coords, curr_res, des_res):\n", + " return [int(c*int(des_res)/int(curr_res)) for c in coords]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_regions_for_ci(canonical_page, ci_id):\n", + " return [r['c'] for r in canonical_page['r'] if ci_id in r['pOf']]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def read_pages_from_s3(issue_id, bucket = 'canonical-data'):\n", + " title = issue_id.split('-')[0]\n", + " s3_path = f\"s3://{bucket}/{title}/pages/{title}-{issue_id.split('-')[1]}/{issue_id}-pages.jsonl.bz2\"\n", + " return [json.loads(t) for t in alternative_read_text(s3_path, IMPRESSO_STORAGEOPT)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def test_scale_coords(issue_id, page_nums, curr_res, dest_res):\n", + " i_pages = read_pages_from_s3(issue_id)\n", + " i_first_page = i_pages[page_nums[0]]\n", + " i_second_page = i_pages[page_nums[1]]\n", + "\n", + " f_pg_iiif = i_first_page['iiif']\n", + " s_pg_iiif = i_second_page['iiif']\n", + " print(f\"iiif pg_{page_nums[0]+1}: \", f_pg_iiif, f\", iiif pg_{page_nums[1]+1}: \", s_pg_iiif)\n", + "\n", + " f_page_r1 = i_first_page['r'][0]['c']\n", + " s_page_r1 = i_second_page['r'][0]['c']\n", + "\n", + " print(\"first_page_r1 current coords: \", f_page_r1)\n", + " print(\"second_page_r1 current coords: \", s_page_r1)\n", + "\n", + " scaled_f_page_r1 = scale_coords(f_page_r1, curr_res, dest_res)\n", + " scaled_s_page_r1 = scale_coords(s_page_r1, curr_res, dest_res)\n", + "\n", + " print(\"scaled_first_page_r1 updated coords: \", scaled_f_page_r1)\n", + " print(\"scaled_second_page_r1 updated coords: \", scaled_s_page_r1)\n", + " return i_pages, f_page_r1, scaled_f_page_r1, s_page_r1, scaled_s_page_r1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Part 1: Find specific newspaper titles to fix for patch 7\n", + "\n", + "The code for this was put into a script: impresso-text-acquisition/text_importer/scripts/patching/canonical_patch_7_find_issues.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Part 2: Find the exact issues to fix for patch 7 and identify which can be fixed (enough information) and which can't\n", + "\n", + "### Once the necessary information is fetched, create the conversion dict, and convert the coordinates\n", + "\n", + "For each of the issues:\n", + "- Read in it's image-info.json file: which strategy was used and which file was used\n", + "- If the strategy was 'png_highest', and that resolutions higher than the one used are in the Document.zip then:\n", + " - write to a dict with issue (page?) ID as key: the file used, the strategy, the dest_res=resolution of the files used and the curr_res=largest resolution available.\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "logger = logging.getLogger()\n", + "log_file = '/home/piconti/impresso-text-acquisition/text_importer/data/patch_logs/patch_7_issues_to_patch_4.log'\n", + "if os.path.isfile(log_file):\n", + " os.remove(log_file)\n", + "init_logger(logger, logging.INFO, log_file)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "info_files_base_path = '/scratch/piconti/impresso/patch_7'\n", + "info_file = os.path.join(info_files_base_path, \"{}_img_res_info.json\")\n", + "issues_to_patch_file = os.path.join(info_files_base_path, \"{}_issues_to_patch_4.json\")\n", + "issues_to_inv_file = os.path.join(info_files_base_path, \"{}_issues_to_investigate_4.json\")\n", + "issues_not_to_touch_file = os.path.join(info_files_base_path, \"{}_issues_not_to_touch_4.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_resolutions(res_file_dict, issue_id):\n", + " pg_res = {'all': []}\n", + " for f in res_file_dict['original']['resolutions']:\n", + " pg = int(f.split(\"/\")[0])\n", + " res = int(os.path.basename(f).split(\".\")[0].split(\"_\")[1])\n", + " if res not in pg_res['all']:\n", + " pg_res['all'].append(res)\n", + " if pg in pg_res:\n", + " pg_res[pg].append(res)\n", + " else:\n", + " pg_res[pg] = [res]\n", + "\n", + " #print(pg_res)\n", + " if all([all([r in pg_res['all'] for r in v]) for k, v in pg_res.items()]):\n", + " #print(f\"{issue_id}: All page images have the same possible resolutions: {pg_res['all']}\")\n", + " logger.debug(\" - %s: All page images have the same possible resolutions: %s\", issue_id, pg_res['all'])\n", + " issue_res = pg_res['all']\n", + " else:\n", + " #print(f\"{issue_id}: Possible resolutions vary with the page: {pg_res}\")\n", + " logger.warning(\" - %s: Possible resolutions vary with the page: %s\", issue_id, pg_res)\n", + " del pg_res['all']\n", + " issue_res = pg_res\n", + "\n", + " return issue_res" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def check_if_rescale(p, scaling, possible_res, issue_id):\n", + " if isinstance(possible_res, dict):\n", + " pos_res = possible_res[p]\n", + " else:\n", + " pos_res = possible_res\n", + "\n", + " if '_' in scaling['source_used']:\n", + " source_res = int(os.path.basename(scaling['source_used']).split(\".\")[0].split(\"_\")[1])\n", + " else:\n", + " if scaling['strat'] == 'png_highest':\n", + " logger.warning(\" %s: No resolution information in the file used to rescale, but should based on strategy: %s\", issue_id, scaling)\n", + " return None, {'dest_res':None, 'curr_res':max(pos_res)}\n", + " else:\n", + " logger.debug(\" %s: No resolution information in the file used to rescale, but not strategy: %s\", issue_id, scaling)\n", + " return False, {'dest_res':None, 'curr_res':max(pos_res)}\n", + "\n", + " dest_res = source_res\n", + " curr_res = max(pos_res)\n", + " if source_res != curr_res:\n", + " if scaling['strat'] == 'png_highest':\n", + " if p == 1:\n", + " logger.info(\" %s: Had strat 'png_highest', but used %s instead out of possibilities %s\", issue_id, source_res, pos_res)\n", + " #print(f\"{issue_id}: Had strat 'png_highest', but used {source_res} instead (out of possibilities {pos_res})\")\n", + " to_rescale = True\n", + " if to_rescale:\n", + " logger.debug(f\" {issue_id}: to_rescale: {to_rescale}, dest_res: {dest_res}, curr_res: {curr_res}\")\n", + " else:\n", + " if p == 1:\n", + " logger.info(\" %s: Had strat %s, but used %s instead out of possibilities %s\", issue_id, scaling['strat'], source_res, pos_res)\n", + " #print(f\"{issue_id}: Had strat {scaling['strat']}, but used {source_res} instead out of possibilities {pos_res} --> to check by hand!\")\n", + " to_rescale = None\n", + " logger.debug(f\" {issue_id}: to investigate, dest_res: {dest_res}, curr_res: {curr_res}\")\n", + "\n", + " return to_rescale, {'dest_res':dest_res, 'curr_res':curr_res}\n", + " \n", + " return False, {'dest_res':dest_res, 'curr_res':curr_res}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def write_to_disk(title, contents, filename, log_msg):\n", + " filepath = filename.format(title)\n", + " logger.info(\"%s: Wirting the list of issues %s to disk: %s\", title, log_msg, filepath)\n", + " with open(filepath, \"w\", encoding=\"utf-8\") as f_out:\n", + " json.dump(contents, f_out, ensure_ascii=False, indent=4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def handle_missing_img_info(issue_id, title, issue_info, no_to_touch, to_inv):\n", + " # try to handle the various cases that can arise when the image info is missing to still identify the issues in need of patching, \n", + " # and the resolutions to use in each case.\n", + " if any(['.tif' in f for f in issue_info['original']['zip_img_contents']]):\n", + " logger.warning(\" %s: No scaling info present, but tif images present in zip\", issue_id)\n", + " no_to_touch.append(issue_id)\n", + " elif title == 'LES' and all(['.jpg' not in f for f in issue_info['original']['zip_img_contents']]):\n", + " logger.debug(\" %s: No scaling info present, but LES and no jpg images present in zip.\", issue_id)\n", + " no_to_touch.append(issue_id)\n", + " elif title == 'LCG' and int(issue_id.split('-')[1])<1892:\n", + " logger.debug(\" %s: No scaling info present, but LCG and earlier than 1891.\", issue_id)\n", + " no_to_touch.append(issue_id)\n", + " else:\n", + " issue_possible_res = get_resolutions(issue_info, issue_id)\n", + " to_inv[issue_id] = {}\n", + " if isinstance(issue_possible_res, dict):\n", + " for p, pos_res in issue_possible_res.items():\n", + " to_inv[issue_id][p] = {'dest_res': min(pos_res), 'curr_res':max(pos_res)}\n", + " else:\n", + " to_inv[issue_id] = {'dest_res':min(issue_possible_res), 'curr_res':max(issue_possible_res)}\n", + " to_inv[issue_id]['zip_contents'] = issue_info['original']['zip_img_contents']\n", + " logger.warning(\" %s: No scaling info present, but multiple resolutions available: %s\", issue_id, issue_info['original']['zip_img_contents'])\n", + " \n", + " return no_to_touch, to_inv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_issues_to_patch_for_title(title: str, info_file=info_file, issues_to_patch_file=issues_to_patch_file) -> tuple[dict, dict, str]:\n", + " info_file_path = info_file.format(title)\n", + "\n", + " with open(info_file_path, mode ='r', encoding='utf-8') as f:\n", + " title_info = json.load(f)\n", + " logger.info(f\"----- Reading the info file for {title}: {len(title_info)} issues -----\")\n", + " \n", + " # dict of issues to patch for title: issue_id -> {resolutions}\n", + " issues_to_patch = {}\n", + " issues_to_investigate = {}\n", + " issues_not_to_rescale = []\n", + "\n", + " logger.info(\"Starting to identify the issues to patch...\")\n", + " for issue_id, info in title_info.items():\n", + " # check if the image-info file is present and non-empty\n", + " if info['img']['file_present'] and len(info['img']['info_f_contents'])!=0:\n", + " # check if the Document.zip file was present and \n", + " if 'original' in info and 'resolutions' in info['original']:\n", + " issue_possible_res = get_resolutions(info, issue_id)\n", + " else:\n", + " # if the files don't have their resolution, we have no way of knowing how to scale if there is an issue\n", + " issues_not_to_rescale.append(issue_id)\n", + " continue\n", + " \n", + " patch_d, inv_d = {}, {}\n", + " # take note of the needed action (rescaling or not) for each page\n", + " for idx, scaling in info['img']['info_f_contents'].items():\n", + " p_num = int(idx)+1\n", + " to_rescale, res_dict = check_if_rescale(p_num, scaling, issue_possible_res, issue_id)\n", + " \n", + " if to_rescale is None:\n", + " inv_d[p_num] = res_dict\n", + " elif to_rescale:\n", + " patch_d[p_num] = res_dict\n", + " \n", + " # once all pages have been traversed, add the information to the final dicts/lists\n", + " if len(inv_d) == 0:\n", + " if len(patch_d) == 0:\n", + " # no rescaling needed\n", + " issues_not_to_rescale.append(issue_id)\n", + " elif all([patch_d[1] == v for v in patch_d.values()]) and len(info['img']['info_f_contents']) == len(patch_d):\n", + " # same rescaling for all issues\n", + " issues_to_patch[issue_id] = patch_d[1]\n", + " else:\n", + " logger.warning(f\" -->> {issue_id}: not all pages have the same patching: {patch_d}!!!\")\n", + " issues_to_patch[issue_id] = patch_d\n", + " else:\n", + " issues_to_investigate[issue_id] = inv_d\n", + " else:\n", + " # if the file is not present, we cannot know which approach was chosen.\n", + " if 'original' in info and 'resolutions' in info['original']:\n", + " issues_not_to_rescale, issues_to_investigate = handle_missing_img_info(issue_id, title, info, issues_not_to_rescale, issues_to_investigate)\n", + " else:\n", + " issues_not_to_rescale.append(issue_id)\n", + "\n", + " if not len(title_info) == len(issues_to_patch) + len(issues_to_investigate) +len(issues_not_to_rescale):\n", + " logger.warning(f\"Problem: counts not matching: {len(title_info)}: {len(issues_to_patch) + len(issues_to_investigate) +len(issues_not_to_rescale)}\")\n", + "\n", + " logger.info((\n", + " f\" Done for {title} : {len(issues_to_patch)}/{len(title_info)} need to be rescaled, \"\n", + " f\" {len(issues_to_investigate)}/{len(title_info)} need to be investigated, \"\n", + " f\" and {len(issues_not_to_rescale)}/{len(title_info)} can be left as-is. \"\n", + " ))\n", + "\n", + " if len(issues_to_patch) != 0:\n", + " write_to_disk(title, issues_to_patch, issues_to_patch_file, 'needing rescaling')\n", + "\n", + " return issues_to_patch, issues_to_investigate, issues_not_to_rescale" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "_, rero_journal_dirs, _ = next(os.walk(\"/mnt/project_impresso/original/RERO/\"))\n", + "rero_titles = [\"LCG\", \"DLE\", \"LNF\", \"LBP\", \"LSE\", \"EXP\"]\n", + "rero_titles.extend(rero_journal_dirs)\n", + "rero_titles = list(set(rero_titles))\n", + "logger.info(\"Will process titles: %s\", rero_titles)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# dict of issues to patch for title: issue_id -> {resolutions}\n", + "all_issues_to_patch = {}\n", + "all_issues_to_investigate = {}\n", + "all_issues_not_to_rescale = []" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for title in rero_titles:\n", + " # identify the exact issues to scale for the title and get the rescaling values\n", + " issues_to_patch, issues_to_investigate, issues_not_to_rescale = get_issues_to_patch_for_title(title)\n", + "\n", + " # add this information to the information collected for previous titles & write the updated files to disk\n", + " if len(issues_to_patch) != 0:\n", + " all_issues_to_patch[title] = issues_to_patch\n", + " write_to_disk('all', all_issues_to_patch, issues_to_patch_file, 'needing rescaling')\n", + " if len(issues_to_investigate) != 0:\n", + " all_issues_to_investigate[title] = issues_to_investigate\n", + " write_to_disk('all', all_issues_to_investigate, issues_to_inv_file, 'needing investigating')\n", + " if len(issues_not_to_rescale) != 0:\n", + " all_issues_not_to_rescale.extend(issues_not_to_rescale)\n", + " write_to_disk('all', all_issues_not_to_rescale, issues_not_to_touch_file, 'not needing rescaling.')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "all_issues_to_patch.keys(), all_issues_to_investigate.keys()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Investigation of issues with missing information" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### LES" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "LES_info_file_path = info_file.format('LES')\n", + "\n", + "with open(LES_info_file_path, mode ='r', encoding='utf-8') as f:\n", + " LES_title_info = json.load(f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "les_ok_1 = 'LES-2009-02-01-a'\n", + "les_jpg_1 = 'LES-2011-05-01-a'\n", + "les_jpg_2 = 'LES-2006-02-01-a'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "LES_title_info[les_ok_1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### LES-2009-02-01-a\n", + "\n", + "Comments/Conclusions:\n", + "- Page 1:\n", + " - [218,1208,720,1236] – original coords cannot be displayed.\n", + " - [142,790,471,809] - new coordinates can be displayed (and seem to display the correct region) but not perfect.\n", + "- Page 5:\n", + " - [65,77,456,126] – original coords can be displayed, but don't displace the correct region of text\n", + " - [42,50,298,82] - new coordinates can be displayed, display the correct region, but not perfect (too large on the right)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "les_ok_pages = read_pages_from_s3(les_ok_1)\n", + "les_ok_page_1 = les_ok_pages[0]\n", + "les_ok_page_5 = les_ok_pages[4]\n", + "\n", + "pg_1_iiif = les_ok_page_1['iiif']\n", + "pg_5_iiif = les_ok_page_5['iiif']\n", + "print(\"iiif pg_1: \", pg_1_iiif, \", iiif pg_5: \", pg_5_iiif)\n", + "\n", + "les_ok_page_1_r1 = les_ok_page_1['r'][0]['c']\n", + "les_ok_page_5_r1 = les_ok_page_5['r'][0]['c']\n", + "\n", + "print(\"les_ok_page_1_r1 current coords: \", les_ok_page_1_r1)\n", + "print(\"les_ok_page_5_r1 current coords: \", les_ok_page_5_r1)\n", + "\n", + "dest_res, curr_res = 72, 110\n", + "\n", + "scaled_les_ok_page_1_r1 = scale_coords(les_ok_page_1_r1, curr_res, dest_res)\n", + "scaled_les_ok_page_5_r1 = scale_coords(les_ok_page_5_r1, curr_res, dest_res)\n", + "\n", + "print(\"scaled_les_ok_page_1_r1 updated coords: \", scaled_les_ok_page_1_r1)\n", + "print(\"scaled_les_ok_page_5_r1 updated coords: \", scaled_les_ok_page_5_r1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### LES-2011-05-01-a\n", + "\n", + "Comments/Conclusions:\n", + "- Page 1:\n", + " - [79,52,468,81] – Display a cropped part of the \"L'essor\" title.\n", + " - [51,34,306,53] - new coordinates can be displayed (and seem to display the correct region) but not perfect.\n", + "- Page 5:\n", + " - [65, 291, 761, 339] – original coords can be displayed, but don't displace the correct region of text\n", + " - [42, 190, 498, 221] - new coordinates can be displayed, and look good" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "les_jpg1_pages = read_pages_from_s3(les_jpg_1)\n", + "les_jpg1_page_1 = les_jpg1_pages[0]\n", + "les_jpg1_page_5 = les_jpg1_pages[4]\n", + "\n", + "jpg1_pg_1_iiif = les_jpg1_page_1['iiif']\n", + "jpg1_pg_5_iiif = les_jpg1_page_5['iiif']\n", + "print(\"iiif pg_1: \", jpg1_pg_1_iiif, \", iiif pg_5: \", jpg1_pg_5_iiif)\n", + "\n", + "les_jpg1_page_1_r1 = les_jpg1_page_1['r'][0]['c']\n", + "les_jpg1_page_5_r1 = les_jpg1_page_5['r'][0]['c']\n", + "\n", + "print(\"les_jpg1_page_1_r1 current coords: \", les_jpg1_page_1_r1)\n", + "print(\"les_jpg1_page_5_r1 current coords: \", les_jpg1_page_5_r1)\n", + "\n", + "curr_res, dest_res = 110, 72\n", + "\n", + "scaled_les_jpg1_page_1_r1 = scale_coords(les_jpg1_page_1_r1, curr_res, dest_res)\n", + "scaled_les_jpg1_page_5_r1 = scale_coords(les_jpg1_page_5_r1, curr_res, dest_res)\n", + "\n", + "print(\"scaled_les_jpg1_page_1_r1 updated coords: \", scaled_les_jpg1_page_1_r1)\n", + "print(\"scaled_les_jpg1_page_5_r1 updated coords: \", scaled_les_jpg1_page_5_r1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### LES-2006-02-01-a\n", + "\n", + "Comments/Conclusions:\n", + "- Page 1:\n", + " - [218,1208,720,1236] – original coords cannot be displayed.\n", + " - [142,790,471,809] - new coordinates can be displayed (and seem to display the correct region) but not perfect.\n", + "- Page 5:\n", + " - [65,77,456,126] – original coords can be displayed, but don't displace the correct region of text\n", + " - [42,50,298,82] - new coordinates can be displayed, display the correct region, but not perfect (too large on the right)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "les_jpg2_pages = read_pages_from_s3(les_jpg_2)\n", + "les_jpg2_page_1 = les_jpg2_pages[0]\n", + "les_jpg2_page_5 = les_jpg2_pages[4]\n", + "\n", + "jpg2_pg_1_iiif = les_jpg2_page_1['iiif']\n", + "jpg2_pg_5_iiif = les_jpg2_page_5['iiif']\n", + "print(\"iiif pg_1: \", jpg2_pg_1_iiif, \", iiif pg_5: \", jpg2_pg_5_iiif)\n", + "\n", + "les_jpg2_page_1_r1 = les_jpg2_page_1['r'][0]['c']\n", + "les_jpg2_page_5_r1 = les_jpg2_page_5['r'][0]['c']\n", + "\n", + "print(\"les_jpg2_page_1_r1 current coords: \", les_jpg2_page_1_r1)\n", + "print(\"les_jpg2_page_5_r1 current coords: \", les_jpg2_page_5_r1)\n", + "\n", + "curr_res, dest_res = 110, 72\n", + "\n", + "scaled_les_jpg2_page_1_r1 = scale_coords(les_jpg2_page_1_r1, curr_res, dest_res)\n", + "scaled_les_jpg2_page_5_r1 = scale_coords(les_jpg2_page_5_r1, curr_res, dest_res)\n", + "\n", + "print(\"scaled_les_jpg2_page_1_r1 updated coords: \", scaled_les_jpg2_page_1_r1)\n", + "print(\"scaled_les_jpg2_page_5_r1 updated coords: \", scaled_les_jpg2_page_5_r1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### DLE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### DLE-1914-01-28-a\n", + "\n", + "Comments/Conclusions:\n", + "- Page 1:\n", + " - [303, 208, 141, 41] – Word appears, but wrong one.\n", + " - [162, 111, 75, 22] - Correctly displayed.\n", + "- Page 3:\n", + " - [167, 59, 553, 64] – original coords can be displayed, but don't display the correct region of text\n", + " - [89, 31, 296, 34] - new coordinates can be displayed, display the correct region." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dle_inv_1 = 'DLE-1914-01-28-a'\n", + "dle_inv_pages, dle_inv_page_1_r1, dle_inv_page_5_r1, scaled_dle_inv_page_1_r1, scaled_dle_inv_page_5_r1 = test_scale_coords(dle_inv_1, [0, 2], 108, 58)\n", + "dle_inv_page_1_r1, dle_inv_page_5_r1, scaled_dle_inv_page_1_r1, scaled_dle_inv_page_5_r1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### EXP" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "EXP_info_file_path = info_file.format('EXP')\n", + "\n", + "with open(EXP_info_file_path, mode ='r', encoding='utf-8') as f:\n", + " EXP_title_info = json.load(f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "all_issues_to_investigate['EXP']['EXP-2015-07-08-a']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "EXP_title_info['EXP-2016-04-22-a']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp_test_issue = 'EXP-2016-04-22-a'\n", + "exp_i_pages = read_pages_from_s3(exp_test_issue)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp_i_pages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp_i_p1 = exp_i_pages[0]\n", + "exp_i_p1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "neuch_coords = [57, 480, 567, 612]\n", + "\n", + "scaled_neuch_coords = scale_coords(neuch_coords, 160, 108)\n", + "neuch_coords_xy = coords_to_xy(neuch_coords)\n", + "scaled_neuch_coords_xy = coords_to_xy(scaled_neuch_coords)\n", + "\n", + "# None of them work, 57,530,567,120 works\n", + "neuch_coords_xy, scaled_neuch_coords, scaled_neuch_coords_xy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "EXP_title_info[exp_test_2_issue]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# test with an early issue with the scaling issue identified\n", + "exp_test_2_issue = 'EXP-1902-09-20-a'\n", + "exp_2_i_pages = [json.loads(p) for p in read_pages_from_s3(exp_test_2_issue)]\n", + "exp_2_i_p1 = exp_2_i_pages[0]\n", + "#exp_2_i_p1 = json.loads(exp_2_i_p1)\n", + "exp_2_i_p1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "r_coords = exp_2_i_p1['r'][0]['c']\n", + "\n", + "scaled_r_coords = scale_coords(r_coords, 144, 72)\n", + "r_coords_xy = coords_to_xy(r_coords)\n", + "scaled_r_coords_xy = coords_to_xy(scaled_r_coords)\n", + "\n", + "# scaled_r_coords work as ints [12, 1090, 348, 1118]\n", + "r_coords_xy, scaled_r_coords, scaled_r_coords_xy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# test with an early issue without img info\n", + "exp_test_3_issue = 'EXP-1902-09-19-a'\n", + "exp_3_i_pages = [json.loads(p) for p in read_pages_from_s3(exp_test_3_issue)]\n", + "exp_3_i_p1 = exp_3_i_pages[0]\n", + "#exp_2_i_p1 = json.loads(exp_2_i_p1)\n", + "print(exp_3_i_p1['r'][0])\n", + "\n", + "r2_coords = exp_3_i_p1['r'][0]['c']\n", + "\n", + "scaled_r2_coords = scale_coords(r2_coords, 144, 72)\n", + "r2_coords_xy = coords_to_xy(r2_coords)\n", + "scaled_r2_coords_xy = coords_to_xy(scaled_r2_coords)\n", + "\n", + "# scaled_r_coords work as ints [14,303,164,206]\n", + "r2_coords, r2_coords_xy, scaled_r2_coords, scaled_r2_coords_xy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp_issue = 'EXP-2010-11-29-a'\n", + "\n", + "exp_pages = read_pages_from_s3(exp_issue)\n", + "exp_pages[6]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### LCG" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### LCG-1892-07-20-a\n", + "\n", + "Comments/Conclusions:\n", + "- Page 1:\n", + " - [106, 511, 288, 22] – Word appears, but wrong one.\n", + " - [71, 344, 194, 14] - Correctly displayed.\n", + "- Page 4: first region is not text, but second one works\n", + " - [298, 166, 537, 45] – original coords can be displayed, but don't display the correct region of text\n", + " - [200, 111, 361, 30] - new coordinates can be displayed, display the correct region." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lcg_inv_issue = 'LCG-1892-07-20-a'\n", + "lcg_inv_pages, lcg_inv_page_1_r1, lcg_inv_page_4_r1, scaled_lcg_inv_page_1_r1, scaled_lcg_inv_page_4_r1 = test_scale_coords(lcg_inv_issue, [0, 3], 144, 97)\n", + "lcg_inv_page_1_r1, lcg_inv_page_4_r1, scaled_lcg_inv_page_1_r1, scaled_lcg_inv_page_4_r1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lcg_inv_pages[3]['r'][:3]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "LCG_info_file_path = info_file.format('LCG')\n", + "\n", + "with open(LCG_info_file_path, mode ='r', encoding='utf-8') as f:\n", + " LCG_title_info = json.load(f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "LCG_title_info['LCG-1892-06-01-a']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### LBP" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### LBP-1881-05-18-a\n", + "\n", + "Comments/Conclusions:\n", + "- Page 1:\n", + " - [330, 439, 1134, 78] – region appears, but wrong one.\n", + " - [183, 243, 630, 43] - Correctly displayed.\n", + "- Page 4: first region is not text, but second one works\n", + " - [104, 116, 937, 121] – original coords can be displayed, but don't display the correct region of text\n", + " - [57, 64, 520, 67] - new coordinates can be displayed, display the correct region." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lbp_inv_issue = 'LBP-1881-05-18-a'\n", + "lbp_inv_pages, lbp_inv_page_1_r1, lbp_inv_page_4_r1, scaled_lbp_inv_page_1_r1, scaled_lbp_inv_page_4_r1 = test_scale_coords(lbp_inv_issue, [0, 3], 108, 60)\n", + "lbp_inv_page_1_r1, lbp_inv_page_4_r1, scaled_lbp_inv_page_1_r1, scaled_lbp_inv_page_4_r1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lbp_inv_pages[3]['r'][0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "LBP_info_file_path = info_file.format('LBP')\n", + "\n", + "with open(LBP_info_file_path, mode ='r', encoding='utf-8') as f:\n", + " LBP_title_info = json.load(f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### LTF" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### LTF-1905-08-09-a\n", + "\n", + "Comments/Conclusions:\n", + "- Page 1:\n", + " - [83, 139, 224, 40] – region appears cropped.\n", + " - [37, 62, 99, 17] - Correctly displayed.\n", + "- Page 4: first region is not text, but second one works\n", + " - [78, 134, 348, 37] – original coords can be displayed, but don't display the correct region of text\n", + " - [34, 59, 155, 16] - new coordinates can be displayed, display the correct region." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ltf_inv_issue = 'LTF-1905-08-09-a'\n", + "ltf_inv_pages, ltf_inv_page_1_r1, ltf_inv_page_4_r1, scaled_ltf_inv_page_1_r1, scaled_ltf_inv_page_4_r1 = test_scale_coords(ltf_inv_issue, [0, 3], 130, 58)\n", + "ltf_inv_page_1_r1, ltf_inv_page_4_r1, scaled_ltf_inv_page_1_r1, scaled_ltf_inv_page_4_r1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ltf_inv_pages[3]['r'][0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Part 3: Implement the patching code functions to be used in the patching script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "all_info_file_path = issues_to_patch_file.format('all')\n", + "all_to_inv_path = issues_to_inv_file.format('all')\n", + "\n", + "with open(all_info_file_path, mode ='r', encoding='utf-8') as f:\n", + " all_to_patch = json.load(f)\n", + "\n", + "with open(all_to_inv_path, mode ='r', encoding='utf-8') as f:\n", + " all_to_inv = json.load(f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# LTF is used as an example as it's relatively small\n", + "LTF_issues, LTF_pages = fetch_files('canonical-data', False, 'both', ['LTF'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def convert_issue_coords(issue, res):\n", + " scaled = False\n", + " for i in issue['i']:\n", + " if 'c' in i['m']:\n", + " i['m']['c'] = scale_coords(i['m']['c'], res['curr_res'], res['dest_res'])\n", + " scaled = True\n", + " elif 'c' in i:\n", + " i['c'] = scale_coords(i['c'], res['curr_res'], res['dest_res'])\n", + " scaled = True\n", + " elif 'iiif_link' in i['m'] or 'iiif_link' in i:\n", + " iiif = i['m']['iiif_link'] if 'iiif_link' in i['m'] else i['iiif_link']\n", + " logger.warning(\"%s: No coordinates but a IIIF link for item %s: %s\", issue['id'], i['m']['id'], iiif)\n", + " # return the issue as-is once it's been scaled\n", + " return issue, scaled" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def convert_page_coords(page, res):\n", + " scaled = 0\n", + " # count the expected number of coordinates to rescale on page\n", + " coords_count = len(page['r'])\n", + " for region in page['r']:\n", + " region['c'] = scale_coords(region['c'], res['curr_res'], res['dest_res'])\n", + " scaled += 1\n", + " for para in region[\"p\"]:\n", + " coords_count += len(para[\"l\"])\n", + " for line in para[\"l\"]:\n", + " line['c'] = scale_coords(line['c'], res['curr_res'], res['dest_res'])\n", + " scaled += 1\n", + " coords_count += len(line[\"t\"])\n", + " for token in line['t']:\n", + " token['c'] = scale_coords(token['c'], res['curr_res'], res['dest_res'])\n", + " scaled += 1\n", + " return page, scaled==coords_count" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def find_convert_coords(elem, title, to_patch, to_inv, is_issue: bool = True):\n", + "\n", + " if is_issue:\n", + " issue_id = elem['id']\n", + " key = 'issue_patching_done'\n", + " patched = {'issue_id': issue_id, key: False, 'num_pages':len(elem['pp'])}\n", + " else:\n", + " issue_id = '-'.join(elem['id'].split('-')[:-1])\n", + " key = 'page_patching_done'\n", + " patched = {'issue_id': issue_id, key: False, 'page_id':elem['id']}\n", + " \n", + "\n", + " # for LCG, only years later than 1891 need to be fixed\n", + " if title != 'LCG' or int(issue_id.split('-')[1])>1906:\n", + " if issue_id in to_patch:\n", + " res = to_patch[issue_id]\n", + " # keep trace of whether or not we fetched the information from the image info file\n", + " res['used_image_info_file'] = True\n", + " elif issue_id in to_inv:\n", + " res = to_inv[issue_id]\n", + " res['used_image_info_file'] = False\n", + " else:\n", + " return elem, patched\n", + " \n", + " if is_issue:\n", + " elem, scaled = convert_issue_coords(elem, res)\n", + " # there may be no coordinated to scale in an issue\n", + " res['scaled'] = scaled\n", + " else:\n", + " elem, scaled = convert_page_coords(elem, res)\n", + " # sanity check that number of regions+lines+tokens=coords scaled\n", + " res['all_scaled'] = scaled\n", + "\n", + " # keep trace of information about the patching performed.\n", + " patched[key] = True\n", + " patched.update(res)\n", + " \n", + " return elem, patched" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "np = 'LTF'\n", + "patched_ltf_issues = LTF_issues.map_partitions(\n", + " lambda i_list: [find_convert_coords(i, np, all_to_patch[np], all_to_inv[np]) for i in i_list]\n", + " ).persist()\n", + "patched_ltf_pages = LTF_pages.map_partitions(\n", + " lambda p_list: [find_convert_coords(p, np, all_to_patch[np], all_to_inv[np], is_issue=False) for p in p_list]\n", + " ).persist()\n", + "\n", + "# extract only the \"patched\" \n", + "patched_issues_ltf = patched_ltf_issues.map_partitions(lambda i_l: [i[1] for i in i_l])\n", + "patched_pages_ltf = patched_ltf_pages.map_partitions(lambda i_l: [i[1] for i in i_l])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "patched_issues_df = patched_issues_ltf.to_dataframe(meta={'issue_id': str, 'issue_patching_done': bool, \n", + " 'num_pages': \"Int64\", 'dest_res': \"Int64\", \n", + " \"curr_res\": \"Int64\", 'zip_contents': str,\n", + " 'used_image_info_file': bool}).compute()#.set_index('issue_id').compute()\n", + "patched_issues_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "patched_pages_df = (\n", + " patched_pages_ltf.to_dataframe(meta={'issue_id': str, 'page_patching_done': bool, \n", + " 'page_id': str, 'dest_res': \"Int64\", \n", + " \"curr_res\": \"Int64\", 'zip_contents': str,\n", + " 'used_image_info_file': bool, \"all_scaled\": bool})\n", + " #.groupby(by='issue_id')\n", + " .groupby(by=['issue_id', 'page_patching_done', 'dest_res', 'curr_res', 'used_image_info_file', 'all_scaled'])\n", + " .agg({'page_id': 'count'})\n", + " .rename(columns={'page_id': 'num_pages'})\n", + " .reset_index()#.set_index('issue_id')\n", + ").compute()\n", + "patched_pages_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "patched_issues__merged_df = patched_issues_df.merge(patched_pages_df, how='outer')\n", + "patched_issues__merged_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "patched_issues__merged_df.to_csv(os.path.join(info_files_base_path, f'{np}_patched_issues.csv'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "all(patched_pages_df.page_patching_done), all(patched_issues_df.issue_patching_done)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "text-acquisition-update", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 6bda00b1c3aa7084105deec512f1c1e9197ff640 Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 27 Mar 2024 15:36:43 +0100 Subject: [PATCH 41/53] Fix API issue when reading the issue IIIF Ark ids form the API --- text_importer/importers/bnf_en/detect.py | 88 ++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/text_importer/importers/bnf_en/detect.py b/text_importer/importers/bnf_en/detect.py index 46bd5900..31d6ca04 100644 --- a/text_importer/importers/bnf_en/detect.py +++ b/text_importer/importers/bnf_en/detect.py @@ -9,6 +9,7 @@ import requests from bs4 import BeautifulSoup +from bs4.element import Tag from dask import bag as db from impresso_commons.path.path_fs import _apply_datefilter from tqdm import tqdm @@ -76,6 +77,71 @@ def get_api_id(journal: str, api_issue: tuple[str, datetime.date], edition: str) journal, date.year, date.month, date.day, ascii_lowercase[edition] ) +def fix_api_year_mismatch(journal: str, year: int, api_issues: list[Tag], last_i: Tag) -> tuple[list[Tag], Tag | None]: + """Modify proivded list of issues fetched from the API to fix some issues present. + + Indeed, the API currently wronly stores the issues for december 31st of some years, + with some issues being shifted from one year. + This is not the case for all years, and the correct issue can be present or not. + This function aims to rectify this issue and fetch the correct IIIF ark IDs. + + Args: + journal (str): Alias of the journal currently under processing. + year (int): Year for which the API was queried. + api_issues (list[Tag]): List of issues as returned from the API. + last_i (Tag): Last december 31st issue entry, returned for the wrong year. + + Returns: + tuple[list[Tag], Tag | None]: Corrected issue list and next december 31st issue + if the error was present again, None otherwise. + """ + curr_last_i = last_i + + # if there is indeed a mismatch in the year + if str(year-1) in api_issues[-1].getText(): + if '31 décembre' not in api_issues[-1].getText(): + logger.warning( + '%s-%s: Mismatch in year for another day!!: %s', + journal, year, api_issues[-1] + ) + next_last_i = None + else: + # store this api_issue for the following year + next_last_i = api_issues[-1] + # sanity check that the previously stored value corresponds to the correct year + if curr_last_i is None: + msg = ( + f"{journal}-{year}: No previously stored Dec 31s value since " + "it's the last available year, removing the incorrect issue." + ) + logger.info(msg) + # if ark is not available: delete the wrong last issue + api_issues = api_issues[:-1] + elif str(year) in curr_last_i.getText(): + # replace the final issue by the one with the correct year + api_issues[-1] = curr_last_i + msg = f"{journal}-{year}: Setting the value of api_issues[-1] to {curr_last_i}" + logger.debug(msg) + else: + msg = ( + f"{journal}-{year}: The previously stored dec 31st issue does " + f"not correspond to this year {curr_last_i}" + ) + logger.info(msg) + elif str(year) in curr_last_i.getText(): + # if the last stored value corresponds to this year and december 31st is missing, add it + if '31 décembre' in curr_last_i.getText() and '31 décembre' not in api_issues[-1].getText(): + api_issues.append(curr_last_i) + msg = f"{journal}-{year}: Appending {curr_last_i} to api_issues." + logger.info(msg) + next_last_i = None + # if it's not missing but corresponds to another day, log it + else: + msg = f"{journal}-{year}: api_issues[-1] corresponding to another day than the previous one: {api_issues[-1].getText()}" + logger.warning(msg) + + return api_issues, next_last_i + def get_issues_iiif_arks(journal_ark: tuple[str, str]) -> list[tuple[str, str]]: """Given a journal name and Ark, fetch its issues' Ark in the Gallica API. @@ -114,14 +180,27 @@ def get_date(dayofyear: str, year: int) -> datetime.date: years = [int(x.contents[0]) for x in years] links = [] - for year in tqdm(years): + next_year_last_i = None + + # start with the last year + for year in tqdm(years[::-1]): # API requrest url = API_ISSUE_URL.format(ark=API_MAPPING[journal], year=year) r = requests.get(url, timeout=60) api_issues = BeautifulSoup(r.content, "lxml").findAll("issue") + + # fix the problem stemming from the API with dec. 31st being of following year + if str(year-1) in api_issues[-1].getText() or (next_year_last_i is not None and str(year) in next_year_last_i.getText()): + logger.debug("%s-%s: api_issues[-1].getText(): %s", journal, year, api_issues[-1].getText()) + api_issues, next_year_last_i = fix_api_year_mismatch(journal, year, api_issues, next_year_last_i) + else: + # reset the value since it won't be valid anymore + next_year_last_i = None + # Parse dates and editions api_issues = [ - (i.get("ark"), get_date(i.get("dayofyear"), year)) for i in api_issues + (i.get("ark"), get_date(i.get("dayofyear"), year)) + for i in api_issues ] editions = [] @@ -139,8 +218,9 @@ def get_date(dayofyear: str, year: int) -> datetime.date: (get_api_id(journal, i, edition), i[0]) for i, edition in zip(api_issues, editions) ] - links += api_issues - return links + links += api_issues[::-1] + # flip the resulting links since they were fetched from end to start + return links[::-1] def construct_iiif_arks() -> dict[str, str]: From 2c4f5b8fb575786919756e26103b025ee9787900 Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 27 Mar 2024 16:13:40 +0100 Subject: [PATCH 42/53] add option to provide the numebr of workers --- text_importer/importers/generic_importer.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/text_importer/importers/generic_importer.py b/text_importer/importers/generic_importer.py index 8730f2bb..9fcd1d65 100644 --- a/text_importer/importers/generic_importer.py +++ b/text_importer/importers/generic_importer.py @@ -2,7 +2,7 @@ Functions and CLI script to convert any OCR data into Impresso's format. Usage: - importer.py --input-dir= (--clear | --incremental) [--output-dir= --image-dirs= --temp-dir= --chunk-size= --s3-bucket= --config-file= --log-file= --verbose --scheduler= --access-rights= --git-repo=] + importer.py --input-dir= (--clear | --incremental) [--output-dir= --image-dirs= --temp-dir= --chunk-size= --s3-bucket= --config-file= --log-file= --verbose --scheduler= --access-rights= --git-repo= --num-workers=] importer.py --version Options: @@ -17,6 +17,7 @@ --access-rights= Access right file if relevant (only for `olive` and `rero` importers) --chunk-size= Chunk size in years used to group issues when importing --git-repo= Local path to the "impresso-text-acquisition" git directory (including it). + --num-workers= Number of workers to use for local dask cluster --verbose Verbose log messages (good for debugging) --clear Removes the output folder (if already existing) --incremental Skips issues already present in output directory @@ -78,7 +79,7 @@ def clear_output_dir(out_dir: str | None, clear_output: bool | None) -> None: def get_dask_client( - scheduler: str | None, log_file: str | None, log_level: int + scheduler: str | None, log_file: str | None, log_level: int, n_workers: int | None ) -> Client: """Instantiate Dask client with given scheduler address or default values. @@ -91,6 +92,7 @@ def get_dask_client( scheduler (str | None): TCP address of the Dask scheduler to use. log_file (str | None): File to use for logging. log_level (int): Log level used, either INFO or DEBUG in verbose mode. + n_workers (int | None): Number of workers to use in local cluster Returns: Client: A client connected to and allowing to manage the Dask cluster. @@ -98,7 +100,10 @@ def get_dask_client( if scheduler is None: #cluster = LocalCluster(n_workers=32, memory_limit='auto', threads_per_worker=2) #client = Client(cluster) - client = Client(n_workers=24, threads_per_worker=2) + if n_workers is not None: + client = Client(n_workers=n_workers, threads_per_worker=2) + else: + client = Client(n_workers=24, threads_per_worker=2) else: client = Client(scheduler) client.run( @@ -201,6 +206,7 @@ def main( print_version = args["--version"] config_file = args["--config-file"] repo_path = args["--git-repo"] + num_workers = args["--num-workers"] if print_version: print(f'impresso-txt-importer v{__version__}') From 0569dbea8e6022998362bf519132c86d6ec5684f Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 27 Mar 2024 16:15:09 +0100 Subject: [PATCH 43/53] minor fix --- text_importer/importers/generic_importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_importer/importers/generic_importer.py b/text_importer/importers/generic_importer.py index 9fcd1d65..0fcc30ee 100644 --- a/text_importer/importers/generic_importer.py +++ b/text_importer/importers/generic_importer.py @@ -216,7 +216,7 @@ def main( logger.info("CLI arguments received: {}".format(args)) # start the dask local cluster - client = get_dask_client(scheduler, log_file, log_level) + client = get_dask_client(scheduler, log_file, log_level, int(num_workers)) logger.info(f"Dask cluster: {client}") From 60090de652da02ddc5a7f5f420e1f95e183fbaa0 Mon Sep 17 00:00:00 2001 From: piconti Date: Wed, 27 Mar 2024 17:41:48 +0100 Subject: [PATCH 44/53] Small fix to ensure manifest stores correct stats --- text_importer/importers/core.py | 11 ++- .../patching/canonical_patch_7_find_issues.py | 70 ++++++++----------- 2 files changed, 35 insertions(+), 46 deletions(-) diff --git a/text_importer/importers/core.py b/text_importer/importers/core.py index 0b1ca821..de902e73 100644 --- a/text_importer/importers/core.py +++ b/text_importer/importers/core.py @@ -498,12 +498,11 @@ def compress_issues( except Exception as e: logger.error("Error for %s: %s", filepath, e) write_error(filepath, e, failed_log) - - # Once the issues were written without issues, add their info to the manifest - yearly_stats = [] - for i in items: - yearly_stats.append(counts_for_canonical_issue(i)) - # manifest.add_by_title_year(newspaper, year, counts_for_canonical_issue(i)) + else: + # Once the issues were written without problems, add their info to the manifest + yearly_stats = [] + for i in items: + yearly_stats.append(counts_for_canonical_issue(i)) return f"{newspaper}-{year}", filepath, yearly_stats diff --git a/text_importer/scripts/patching/canonical_patch_7_find_issues.py b/text_importer/scripts/patching/canonical_patch_7_find_issues.py index da358aeb..460d4860 100644 --- a/text_importer/scripts/patching/canonical_patch_7_find_issues.py +++ b/text_importer/scripts/patching/canonical_patch_7_find_issues.py @@ -14,20 +14,13 @@ import os import json import logging -import jsonlines -from impresso_commons.utils import s3 -from impresso_commons.path.path_s3 import fetch_files -from impresso_commons.versioning.data_manifest import DataManifest -from text_importer.importers.core import remove_filelocks -import dask.bag as db -from typing import Any, Callable -import git -from text_importer.utils import init_logger -import copy -from docopt import docopt -from collections import defaultdict import shutil +from typing import Any, Callable from zipfile import ZipFile, BadZipFile +from docopt import docopt + +from impresso_commons.utils import s3 +from text_importer.utils import init_logger IMPRESSO_STORAGEOPT = s3.get_storage_options() UZH_TITLES = ["FedGazDe", "FedGazFr", "NZZ"] @@ -37,22 +30,6 @@ logger = logging.getLogger() -def add_property( - object_dict: dict[str, Any], - prop_name: str, - prop_function: Callable[[str], str], - function_input: str, -): - object_dict[prop_name] = prop_function(function_input) - logger.debug( - "%s -> Added property %s: %s", - object_dict["id"], - prop_name, - object_dict[prop_name], - ) - return object_dict - - def empty_folder(dir_path: str) -> None: """Empty a directoy given its path if it exists. @@ -81,12 +58,12 @@ def write_error( """ note = f"Error in {origin_function} for {thing_id}: {error}" logger.exception(note) - with open(failed_log, "a+") as f: + with open(failed_log, "a+", encoding="utf-8") as f: f.write(note + "\n") -def extract_zip_contents(zip_path): - +def extract_zip_contents(zip_path: str) -> tuple[list[str], list[str]]: + zip_contents = ZipFile(zip_path).namelist() pg_res_files = [f for f in zip_contents if "Img" in f and "Pg" in f] @@ -101,17 +78,23 @@ def extract_zip_contents(zip_path): # pg_res[pg] = [res] return pg_res_files, pg_res + def load_json(f_path: str) -> dict: with open(f_path, mode="r", encoding="utf-8") as f_in: file = json.load(f_in) return file + def fetch_needed_info_for_title(title, og_data_path, img_data_path, out_path): # resume a listing in the middle if os.path.exists(out_path): title_info = load_json(out_path) - logger.info("Continuing to fetch for %s, restarting from %a issues", title, len(title_info)) + logger.info( + "Continuing to fetch for %s, restarting from %a issues", + title, + len(title_info), + ) else: title_info = {} @@ -123,10 +106,10 @@ def fetch_needed_info_for_title(title, og_data_path, img_data_path, out_path): for dir_path, sub_dirs, files in os.walk(os.path.join(img_data_path, title)): # only consider the cases where we are in an issue directory - if len(sub_dirs) == 0 : + if len(sub_dirs) == 0: issue_sub_path = dir_path.replace(img_data_path, "") issue_id = issue_sub_path.replace("/", "-") - if title != 'LCE' or issue_id not in title_info: + if title != "LCE" or issue_id not in title_info: # add the image info img_info_file = [f for f in files if f.endswith("image-info.json")] @@ -150,7 +133,9 @@ def fetch_needed_info_for_title(title, og_data_path, img_data_path, out_path): } elif len(img_info_file) == 0: - print(f"Warining: Missing image-info file for {issue_id}: {dir_path}") + print( + f"Warining: Missing image-info file for {issue_id}: {dir_path}" + ) title_info[issue_id] = { "img": {"file_present": False, "img_info_file": dir_path} } @@ -168,11 +153,15 @@ def fetch_needed_info_for_title(title, og_data_path, img_data_path, out_path): doc_zip_path = os.path.join(og_data_dir_path, "Document.zip") if os.path.exists(doc_zip_path): try: - title_info[issue_id]["original"] = {"zip_doc_path": doc_zip_path} + title_info[issue_id]["original"] = { + "zip_doc_path": doc_zip_path + } pg_res_files, pg_res = extract_zip_contents( title_info[issue_id]["original"]["zip_doc_path"] ) - title_info[issue_id]["original"]["zip_img_contents"] = pg_res_files + title_info[issue_id]["original"][ + "zip_img_contents" + ] = pg_res_files if len(pg_res) != 0: title_info[issue_id]["original"]["resolutions"] = pg_res except BadZipFile as e: @@ -186,7 +175,9 @@ def fetch_needed_info_for_title(title, og_data_path, img_data_path, out_path): if len(title_info) % 50 == 0: logger.info( - "Currently on issue %s, done %s issues.", issue_id, len(title_info) + "Currently on issue %s, done %s issues.", + issue_id, + len(title_info), ) if len(title_info) % 500 == 0: logger.info("Done 500 issues, saving file temporarily.") @@ -228,7 +219,6 @@ def main(): # set some titles at the front of the line as priority rero_titles = ["LCG", "DLE", "LNF", "LBP", "LSE", "EXP"] rero_titles.extend(rero_journal_dirs) - #rero_titles = rero_journal_dirs logger.info("Will process titles: %s", rero_titles) @@ -237,7 +227,7 @@ def main(): img_info_paths_file = f"{local_base_path}/{journal}_img_res_info.json" - if not os.path.exists(img_info_paths_file) or journal == 'LCE': + if not os.path.exists(img_info_paths_file) or journal == "LCE": title_info, missing_info_files, mltp_if = fetch_needed_info_for_title( journal, og_data_base_path, images_base_path, img_info_paths_file ) From 8214cf64b6c19c2246d00a6a66b4283c325bceed Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 2 Apr 2024 13:45:13 +0200 Subject: [PATCH 45/53] Fix repeated dates issue --- text_importer/importers/bnf_en/detect.py | 37 +++++++++++++++--------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/text_importer/importers/bnf_en/detect.py b/text_importer/importers/bnf_en/detect.py index 31d6ca04..18610ed5 100644 --- a/text_importer/importers/bnf_en/detect.py +++ b/text_importer/importers/bnf_en/detect.py @@ -77,7 +77,7 @@ def get_api_id(journal: str, api_issue: tuple[str, datetime.date], edition: str) journal, date.year, date.month, date.day, ascii_lowercase[edition] ) -def fix_api_year_mismatch(journal: str, year: int, api_issues: list[Tag], last_i: Tag) -> tuple[list[Tag], Tag | None]: +def fix_api_year_mismatch(journal: str, year: int, api_issues: list[Tag], last_i: list[Tag]|None) -> tuple[list[Tag], Tag | None]: """Modify proivded list of issues fetched from the API to fix some issues present. Indeed, the API currently wronly stores the issues for december 31st of some years, @@ -106,21 +106,32 @@ def fix_api_year_mismatch(journal: str, year: int, api_issues: list[Tag], last_i ) next_last_i = None else: - # store this api_issue for the following year - next_last_i = api_issues[-1] + #it can happen that there are 2 issues on Dec 31st: + if str(year-1) in api_issues[-2].getText(): + # save the last 2 issues + msg = f"{journal}-{year}: Saving 2 editions for Dec 31st {curr_last_i}" + logger.info(msg) + num_to_replace = 2 + next_last_i = api_issues[-num_to_replace:] + else: + # store this api_issue for the following year + num_to_replace = 1 + next_last_i = [api_issues[-num_to_replace]] # sanity check that the previously stored value corresponds to the correct year if curr_last_i is None: msg = ( f"{journal}-{year}: No previously stored Dec 31s value since " - "it's the last available year, removing the incorrect issue." + f"it's the last available year, removing the {num_to_replace} incorrect issue." ) logger.info(msg) # if ark is not available: delete the wrong last issue - api_issues = api_issues[:-1] - elif str(year) in curr_last_i.getText(): + api_issues = api_issues[:num_to_replace] + elif all(str(year) in i.getText() for i in curr_last_i): + # remove the number of issues of the wrong year + api_issues = api_issues[:-num_to_replace] # replace the final issue by the one with the correct year - api_issues[-1] = curr_last_i - msg = f"{journal}-{year}: Setting the value of api_issues[-1] to {curr_last_i}" + api_issues.extend(curr_last_i) + msg = f"{journal}-{year}: Setting the value of api_issues[:-{num_to_replace}] to {curr_last_i}" logger.debug(msg) else: msg = ( @@ -128,10 +139,10 @@ def fix_api_year_mismatch(journal: str, year: int, api_issues: list[Tag], last_i f"not correspond to this year {curr_last_i}" ) logger.info(msg) - elif str(year) in curr_last_i.getText(): + elif all(str(year) in i.getText() for i in curr_last_i): # if the last stored value corresponds to this year and december 31st is missing, add it - if '31 décembre' in curr_last_i.getText() and '31 décembre' not in api_issues[-1].getText(): - api_issues.append(curr_last_i) + if all('31 décembre' in i.getText() for i in curr_last_i) and '31 décembre' not in api_issues[-1].getText(): + api_issues.extend(curr_last_i) msg = f"{journal}-{year}: Appending {curr_last_i} to api_issues." logger.info(msg) next_last_i = None @@ -190,8 +201,8 @@ def get_date(dayofyear: str, year: int) -> datetime.date: api_issues = BeautifulSoup(r.content, "lxml").findAll("issue") # fix the problem stemming from the API with dec. 31st being of following year - if str(year-1) in api_issues[-1].getText() or (next_year_last_i is not None and str(year) in next_year_last_i.getText()): - logger.debug("%s-%s: api_issues[-1].getText(): %s", journal, year, api_issues[-1].getText()) + if str(year-1) in api_issues[-1].getText() or (next_year_last_i is not None and all(str(year) in i.getText() for i in next_year_last_i)): + logger.debug("%s-%s: api_issues[-1].getText(): %s, next_year_last_i: %s", journal, year, api_issues[-1].getText(), next_year_last_i) api_issues, next_year_last_i = fix_api_year_mismatch(journal, year, api_issues, next_year_last_i) else: # reset the value since it won't be valid anymore From 95eceef9e2369ccc0100b0edb45fb7453f80cdf7 Mon Sep 17 00:00:00 2001 From: piconti Date: Mon, 29 Apr 2024 15:33:42 +0200 Subject: [PATCH 46/53] Continue adapting to pylint --- text_importer/importers/bnf_en/classes.py | 275 +++++++++++----------- text_importer/utils.py | 44 ++-- 2 files changed, 165 insertions(+), 154 deletions(-) diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index a79bd9be..bdd6337b 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -14,13 +14,17 @@ from bs4.element import NavigableString, Tag from impresso_commons.path import IssueDir -from text_importer.importers import (CONTENTITEM_TYPES, - CONTENTITEM_TYPE_ADVERTISEMENT, - CONTENTITEM_TYPE_IMAGE, - CONTENTITEM_TYPE_TABLE) +from text_importer.importers import ( + CONTENTITEM_TYPES, + CONTENTITEM_TYPE_ADVERTISEMENT, + CONTENTITEM_TYPE_IMAGE, + CONTENTITEM_TYPE_TABLE, +) from text_importer.importers.bnf.helpers import BNF_CONTENT_TYPES -from text_importer.importers.mets_alto import (MetsAltoNewspaperIssue, - MetsAltoNewspaperPage) +from text_importer.importers.mets_alto import ( + MetsAltoNewspaperIssue, + MetsAltoNewspaperPage, +) from text_importer.utils import get_issue_schema, get_page_schema, get_reading_order IssueSchema = get_issue_schema() @@ -31,13 +35,12 @@ IIIF_ENDPOINT_URI = "https://gallica.bnf.fr/iiif/ark:/12148/" IIIF_SUFFIX = "info.json" IIIF_MANIFEST_SUFFIX = "manifest.json" -IIIF_IMAGE_SUFFIX = "full/full/0/default.jpg" # TODO remove SECTION_TYPE = "section" type_translation = { - 'illustration': CONTENTITEM_TYPE_IMAGE, - 'advertisement': CONTENTITEM_TYPE_ADVERTISEMENT, - } + "illustration": CONTENTITEM_TYPE_IMAGE, + "advertisement": CONTENTITEM_TYPE_ADVERTISEMENT, +} class BnfEnNewspaperPage(MetsAltoNewspaperPage): @@ -48,7 +51,7 @@ class BnfEnNewspaperPage(MetsAltoNewspaperPage): number (int): Page number. filename (str): Name of the Alto XML page file. basedir (str): Base directory where Alto files are located. - + Attributes: id (str): Canonical Page ID (e.g. ``GDL-1900-01-02-a-p0004``). number (int): Page number. @@ -60,18 +63,17 @@ class BnfEnNewspaperPage(MetsAltoNewspaperPage): is_gzip (bool): Whether the page's corresponding file is in .gzip. ark_link (str): IIIF Ark identifier for this page. """ - - def __init__(self, _id: str, number: int, - filename: str, basedir: str) -> None: + + def __init__(self, _id: str, number: int, filename: str, basedir: str) -> None: super().__init__(_id, number, filename, basedir) - - page_tag = self.xml.find('Page') - self.page_width = float(page_tag.get('WIDTH')) - + + page_tag = self.xml.find("Page") + self.page_width = float(page_tag.get("WIDTH")) + def add_issue(self, issue: MetsAltoNewspaperIssue) -> None: self.issue = issue ark = issue.ark_link - self.page_data['iiif_img_base_uri'] = os.path.join( + self.page_data["iiif_img_base_uri"] = os.path.join( IIIF_ENDPOINT_URI, ark, "f{}".format(self.number) ) @@ -79,7 +81,7 @@ def add_issue(self, issue: MetsAltoNewspaperIssue) -> None: class BnfEnNewspaperIssue(MetsAltoNewspaperIssue): """Newspaper Issue in BNF-EN (Mets/Alto) format. - All functions defined in this child class are specific to parsing + All functions defined in this child class are specific to parsing BNF-Europeana Mets/Alto format. Args: @@ -102,57 +104,51 @@ class BnfEnNewspaperIssue(MetsAltoNewspaperIssue): def __init__(self, issue_dir: IssueDir) -> None: self.ark_link = issue_dir.ark_link super().__init__(issue_dir) - + def _find_pages(self) -> None: """Detect and create the issue pages using the relevant Alto XML files. - + Created NewspaperPage instances are added to pages. Raises: e: Creating a BnfEnlNewspaperPage` raised an exception. """ - alto_path = os.path.join(self.path, 'ALTO') - + alto_path = os.path.join(self.path, "ALTO") + if not os.path.exists(alto_path): msg = f"Could not find pages for {self.id}, non-existing path: {alto_path}" logger.critical(msg) - raise Exception(msg) - + raise ValueError(msg) + page_file_names = [ file for file in os.listdir(alto_path) - if not file.startswith('.') and '.xml' in file + if not file.startswith(".") and ".xml" in file ] - + page_numbers = [] - + for fname in page_file_names: - page_no = fname.split('.')[0].split('-')[1] + page_no = fname.split(".")[0].split("-")[1] page_numbers.append(int(page_no)) - + page_canonical_names = [ - "{}-p{}".format(self.id, str(page_n).zfill(4)) - for page_n in page_numbers + "{}-p{}".format(self.id, str(page_n).zfill(4)) for page_n in page_numbers ] - + self.pages = [] for filename, page_no, page_id in zip( - page_file_names, page_numbers, page_canonical_names - ): + page_file_names, page_numbers, page_canonical_names + ): try: self.pages.append( - BnfEnNewspaperPage( - page_id, - page_no, - filename, - alto_path - ) - ) + BnfEnNewspaperPage(page_id, page_no, filename, alto_path) + ) except Exception as e: - msg = f'Adding page {page_no} {page_id} {filename} raised following exception: {e}' + msg = f"Adding page {page_no} {page_id} {filename} raised following exception: {e}" logger.error(msg) raise Exception(msg) from e - + def _parse_content_parts(self, content_div: Tag) -> list[dict[str, Any]]: """Parse given div's children tags to create legacy `parts` component. @@ -167,28 +163,28 @@ def _parse_content_parts(self, content_div: Tag) -> list[dict[str, Any]]: """ parts = [] for child in content_div.children: - + if isinstance(child, NavigableString): continue elif isinstance(child, Tag): - type_attr = child.get('TYPE') + type_attr = child.get("TYPE") comp_role = type_attr.lower() if type_attr else None - areas = child.findAll('area') + areas = child.findAll("area") for area in areas: - comp_id = area.get('BEGIN') - comp_fileid = area.get('FILEID') - comp_page_no = int(comp_fileid.replace('ALTO', '')) - + comp_id = area.get("BEGIN") + comp_fileid = area.get("FILEID") + comp_page_no = int(comp_fileid.replace("ALTO", "")) + parts.append( { - 'comp_role': comp_role, - 'comp_id': comp_id, - 'comp_fileid': comp_fileid, - 'comp_page_no': comp_page_no + "comp_role": comp_role, + "comp_id": comp_id, + "comp_fileid": comp_fileid, + "comp_page_no": comp_page_no, } ) return parts - + def _get_ci_language(self, dmdid: str, mets_doc: BeautifulSoup) -> Optional[str]: """Find the language code of the CI with given DMDID. @@ -196,7 +192,7 @@ def _get_ci_language(self, dmdid: str, mets_doc: BeautifulSoup) -> Optional[str] Args: dmdid (str): Identifier of the content item in the dmd section. - mets_doc (BeautifulSoup): Contents of the Mets XML file. + mets_doc (BeautifulSoup): Contents of the Mets XML file. Returns: Optional[str]: Language or None if not present in Mets file. @@ -204,11 +200,11 @@ def _get_ci_language(self, dmdid: str, mets_doc: BeautifulSoup) -> Optional[str] lang = mets_doc.find("dmdSec", {"ID": dmdid}) if lang is None: return None - lang = lang.find("mods:languageTerm") + lang = lang.find("mods:languageTerm") if lang is None: return None return lang.text - + def _parse_content_item( self, item_div: Tag, counter: int, mets_doc: BeautifulSoup ) -> dict[str, Any]: @@ -216,54 +212,56 @@ def _parse_content_item( Args: item_div (Tag): Div of content item. - counter (int): Number of content items already added (needed to + counter (int): Number of content items already added (needed to generate canonical id). - mets_doc (BeautifulSoup): Contents of the Mets XML file. + mets_doc (BeautifulSoup): Contents of the Mets XML file. Returns: dict[str, Any]: Resulting content item in canonical format. """ - div_type = item_div.get('TYPE').lower() + div_type = item_div.get("TYPE").lower() # Translate types if div_type in type_translation: div_type = type_translation[div_type] - + # Check if new content item is found (or if we need more translation) if div_type not in CONTENTITEM_TYPES: - logger.warning(f"Found new content item type: {div_type}") - + logger.warning("Found new content item type: %s", div_type) + metadata = { - 'id': "{}-i{}".format(self.id, str(counter).zfill(4)), - 'tp': div_type, - 'pp': [], - 't': item_div.get('LABEL') + "id": "{}-i{}".format(self.id, str(counter).zfill(4)), + "tp": div_type, + "pp": [], + "t": item_div.get("LABEL"), } - + # Get CI language - language = self._get_ci_language(item_div.get('DMDID'), mets_doc) + language = self._get_ci_language(item_div.get("DMDID"), mets_doc) if language is not None: - metadata['l'] = language - + metadata["l"] = language + content_item = { "m": metadata, "l": { - "id": item_div.get('ID'), - "parts": self._parse_content_parts(item_div) - } + "id": item_div.get("ID"), + "parts": self._parse_content_parts(item_div), + }, } - for p in content_item['l']['parts']: + for p in content_item["l"]["parts"]: pge_no = p["comp_page_no"] - if pge_no not in content_item['m']['pp']: - content_item['m']['pp'].append(pge_no) - + if pge_no not in content_item["m"]["pp"]: + content_item["m"]["pp"].append(pge_no) + if div_type in [CONTENTITEM_TYPE_IMAGE, CONTENTITEM_TYPE_TABLE]: - content_item['c'], content_item['m']['iiif_link'] = self._get_image_info(content_item) + content_item["c"], content_item["m"]["iiif_link"] = self._get_image_info( + content_item + ) return content_item - + def _decompose_section(self, div: Tag) -> list[Tag]: """Recursively decompose the given `section` div into individual items. - In Mets, sometimes textblocks and images are withing `Section` tags. + In Mets, sometimes textblocks and images are withing `Section` tags. Those need to be recursively decomposed to reach the content item divs. Args: @@ -273,23 +271,24 @@ def _decompose_section(self, div: Tag) -> list[Tag]: list[Tag]: List of all children divs not of type `Section`. """ logger.info("Decomposing section type") - section_divs = [d for d in div.findAll("div") if - d.get('TYPE').lower() in BNF_CONTENT_TYPES] + section_divs = [ + d for d in div.findAll("div") if d.get("TYPE").lower() in BNF_CONTENT_TYPES + ] # Sort to get same IDS - section_divs = sorted(section_divs, key=lambda x: x.get('ID').lower()) - + section_divs = sorted(section_divs, key=lambda x: x.get("ID").lower()) + final_divs = [] # Now do it recursively for d in section_divs: - d_type = d.get('TYPE') + d_type = d.get("TYPE") if d_type is not None: if d_type.lower() == SECTION_TYPE: # Recursively decompose - final_divs += self._decompose_section(d) + final_divs += self._decompose_section(d) else: final_divs.append(d) return final_divs - + def _parse_content_items(self) -> list[dict[str, Any]]: """Extract content item elements from the issue's Mets XML file. @@ -298,24 +297,27 @@ def _parse_content_items(self) -> list[dict[str, Any]]: """ content_items = [] doc = self.xml - + dmd_sections = doc.findAll("dmdSec") struct_map = doc.find("div", {"TYPE": "CONTENT"}) - # Sort to have same namings - # TODO fix this ordering! - sorted_divs = sorted(dmd_sections, key=lambda x: x.get('ID').lower()) - + + # Sort to have same namings - reading order is added separately later + sorted_divs = sorted(dmd_sections, key=lambda x: x.get("ID").lower()) + counter = 1 for d in sorted_divs: - div = struct_map.findAll("div", {"DMDID": d.get('ID')}) + div = struct_map.findAll("div", {"DMDID": d.get("ID")}) if len(div) == 0: continue - elif len(div) > 1: - logger.warning(f"Multiple divs matching {d.get('ID')} " - f"in structmap for {self.id}") + if len(div) > 1: + logger.warning( + "Multiple divs matching %s in structmap for %s", + d.get("ID"), + self.id, + ) else: div = div[0] - div_type = div.get('TYPE').lower() + div_type = div.get("TYPE").lower() if div_type == SECTION_TYPE: section_divs = self._decompose_section(div) for sd in section_divs: @@ -328,13 +330,11 @@ def _parse_content_items(self) -> list[dict[str, Any]]: # add the reading order to the items metadata reading_order_dict = get_reading_order(content_items) for item in content_items: - item['m']['ro'] = reading_order_dict[item['m']['id']] + item["m"]["ro"] = reading_order_dict[item["m"]["id"]] return content_items - - def _get_image_info( - self, content_item: dict[str, Any] - ) -> tuple[list[int], str]: + + def _get_image_info(self, content_item: dict[str, Any]) -> tuple[list[int], str]: """Given an image content item, get its coordinates and iiif url. TODO: Find an approach to reduce the number of calls to page.xml @@ -346,62 +346,69 @@ def _get_image_info( tuple[list[int], str]: Content item coordinates and iiif url. """ # Fetch the legacy parts - + # Images cannot be on multiple pages - num_pages = len(content_item['m']['pp']) + num_pages = len(content_item["m"]["pp"]) assert num_pages == 1, "Image is on more than one page" - - page_nb = content_item['m']['pp'][0] + + page_nb = content_item["m"]["pp"][0] page = [p for p in self.pages if p.number == page_nb][0] - parts = content_item['l']['parts'] - + parts = content_item["l"]["parts"] + assert len(parts) >= 1, f"No parts for image {content_item['m']['id']}" - + if len(parts) > 1: - logger.info(f"Found multiple parts for image " - f"{content_item['m']['id']}, selecting largest one") - + logger.info( + "Found multiple parts for image %s, selecting largest one", + content_item["m"]["id"], + ) + page_doc = page.xml coords = None max_area = 0 # Image can have multiple parts, choose largest one (with max area) for part in parts: - comp_id = part['comp_id'] - + comp_id = part["comp_id"] + elements = page_doc.findAll(["ComposedBlock", "TextBlock"], {"ID": comp_id}) assert len(elements) <= 1, "Image comp_id matches multiple TextBlock tags" if len(elements) == 0: continue - + element = elements[0] - hpos, vpos = element.get('HPOS'), element.get('VPOS') - width, height = element.get('WIDTH'), element.get('HEIGHT') - + hpos, vpos = element.get("HPOS"), element.get("VPOS") + width, height = element.get("WIDTH"), element.get("HEIGHT") + # Select largest image area = int(float(width)) * int(float(height)) if area > max_area: max_area = area - coords = [int(float(hpos)), int(float(vpos)), - int(float(width)), int(float(height))] - + coords = [ + int(float(hpos)), + int(float(vpos)), + int(float(width)), + int(float(height)), + ] + # coords = convert_coordinates(coords, self.image_properties[page.number], page.page_width) - iiif_link = os.path.join(IIIF_ENDPOINT_URI, self.ark_link, - "f{}".format(page.number), IIIF_SUFFIX) - + iiif_link = os.path.join( + IIIF_ENDPOINT_URI, self.ark_link, f"f{page.number}", IIIF_SUFFIX + ) + return coords, iiif_link - + def _parse_mets(self) -> None: content_items = self._parse_content_items() - iiif_manifest = os.path.join(IIIF_ENDPOINT_URI, - self.ark_link, - IIIF_MANIFEST_SUFFIX) - + iiif_manifest = os.path.join( + IIIF_ENDPOINT_URI, self.ark_link, IIIF_MANIFEST_SUFFIX + ) + self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), "id": self.id, "i": content_items, "ar": self.rights, "pp": [p.id for p in self.pages], - "iiif_manifest_uri": iiif_manifest + "iiif_manifest_uri": iiif_manifest, } diff --git a/text_importer/utils.py b/text_importer/utils.py index 87eb9821..a3d688e1 100644 --- a/text_importer/utils.py +++ b/text_importer/utils.py @@ -22,7 +22,7 @@ def init_logger( _logger (logging.RootLogger): Logger instance to initialise. log_level (int): Desidered logging level (e.g. ``logging.INFO``). log_file (str): Path to destination file for logging output. If no - output file is provided (``log_file`` is ``None``) logs will + output file is provided (``log_file`` is ``None``) logs will be written to standard output. Returns: @@ -36,23 +36,22 @@ def init_logger( else: handler = logging.StreamHandler() - formatter = logging.Formatter( - "%(asctime)s %(name)-12s %(levelname)-8s %(message)s" - ) + formatter = logging.Formatter("%(asctime)s %(name)-12s %(levelname)-8s %(message)s") handler.setFormatter(formatter) _logger.addHandler(handler) _logger.info("Logger successfully initialised") return _logger + def get_pkg_resource( file_manager: ExitStack, path: str, package: str = "text_importer" ) -> pathlib.PosixPath: """Return the resource at `path` in `package`, using a context manager. - Note: - The context manager `file_manager` needs to be instantiated prior to - calling this function and should be closed once the package resource + Note: + The context manager `file_manager` needs to be instantiated prior to + calling this function and should be closed once the package resource is no longer of use. Args: @@ -63,12 +62,12 @@ def get_pkg_resource( Returns: pathlib.PosixPath: Path to desired managed resource. """ - ref = importlib_resources.files(package)/path + ref = importlib_resources.files(package) / path return file_manager.enter_context(importlib_resources.as_file(ref)) def get_page_schema( - schema_folder: str = "impresso-schemas/json/newspaper/page.schema.json" + schema_folder: str = "impresso-schemas/json/newspaper/page.schema.json", ) -> pjs.util.Namespace: """Generate a list of python classes starting from a JSON schema. @@ -90,7 +89,7 @@ def get_page_schema( def get_issue_schema( - schema_folder: str = "impresso-schemas/json/newspaper/issue.schema.json" + schema_folder: str = "impresso-schemas/json/newspaper/issue.schema.json", ) -> pjs.util.Namespace: """Generate a list of python classes starting from a JSON schema. @@ -138,7 +137,7 @@ def verify_imported_issues( ) -> None: """Verify that the imported issues fit expectations. - Two verifications are done: the number of content items, and their IDs. + Two verifications are done: the number of content items, and their IDs. Args: actual_issue_json (dict[str, Any]): Created issue json, @@ -147,9 +146,11 @@ def verify_imported_issues( # FIRST CHECK: number of content items actual_ids = set([i["m"]["id"] for i in actual_issue_json["i"]]) expected_ids = set([i["m"]["id"] for i in expected_issue_json["i"]]) - logger.info(f"[{actual_issue_json['id']}] " - f"Expected IDs: {len(expected_ids)}" - f"; actual IDs: {len(actual_ids)}") + logger.info( + f"[{actual_issue_json['id']}] " + f"Expected IDs: {len(expected_ids)}" + f"; actual IDs: {len(actual_ids)}" + ) assert expected_ids.difference(actual_ids) == set() # SECOND CHECK: identity of content items @@ -171,8 +172,10 @@ def verify_imported_issues( assert actual_content_item["l"] == expected_content_item["l"] - logger.info(f"Content item {actual_content_item['m']['id']}" - "dit not change (legacy metadata are identical)") + logger.info( + f"Content item {actual_content_item['m']['id']}" + "dit not change (legacy metadata are identical)" + ) def get_reading_order(items: list[dict[str, Any]]) -> dict[str, int]: @@ -188,9 +191,10 @@ def get_reading_order(items: list[dict[str, Any]]) -> dict[str, int]: dict[str, int]: A dictionary mapping item IDs to their reading order. """ items_copy = copy.deepcopy(items) - ids_and_pages = [(i['m']['id'], i['m']['pp']) for i in items_copy] + ids_and_pages = [(i["m"]["id"], i["m"]["pp"]) for i in items_copy] sorted_ids = sorted( - sorted(ids_and_pages, key=lambda x: int(x[0].split('-i')[-1])), - key=lambda x: x[1] + sorted(ids_and_pages, key=lambda x: int(x[0].split("-i")[-1])), + key=lambda x: x[1], ) - return {t[0]: index+1 for index, t in enumerate(sorted_ids)} \ No newline at end of file + + return {t[0]: index + 1 for index, t in enumerate(sorted_ids)} From 14c6709d2f287cede56854c59339600ada7eb99a Mon Sep 17 00:00:00 2001 From: piconti Date: Mon, 29 Apr 2024 18:37:22 +0200 Subject: [PATCH 47/53] Prepare code for pull request --- text_importer/importers/bnf/detect.py | 142 +++++----- text_importer/importers/bnf/helpers.py | 70 +++-- text_importer/importers/bnf/parsers.py | 109 ++++--- text_importer/importers/bnf_en/classes.py | 2 + text_importer/importers/bnf_en/detect.py | 54 ++-- text_importer/importers/mets_alto/mets.py | 2 +- text_importer/importers/olive/detect.py | 36 +-- text_importer/importers/olive/helpers.py | 265 +++++++----------- text_importer/importers/olive/parsers.py | 121 ++++---- text_importer/importers/rero/classes.py | 11 +- text_importer/importers/rero/detect.py | 81 +++--- text_importer/importers/swa/detect.py | 151 +++++----- .../patching/canonical_patch_5_rero.py | 119 +++++--- text_importer/utils.py | 34 ++- 14 files changed, 592 insertions(+), 605 deletions(-) diff --git a/text_importer/importers/bnf/detect.py b/text_importer/importers/bnf/detect.py index 8119f676..ab5915b9 100644 --- a/text_importer/importers/bnf/detect.py +++ b/text_importer/importers/bnf/detect.py @@ -1,5 +1,6 @@ """This module contains helper functions to find BNF OCR data to import. """ + import json import logging import os @@ -18,15 +19,8 @@ logger = logging.getLogger(__name__) BnfIssueDir = namedtuple( - "IssueDirectory", [ - 'journal', - 'date', - 'edition', - 'path', - 'rights', - 'secondary_date' - ] - ) + "IssueDirectory", ["journal", "date", "edition", "path", "rights", "secondary_date"] +) """A light-weight data structure to represent a newspaper issue. This named tuple contains basic metadata about a newspaper issue. They @@ -65,6 +59,7 @@ DATE_FORMATS = ["%Y-%m-%d", "%Y/%m/%d"] DATE_SEPARATORS = ["/", "-"] + def get_id(issue: BnfIssueDir) -> str: """Return an issue's canonical ID given its IssueDir. @@ -74,9 +69,9 @@ def get_id(issue: BnfIssueDir) -> str: Returns: str: Canonical ID of issue. """ - return "{}-{}-{:02}-{:02}-{}".format(issue.journal, issue.date.year, - issue.date.month, issue.date.day, - issue.edition) + return "{}-{}-{:02}-{:02}-{}".format( + issue.journal, issue.date.year, issue.date.month, issue.date.day, issue.edition + ) def get_number(issue: BnfIssueDir) -> str: @@ -88,7 +83,7 @@ def get_number(issue: BnfIssueDir) -> str: Returns: str: Identifying number in BNF's original file structure. """ - return issue.path.split('/')[-1] + return issue.path.split("/")[-1] def assign_editions(issues: list[BnfIssueDir]) -> list[BnfIssueDir]: @@ -107,12 +102,14 @@ def assign_editions(issues: list[BnfIssueDir]) -> list[BnfIssueDir]: issues = sorted(issues, key=lambda x: x[1]) new_issues = [] for index, (i, n) in enumerate(issues): - i = BnfIssueDir(journal=i.journal, - date=i.date, - edition=string.ascii_lowercase[index], - path=i.path, - rights=i.rights, - secondary_date=i.secondary_date) + i = BnfIssueDir( + journal=i.journal, + date=i.date, + edition=string.ascii_lowercase[index], + path=i.path, + rights=i.rights, + secondary_date=i.secondary_date, + ) new_issues.append((i, n)) return new_issues @@ -131,37 +128,38 @@ def dir2issue(issue_path: str, access_rights_dict: dict) -> BnfIssueDir: BnfIssueDir: New `BnfIssueDir` object """ manifest_file = os.path.join(issue_path, "manifest.xml") - + issue = None if os.path.isfile(manifest_file): - with open(manifest_file) as f: + with open(manifest_file, encoding="utf-8") as f: manifest = BeautifulSoup(f, "xml") - + try: # Issue info is in dmdSec of id "DMD.2" - issue_info = get_dmd_sec(manifest, "DMD.2") + issue_info = get_dmd_sec(manifest, "DMD.2") journal = get_journal_name(issue_path) np_date, secondary_date = parse_date( - issue_info.find("date").contents[0], - DATE_FORMATS, - DATE_SEPARATORS + issue_info.find("date").contents[0], DATE_FORMATS, DATE_SEPARATORS ) edition = "a" rights = get_access_right(journal, np_date, access_rights_dict) - issue = BnfIssueDir(journal=journal, date=np_date, - edition=edition, path=issue_path, - rights=rights, secondary_date=secondary_date) + issue = BnfIssueDir( + journal=journal, + date=np_date, + edition=edition, + path=issue_path, + rights=rights, + secondary_date=secondary_date, + ) except ValueError as e: logger.info(e) - logger.error(f"Could not parse issue at {issue_path}") + logger.error("Could not parse issue at %s", issue_path) else: - logger.error(f"Could not find manifest in {issue_path}") + logger.error("Could not find manifest in %s", issue_path) return issue -def detect_issues( - base_dir: str, access_rights: str = None -) -> list[BnfIssueDir]: +def detect_issues(base_dir: str, access_rights: str = None) -> list[BnfIssueDir]: """Detect BNF issues to import within the filesystem This function the directory structure used by BNF (one subdir by journal). @@ -174,35 +172,33 @@ def detect_issues( Returns: list[BnfIssueDir]: List of `BnfIssueDir` instances, to be imported. """ - dir_path, dirs, files = next(os.walk(base_dir)) + dir_path, dirs, _ = next(os.walk(base_dir)) journal_dirs = [ - os.path.join(dir_path, _dir) - for _dir in dirs if not _dir.startswith("2") + os.path.join(dir_path, _dir) for _dir in dirs if not _dir.startswith("2") ] issue_dirs = [ os.path.join(journal, _dir) for journal in journal_dirs for _dir in os.listdir(journal) - ] - - with open(access_rights, 'r') as f: + ] + + with open(access_rights, "r", encoding="utf-8") as f: access_rights_dict = json.load(f) issue_dirs = [dir2issue(_dir, access_rights_dict) for _dir in issue_dirs] initial_length = len(issue_dirs) issue_dirs = [i for i in issue_dirs if i is not None] - - df = pd.DataFrame([ - {"issue": i, "id": get_id(i), "number": get_number(i)} - for i in issue_dirs - ]) - vals = df.groupby('id').apply( - lambda x: x[['issue', 'number']].values.tolist() - ).values - + + df = pd.DataFrame( + [{"issue": i, "id": get_id(i), "number": get_number(i)} for i in issue_dirs] + ) + vals = ( + df.groupby("id").apply(lambda x: x[["issue", "number"]].values.tolist()).values + ) + issue_dirs = [i if len(i) == 1 else assign_editions(i) for i in vals] issue_dirs = [j[0] for i in issue_dirs for j in i] - - logger.info(f"Removed {initial_length - len(issue_dirs)} problematic issues") + + logger.info("Removed %s problematic issues", initial_length - len(issue_dirs)) return [i for i in issue_dirs if i is not None] @@ -210,7 +206,7 @@ def select_issues( base_dir: str, config: dict, access_rights: str ) -> Optional[List[BnfIssueDir]]: """Detect selectively newspaper issues to import. - + The behavior is very similar to :func:`detect_issues` with the only difference that ``config`` specifies some rules to filter the data to import. See `this section <../importers.html#configuration-files>`__ for @@ -219,38 +215,42 @@ def select_issues( Args: base_dir (str): Path to the base directory of newspaper data. config (dict): Config dictionary for filtering. - access_rights (str): Not used for this imported, but argument is kept + access_rights (str): Not used for this imported, but argument is kept for normality. Returns: Optional[List[BnfIssueDir]]: List of `BnfIssueDir` instances, to be imported. """ - + # read filters from json configuration (see config.example.json) try: filter_dict = config["newspapers"] exclude_list = config["exclude_newspapers"] year_flag = config["year_only"] - + except KeyError: - logger.critical(f"The key [newspapers|exclude_newspapers|year_only] " - "is missing in the config file.") + logger.critical( + "The key [newspapers|exclude_newspapers|year_only] " + "is missing in the config file." + ) return - + issues = detect_issues(base_dir, access_rights) issue_bag = db.from_sequence(issues) - selected_issues = ( - issue_bag.filter( - lambda i: ( - len(filter_dict) == 0 or i.journal in filter_dict.keys() - ) and i.journal not in exclude_list - ).compute() - ) + selected_issues = issue_bag.filter( + lambda i: (len(filter_dict) == 0 or i.journal in filter_dict.keys()) + and i.journal not in exclude_list + ).compute() exclude_flag = False if not exclude_list else True - filtered_issues = _apply_datefilter( - filter_dict, selected_issues, year_only=year_flag - ) if not exclude_flag else selected_issues - logger.info(f"{len(filtered_issues)} newspaper issues remained " - f"after applying filter: {filtered_issues}") - + filtered_issues = ( + _apply_datefilter(filter_dict, selected_issues, year_only=year_flag) + if not exclude_flag + else selected_issues + ) + logger.info( + "%s newspaper issues remained after applying filter: %s", + len(filtered_issues), + filtered_issues, + ) + return filtered_issues diff --git a/text_importer/importers/bnf/helpers.py b/text_importer/importers/bnf/helpers.py index e2bb7a60..6b677c12 100644 --- a/text_importer/importers/bnf/helpers.py +++ b/text_importer/importers/bnf/helpers.py @@ -1,14 +1,26 @@ """Set of helper functions for BNF importer""" + import logging from datetime import datetime -from typing import List, Optional, Tuple +from typing import Optional -from text_importer.importers import CONTENTITEM_TYPE_ADVERTISEMENT, CONTENTITEM_TYPE_ARTICLE, CONTENTITEM_TYPE_IMAGE, \ - CONTENTITEM_TYPE_OBITUARY, CONTENTITEM_TYPE_TABLE +from text_importer.importers import ( + CONTENTITEM_TYPE_ADVERTISEMENT, + CONTENTITEM_TYPE_ARTICLE, + CONTENTITEM_TYPE_IMAGE, + CONTENTITEM_TYPE_OBITUARY, + CONTENTITEM_TYPE_TABLE, +) # BNF types that do not have a direct `area` descendant -BNF_CONTENT_TYPES = ["article", "advertisement", "illustration", - "ornament", "freead", "table"] +BNF_CONTENT_TYPES = [ + "article", + "advertisement", + "illustration", + "ornament", + "freead", + "table", +] SECTION = "section" """ Content types as defined in BNF Mets flavour. @@ -18,12 +30,12 @@ """ type_translation = { - 'illustration': CONTENTITEM_TYPE_IMAGE, - 'advertisement': CONTENTITEM_TYPE_ADVERTISEMENT, - 'ornament': CONTENTITEM_TYPE_OBITUARY, - 'table': CONTENTITEM_TYPE_TABLE, - 'article': CONTENTITEM_TYPE_ARTICLE, - 'freead': CONTENTITEM_TYPE_ADVERTISEMENT + "illustration": CONTENTITEM_TYPE_IMAGE, + "advertisement": CONTENTITEM_TYPE_ADVERTISEMENT, + "ornament": CONTENTITEM_TYPE_OBITUARY, + "table": CONTENTITEM_TYPE_TABLE, + "article": CONTENTITEM_TYPE_ARTICLE, + "freead": CONTENTITEM_TYPE_ADVERTISEMENT, } logger = logging.getLogger(__name__) @@ -33,7 +45,7 @@ def add_div( _dict: dict[str, tuple[str, str]], _type: str, div_id: str, label: str ) -> dict[str, tuple[str, str]]: """Adds a div item to the given dictionary (sorted by type). - + The types used as keys should be in `BNF_CONTENT_TYPES` or `SECTION`. Args: @@ -51,7 +63,8 @@ def add_div( else: _dict[_type] = [(div_id, label)] else: - logger.warning(f"Tried to add div of type {_type}") + logger.warning("Tried to add div of type %s", _type) + return _dict @@ -66,8 +79,9 @@ def get_journal_name(archive_path: str) -> str: Returns: str: Extracted journal name in lowercase. """ - journal = archive_path.split('/')[-2].split('-') + journal = archive_path.split("/")[-2].split("-") journal = "".join(journal).lower() + return journal @@ -102,6 +116,7 @@ def get_dates(date_string: str, separators: list[str]) -> list[Optional[str]]: for s in separators: if len(date_string.split(s)) == 2: return date_string.split(s) + return [None, None] @@ -111,7 +126,7 @@ def parse_date( """Parse a date given a list of formats. The input string can sometimes represent a pair of dates, in which case - they are both parsed if possible. + they are both parsed if possible. Args: date_string (str): Date string to parse. @@ -130,31 +145,32 @@ def parse_date( """ date, secondary = None, None # Date_string 1 and 2 - ds_1, ds_2 = None, None - - # Dates have at least 10 characters. + ds_1, ds_2 = None, None + + # Dates have at least 10 characters. # Some (very rarely) issues have only year/month if len(date_string) < 10: - raise ValueError("Could not parse date {}".format(date_string)) + raise ValueError(f"Could not parse date {date_string}") # Here we potentially have two dates, take the first one - elif is_multi_date(date_string): - logger.info(f"Got two dates {date_string}") + elif is_multi_date(date_string): + logger.info("Got two dates %s", date_string) ds_1, ds_2 = get_dates(date_string, separators) - + if ds_1 is None: - raise ValueError("Could not parse date {}".format(date_string)) + raise ValueError(f"Could not parse date {date_string}") else: ds_1 = date_string - + # Now parse for f in formats: try: date = datetime.strptime(ds_1, f).date() if ds_2 is not None: secondary = datetime.strptime(ds_2, f).date() - except ValueError as e: + except ValueError: pass - + if date is None: - raise ValueError("Could not parse date {}".format(date_string)) + raise ValueError(f"Could not parse date {date_string}") + return date, secondary diff --git a/text_importer/importers/bnf/parsers.py b/text_importer/importers/bnf/parsers.py index c49c8c51..b5aca95a 100644 --- a/text_importer/importers/bnf/parsers.py +++ b/text_importer/importers/bnf/parsers.py @@ -1,6 +1,6 @@ """Utility functions to parse BNF ALTO files.""" + import logging -from typing import List, Dict, Optional, Tuple import bs4 from bs4 import NavigableString @@ -13,7 +13,7 @@ def parse_printspace( - element: Tag, mappings: Dict[str, str] + element: Tag, mappings: dict[str, str] ) -> tuple[list[dict], list[str] | None]: """Parse the ```` element of an ALTO XML document for BNF. @@ -25,15 +25,15 @@ def parse_printspace( tuple[list[dict], list[str] | None]: Parsed regions and paragraphs, and potential notes on issues encountered during the parsing. """ - + regions = [] notes = [] for block in element.children: - + if isinstance(block, bs4.element.NavigableString): continue - - block_id = block.get('ID') + + block_id = block.get("ID") if block.name == "ComposedBlock": cb_regions, new_notes = parse_printspace(block, mappings) regions += cb_regions @@ -43,86 +43,81 @@ def parse_printspace( part_of_contentitem = mappings[block_id] else: part_of_contentitem = None - + coordinates = distill_coordinates(block) - + tmp = [ parse_textline(line_element) - for line_element in block.findAll('TextLine') + for line_element in block.findAll("TextLine") ] - + if len(tmp) > 0: lines, new_notes = list(zip(*tmp)) new_notes = [i for n in new_notes for i in n] else: lines, new_notes = [], [] - - paragraph = { - "c": coordinates, - "l": lines - } - - region = { - "c": coordinates, - "p": [paragraph] - } - + + paragraph = {"c": coordinates, "l": lines} + + region = {"c": coordinates, "p": [paragraph]} + if part_of_contentitem: - region['pOf'] = part_of_contentitem + region["pOf"] = part_of_contentitem notes += new_notes regions.append(region) + return regions, notes def parse_div_parts(div: Tag) -> list[dict[str, str | int]]: """Parse the parts of a given div element. - - Typically, any div of type in `BNF_CONTENT_TYPES` is composed of child + + Typically, any div of type in `BNF_CONTENT_TYPES` is composed of child divs. This is what this function parses. - Each element of the output contains keys {'comp_role', 'comp_id', + Each element of the output contains keys {'comp_role', 'comp_id', 'comp_fileid', 'comp_page_no'}. Args: div (Tag): Child div to parse. Returns: - list[dict[str, str | int]]: The list of parts of this Tag. + list[dict[str, str | int]]: The list of parts of this Tag. """ parts = [] for child in div.children: - + if isinstance(child, NavigableString): continue elif isinstance(child, Tag): - type_attr = child.get('TYPE') + type_attr = child.get("TYPE") comp_role = type_attr.lower() if type_attr else None - + if comp_role not in BNF_CONTENT_TYPES: - areas = child.findAll('area') + areas = child.findAll("area") for area in areas: - comp_id = area.get('BEGIN') - comp_fileid = area.get('FILEID') + comp_id = area.get("BEGIN") + comp_fileid = area.get("FILEID") comp_page_no = int(comp_fileid.split(".")[1]) - + parts.append( { - 'comp_role': comp_role, - 'comp_id': comp_id, - 'comp_fileid': comp_fileid, - 'comp_page_no': comp_page_no + "comp_role": comp_role, + "comp_id": comp_id, + "comp_fileid": comp_fileid, + "comp_page_no": comp_page_no, } ) return parts -def parse_embedded_cis(div: Tag, label: str, issue_id: str, - parent_id: str | None, counter: int +def parse_embedded_cis( + div: Tag, label: str, issue_id: str, parent_id: str | None, counter: int ) -> tuple[list[dict], int]: """Parse the div Tags embedded in the given one. - The input `div` should be of type in `BNF_CONTENT_TYPES` and should have + The input `div` should be of type in `BNF_CONTENT_TYPES` and should have children of types also in that category. - Each child tag represents separate content items, which should thus be + Each child tag represents separate content items, which should thus be processed separately. Args: @@ -137,37 +132,35 @@ def parse_embedded_cis(div: Tag, label: str, issue_id: str, """ new_cis = [] for child in div.children: - + if isinstance(child, NavigableString): continue elif isinstance(child, Tag): - type_attr = child.get('TYPE') + type_attr = child.get("TYPE") comp_role = type_attr.lower() if type_attr else None if comp_role in BNF_CONTENT_TYPES: if comp_role in type_translation: impresso_type = type_translation[comp_role] else: - logger.warning(f"Type {comp_role} does not have translation") + logger.warning("Type %s does not have translation", comp_role) impresso_type = comp_role - + metadata = { - 'id': "{}-i{}".format(issue_id, str(counter).zfill(4)), - 'tp': impresso_type, - 'pp': [], + "id": "{}-i{}".format(issue_id, str(counter).zfill(4)), + "tp": impresso_type, + "pp": [], } - lab = child.get('LABEL') or label - + lab = child.get("LABEL") or label + if lab is not None: - metadata['t'] = lab - # Check if the parent exists - # (sometimes tables are embedded into articles, + metadata["t"] = lab + # Check if the parent exists + # (sometimes tables are embedded into articles, # but the articles are empty) if parent_id is not None: - metadata['pOf'] = parent_id - new_ci = { - 'm': metadata, - 'l': {'parts': parse_div_parts(child)} - } + metadata["pOf"] = parent_id + new_ci = {"m": metadata, "l": {"parts": parse_div_parts(child)}} new_cis.append(new_ci) counter += 1 + return new_cis, counter diff --git a/text_importer/importers/bnf_en/classes.py b/text_importer/importers/bnf_en/classes.py index bdd6337b..7cdd78a3 100644 --- a/text_importer/importers/bnf_en/classes.py +++ b/text_importer/importers/bnf_en/classes.py @@ -256,6 +256,7 @@ def _parse_content_item( content_item["c"], content_item["m"]["iiif_link"] = self._get_image_info( content_item ) + return content_item def _decompose_section(self, div: Tag) -> list[Tag]: @@ -287,6 +288,7 @@ def _decompose_section(self, div: Tag) -> list[Tag]: final_divs += self._decompose_section(d) else: final_divs.append(d) + return final_divs def _parse_content_items(self) -> list[dict[str, Any]]: diff --git a/text_importer/importers/bnf_en/detect.py b/text_importer/importers/bnf_en/detect.py index 18610ed5..46fa0294 100644 --- a/text_importer/importers/bnf_en/detect.py +++ b/text_importer/importers/bnf_en/detect.py @@ -77,11 +77,14 @@ def get_api_id(journal: str, api_issue: tuple[str, datetime.date], edition: str) journal, date.year, date.month, date.day, ascii_lowercase[edition] ) -def fix_api_year_mismatch(journal: str, year: int, api_issues: list[Tag], last_i: list[Tag]|None) -> tuple[list[Tag], Tag | None]: + +def fix_api_year_mismatch( + journal: str, year: int, api_issues: list[Tag], last_i: list[Tag] | None +) -> tuple[list[Tag], list[Tag] | None]: """Modify proivded list of issues fetched from the API to fix some issues present. Indeed, the API currently wronly stores the issues for december 31st of some years, - with some issues being shifted from one year. + with some issues being shifted from one year. This is not the case for all years, and the correct issue can be present or not. This function aims to rectify this issue and fetch the correct IIIF ark IDs. @@ -92,22 +95,24 @@ def fix_api_year_mismatch(journal: str, year: int, api_issues: list[Tag], last_i last_i (Tag): Last december 31st issue entry, returned for the wrong year. Returns: - tuple[list[Tag], Tag | None]: Corrected issue list and next december 31st issue - if the error was present again, None otherwise. + tuple[list[Tag], list[Tag] | None]: Corrected issue list and next december 31st + issue(s) if the error was present again, None otherwise. """ curr_last_i = last_i # if there is indeed a mismatch in the year - if str(year-1) in api_issues[-1].getText(): - if '31 décembre' not in api_issues[-1].getText(): + if str(year - 1) in api_issues[-1].getText(): + if "31 décembre" not in api_issues[-1].getText(): logger.warning( - '%s-%s: Mismatch in year for another day!!: %s', - journal, year, api_issues[-1] - ) + "%s-%s: Mismatch in year for another day!!: %s", + journal, + year, + api_issues[-1], + ) next_last_i = None else: - #it can happen that there are 2 issues on Dec 31st: - if str(year-1) in api_issues[-2].getText(): + # it can happen that there are 2 issues on Dec 31st: + if str(year - 1) in api_issues[-2].getText(): # save the last 2 issues msg = f"{journal}-{year}: Saving 2 editions for Dec 31st {curr_last_i}" logger.info(msg) @@ -141,7 +146,10 @@ def fix_api_year_mismatch(journal: str, year: int, api_issues: list[Tag], last_i logger.info(msg) elif all(str(year) in i.getText() for i in curr_last_i): # if the last stored value corresponds to this year and december 31st is missing, add it - if all('31 décembre' in i.getText() for i in curr_last_i) and '31 décembre' not in api_issues[-1].getText(): + if ( + all("31 décembre" in i.getText() for i in curr_last_i) + and "31 décembre" not in api_issues[-1].getText() + ): api_issues.extend(curr_last_i) msg = f"{journal}-{year}: Appending {curr_last_i} to api_issues." logger.info(msg) @@ -191,7 +199,7 @@ def get_date(dayofyear: str, year: int) -> datetime.date: years = [int(x.contents[0]) for x in years] links = [] - next_year_last_i = None + next_year_last_i = None # start with the last year for year in tqdm(years[::-1]): @@ -201,17 +209,27 @@ def get_date(dayofyear: str, year: int) -> datetime.date: api_issues = BeautifulSoup(r.content, "lxml").findAll("issue") # fix the problem stemming from the API with dec. 31st being of following year - if str(year-1) in api_issues[-1].getText() or (next_year_last_i is not None and all(str(year) in i.getText() for i in next_year_last_i)): - logger.debug("%s-%s: api_issues[-1].getText(): %s, next_year_last_i: %s", journal, year, api_issues[-1].getText(), next_year_last_i) - api_issues, next_year_last_i = fix_api_year_mismatch(journal, year, api_issues, next_year_last_i) + if str(year - 1) in api_issues[-1].getText() or ( + next_year_last_i is not None + and all(str(year) in i.getText() for i in next_year_last_i) + ): + logger.debug( + "%s-%s: api_issues[-1].getText(): %s, next_year_last_i: %s", + journal, + year, + api_issues[-1].getText(), + next_year_last_i, + ) + api_issues, next_year_last_i = fix_api_year_mismatch( + journal, year, api_issues, next_year_last_i + ) else: # reset the value since it won't be valid anymore next_year_last_i = None # Parse dates and editions api_issues = [ - (i.get("ark"), get_date(i.get("dayofyear"), year)) - for i in api_issues + (i.get("ark"), get_date(i.get("dayofyear"), year)) for i in api_issues ] editions = [] diff --git a/text_importer/importers/mets_alto/mets.py b/text_importer/importers/mets_alto/mets.py index 3d77bed3..a4cdcaa7 100644 --- a/text_importer/importers/mets_alto/mets.py +++ b/text_importer/importers/mets_alto/mets.py @@ -98,7 +98,7 @@ def get_dmd_sec(mets_doc: BeautifulSoup, _filter: str) -> Tag: Args: mets_doc (BeautifulSoup): BeautifulSoup object of Mets XML document. - filter (str): ID of the subsection of interest to filter the search. + _filter (str): ID of the subsection of interest to filter the search. Returns: Tag: Contents of the desired ```` of the Mets XML document. diff --git a/text_importer/importers/olive/detect.py b/text_importer/importers/olive/detect.py index 81af2f80..21bc3ef5 100644 --- a/text_importer/importers/olive/detect.py +++ b/text_importer/importers/olive/detect.py @@ -5,19 +5,12 @@ from collections import namedtuple from typing import Any -from impresso_commons.path.path_fs import (IssueDir, detect_issues, - select_issues) +from impresso_commons.path.path_fs import IssueDir, detect_issues, select_issues from text_importer.utils import get_access_right OliveIssueDir = namedtuple( - "OliveIssueDirectory", [ - 'journal', - 'date', - 'edition', - 'path', - 'rights' - ] + "OliveIssueDirectory", ["journal", "date", "edition", "path", "rights"] ) """A light-weight data structure to represent a newspaper issue. @@ -58,12 +51,9 @@ def dir2olivedir( OliveIssueDir: New ``OliveIssueDir`` object. """ ar = get_access_right(issue_dir.journal, issue_dir.date, access_rights) + return OliveIssueDir( - issue_dir.journal, - issue_dir.date, - issue_dir.edition, - issue_dir.path, - rights=ar + issue_dir.journal, issue_dir.date, issue_dir.edition, issue_dir.path, rights=ar ) @@ -71,7 +61,7 @@ def olive_detect_issues( base_dir: str, access_rights: str, journal_filter: set | None = None, - exclude: bool = False + exclude: bool = False, ) -> list[OliveIssueDir]: """Detect newspaper issues to import within the filesystem. @@ -81,7 +71,7 @@ def olive_detect_issues( Args: base_dir (str): Path to the base directory of newspaper data. access_rights (str): Path to ``access_rights.json`` file. - journal_filter (set | None, optional): IDs of newspapers to consider. + journal_filter (set | None, optional): IDs of newspapers to consider. Defaults to None. exclude (bool, optional): Whether ``journal_filter`` should determine exclusion. Defaults to False. @@ -89,22 +79,16 @@ def olive_detect_issues( Returns: list[OliveIssueDir]: List of `OliveIssueDir` instances, to be imported. """ - with open(access_rights, 'r') as f: + with open(access_rights, "r", encoding="utf-8") as f: access_rights_dict = json.load(f) - issues = detect_issues( - base_dir, - journal_filter=journal_filter, - exclude=exclude - ) + issues = detect_issues(base_dir, journal_filter=journal_filter, exclude=exclude) return [dir2olivedir(x, access_rights_dict) for x in issues] def olive_select_issues( - base_dir: str, - config: dict[str, Any], - access_rights: str + base_dir: str, config: dict[str, Any], access_rights: str ) -> list[OliveIssueDir]: """Detect selectively newspaper issues to import. @@ -121,7 +105,7 @@ def olive_select_issues( Returns: list[OliveIssueDir]: List of `OliveIssueDir` instances, to be imported. """ - with open(access_rights, 'r') as f: + with open(access_rights, "r", encoding="utf-8") as f: access_rights_dict = json.load(f) issues = select_issues(config, base_dir) diff --git a/text_importer/importers/olive/helpers.py b/text_importer/importers/olive/helpers.py index d712ee40..d908171c 100644 --- a/text_importer/importers/olive/helpers.py +++ b/text_importer/importers/olive/helpers.py @@ -35,15 +35,15 @@ def merge_tokens(tokens: list[dict[str, Any]], line: str) -> dict[str, Any]: merged_token = { "tx": "".join([token["tx"] for token in tokens]), "c": tokens[0]["c"][:2] + tokens[-1]["c"][2:], - "s": tokens[0]["s"] + "s": tokens[0]["s"], } logger.debug( - "(In-line pseudo tokens) Merged {} => {} in line \"{}\"".format( - "".join([t["tx"] for t in tokens]), - merged_token["tx"], - line - ) + '(In-line pseudo tokens) Merged %s => %s in line "%s"', + "".join([t["tx"] for t in tokens]), + merged_token["tx"], + line, ) + return merged_token @@ -57,7 +57,7 @@ def merge_pseudo_tokens(line: dict[str, list[Any]]) -> dict[str, list[Any]]: dict[str, list[Any]]: A new line object (with some merged tokens). """ original_line = " ".join([t["tx"] for t in line["t"]]) - qids = set([token["qid"] for token in line["t"] if "qid" in token]) + qids = {token["qid"] for token in line["t"] if "qid" in token} inline_qids = [] @@ -82,10 +82,7 @@ def merge_pseudo_tokens(line: dict[str, list[Any]]) -> dict[str, list[Any]]: ] # remove tokens to merge from the line - tokens_to_merge = [ - line["t"].pop(line["t"].index(token)) - for i, token in tokens - ] + tokens_to_merge = [line["t"].pop(line["t"].index(token)) for i, token in tokens] if len(tokens_to_merge) >= 2: insertion_point = tokens[0][0] @@ -115,18 +112,15 @@ def normalize_hyphenation(line: dict[str, list[Any]]) -> dict[str, list[Any]]: "tx": "".join([prev_token["tx"], token["tx"]]), "c": prev_token["c"][:2] + token["c"][2:], "s": token["s"], - "hy": token["hy"] + "hy": token["hy"], } - logger.debug( - f"Merged {prev_token} and {token} => {merged_token}" - ) + logger.debug("Merged %s and %s => %s", prev_token, token, merged_token) line["t"].append(merged_token) + return line -def combine_article_parts( - article_parts: list[dict[str, Any]] -) -> dict[str, Any]: +def combine_article_parts(article_parts: list[dict[str, Any]]) -> dict[str, Any]: """Merge article parts into a single element. Olive format splits an article into multiple components whenever it spans @@ -141,30 +135,17 @@ def combine_article_parts( if len(article_parts) > 1: # if an article has >1 part, retain the metadata # from the first item in the list - article_dict = { - "meta": {}, - "fulltext": "", - "stats": {}, - "legacy": {} - } - article_dict["legacy"]["id"] = [ - ar["legacy"]["id"] - for ar in article_parts - ] + article_dict = {"meta": {}, "fulltext": "", "stats": {}, "legacy": {}} + article_dict["legacy"]["id"] = [ar["legacy"]["id"] for ar in article_parts] article_dict["legacy"]["source"] = [ - ar["legacy"]["source"] - for ar in article_parts + ar["legacy"]["source"] for ar in article_parts ] article_dict["meta"]["type"] = {} - article_dict["meta"]["type"]["raw"] = ( - article_parts[0]["meta"]["type"]["raw"] - ) + article_dict["meta"]["type"]["raw"] = article_parts[0]["meta"]["type"]["raw"] article_dict["meta"]["title"] = article_parts[0]["meta"]["title"] article_dict["meta"]["page_no"] = [ - int(n) - for ar in article_parts - for n in ar["meta"]["page_no"] + int(n) for ar in article_parts for n in ar["meta"]["page_no"] ] # TODO: remove from production @@ -174,19 +155,16 @@ def combine_article_parts( article_dict["meta"]["language"] = {} article_dict["meta"]["language"] = article_parts[0]["meta"]["language"] - article_dict["meta"]["issue_date"] = ( - article_parts[0]["meta"]["issue_date"] - ) + article_dict["meta"]["issue_date"] = article_parts[0]["meta"]["issue_date"] elif len(article_parts) == 1: article_dict = next(iter(article_parts)) else: article_dict = None + return article_dict -def normalize_line( - line: dict[str, list[Any]], lang: str -) -> dict[str, list[Any]]: +def normalize_line(line: dict[str, list[Any]], lang: str) -> dict[str, list[Any]]: """Apply normalization rules to a line of OCR. The normalization rules that are applied depend on the language in which @@ -200,11 +178,7 @@ def normalize_line( Returns: dict[str, list[Any]]: The new normalized line of text. """ - mw_tokens = [ - token - for token in line["t"] - if "qid" in token - ] + mw_tokens = [token for token in line["t"] if "qid" in token] # apply normalization only to those lines that contain at least one # multi-word token (denoted by presence of `qid` field) if len(mw_tokens) > 0: @@ -220,34 +194,20 @@ def normalize_line( if i == 0 and i != len(line["t"]) - 1: insert_ws = insert_whitespace( - token["tx"], - line["t"][i + 1]["tx"], - None, - lang + token["tx"], line["t"][i + 1]["tx"], None, lang ) elif i == 0 and i == len(line["t"]) - 1: - insert_ws = insert_whitespace( - token["tx"], - None, - None, - lang - ) + insert_ws = insert_whitespace(token["tx"], None, None, lang) elif i == len(line["t"]) - 1: insert_ws = insert_whitespace( - token["tx"], - None, - line["t"][i - 1]["tx"], - lang + token["tx"], None, line["t"][i - 1]["tx"], lang ) else: insert_ws = insert_whitespace( - token["tx"], - line["t"][i + 1]["tx"], - line["t"][i - 1]["tx"], - lang + token["tx"], line["t"][i + 1]["tx"], line["t"][i - 1]["tx"], lang ) if not insert_ws: token["gn"] = True @@ -256,9 +216,9 @@ def normalize_line( def keep_title(title: str) -> bool: - """Whether an element's title should be kept. + """Whether an element's title should be kept. - The title should not be kept if it is one of "untitled article", + The title should not be kept if it is one of "untitled article", "untitled ad", and "untitled picture". Args: @@ -267,15 +227,11 @@ def keep_title(title: str) -> bool: Returns: bool: False if given title is in the black list, True otherwise. """ - black_list = [ - "untitled article", - "untitled ad", - "untitled picture" - ] + black_list = ["untitled article", "untitled ad", "untitled picture"] if title.lower() in black_list: return False - else: - return True + + return True def recompose_ToC( @@ -300,16 +256,15 @@ def recompose_ToC( toc_data = copy.deepcopy(original_toc_data) # concate content items from all pages into a single flat list content_items = [ - toc_data[pn][elid] - for pn in toc_data.keys() for elid in toc_data[pn].keys() + toc_data[pn][elid] for pn in toc_data.keys() for elid in toc_data[pn].keys() ] # filter out those items that are part of a multipart article contents = [] - sorted_content_items = sorted(content_items, key=itemgetter('seq')) + sorted_content_items = sorted(content_items, key=itemgetter("seq")) for item in sorted_content_items: - item['m'] = {} + item["m"] = {} item["l"] = {} if item["type"] == "Article" or item["type"] == "Ad": @@ -318,7 +273,7 @@ def recompose_ToC( # by using `legacy_id` as the search key # if not found (raises exception) means that it's one of the # multipart articles, and it's ok to skip it - legacy_id = item['legacy_id'] + legacy_id = item["legacy_id"] article = None for ar in articles: if isinstance(ar["legacy"]["id"], list): @@ -333,13 +288,13 @@ def recompose_ToC( except Exception: continue - item['m']["id"] = item["id"] - item['m']['pp'] = article["meta"]["page_no"] - item['m']['l'] = article["meta"]["language"] - item['m']['tp'] = article["meta"]["type"]["raw"].lower() + item["m"]["id"] = item["id"] + item["m"]["pp"] = article["meta"]["page_no"] + item["m"]["l"] = article["meta"]["language"] + item["m"]["tp"] = article["meta"]["type"]["raw"].lower() if keep_title(article["meta"]["title"]): - item['m']['t'] = article["meta"]["title"] + item["m"]["t"] = article["meta"]["title"] item["l"]["id"] = article["legacy"]["id"] item["l"]["source"] = article["legacy"]["source"] @@ -350,38 +305,36 @@ def recompose_ToC( page_no = [ page_no for page_no in toc_data - if item['legacy_id'] in toc_data[page_no] + if item["legacy_id"] in toc_data[page_no] ] # get the new canonical id via the legacy id - item['m']['id'] = item['id'] - item['m']['tp'] = item['type'].lower() - item['m']['pp'] = page_no + item["m"]["id"] = item["id"] + item["m"]["tp"] = item["type"].lower() + item["m"]["pp"] = page_no try: - image = [ - image - for image in images - if image['id'] == item['legacy_id'] - ][0] + image = [image for image in images if image["id"] == item["legacy_id"]][ + 0 + ] except IndexError: # if the image XML was faulty (e.g. because of missing # coords, it won't find a corresping image item - logger.info(f"Image {item['legacy_id']} will be skipped") + logger.info("Image %s will be skipped", item["legacy_id"]) continue if keep_title(image["name"]): - item['m']['t'] = image["name"] + item["m"]["t"] = image["name"] - item['l']['id'] = item['legacy_id'] - item['l']['res'] = image['resolution'] - item['l']['path'] = image['filepath'] + item["l"]["id"] = item["legacy_id"] + item["l"]["res"] = image["resolution"] + item["l"]["path"] = image["filepath"] - item['c'] = image['coords'] - toc_item = toc_data[page_no[0]][item['legacy_id']] + item["c"] = image["coords"] + toc_item = toc_data[page_no[0]][item["legacy_id"]] if "embedded_into" in item: - cont_article_id = toc_item['embedded_into'] + cont_article_id = toc_item["embedded_into"] try: containing_article = toc_data[page_no[0]][cont_article_id] @@ -389,27 +342,29 @@ def recompose_ToC( # the `toc_data` dict, depending on whether they have # already been processed in this `for` loop or not if ( - "m" in containing_article and - len(containing_article['m'].keys()) > 0 + "m" in containing_article + and len(containing_article["m"].keys()) > 0 ): - item['pOf'] = containing_article['m']['id'] + item["pOf"] = containing_article["m"]["id"] else: - item['pOf'] = containing_article['id'] + item["pOf"] = containing_article["id"] except Exception as e: logger.error( - f"Containing article for {item['m']['id']}" - f" not found (error = {e})" + "Containing article for %s not found (error = %s)", + item["m"]["id"], + e, ) # delete redundant fields if "embedded_into" in item: - del item['embedded_into'] - del item['seq'] - del item['legacy_id'] - del item['type'] - del item['id'] + del item["embedded_into"] + del item["seq"] + del item["legacy_id"] + del item["type"] + del item["id"] contents.append(item) + return contents @@ -436,17 +391,11 @@ def recompose_page( Returns: dict[str, Any]: Page data according to impresso canonical format. """ - page = { - "r": [], - "cdt": strftime("%Y-%m-%d %H:%M:%S") - } - ordered_elements = sorted( - list(info_from_toc.values()), key=itemgetter('seq') - ) + page = {"r": [], "cdt": strftime("%Y-%m-%d %H:%M:%S")} + ordered_elements = sorted(list(info_from_toc.values()), key=itemgetter("seq")) id_mappings = { - legacy_id: info_from_toc[legacy_id]['id'] - for legacy_id in info_from_toc + legacy_id: info_from_toc[legacy_id]["id"] for legacy_id in info_from_toc } # put together the regions while keeping the order in the page @@ -459,11 +408,11 @@ def recompose_page( # this is to manage the situation of a multi-part article part_of = None - if el['legacy_id'] in clusters: - part_of = el['legacy_id'] + if el["legacy_id"] in clusters: + part_of = el["legacy_id"] else: for key in clusters: - if el['legacy_id'] in clusters[key]: + if el["legacy_id"] in clusters[key]: part_of = key break @@ -471,12 +420,12 @@ def recompose_page( element = page_elements[el["legacy_id"]] else: logger.error( - f"{el['id']}: {el['legacy_id']} not found in page {page_id}" + "%s: %s not found in page %s", el["id"], el["legacy_id"], page_id ) continue mapped_id = id_mappings[part_of] if part_of in id_mappings else None - for i, region in enumerate(element["r"]): + for region in element["r"]: region["pOf"] = mapped_id page["r"] += element["r"] @@ -497,7 +446,8 @@ def convert_box(coords: list[int], scale_factor: float) -> list[int]: box = " ".join([str(coord) for coord in coords]) converted_box = compute_box(scale_factor, box) new_box = [int(c) for c in converted_box.split()] - logger.debug(f'Converted box coordinates: {box} => {converted_box}') + logger.debug("Converted box coordinates: %s => %s", box, converted_box) + return new_box @@ -510,13 +460,13 @@ def convert_page_coordinates( page_image_name: str, zip_archive: ZipArchive, box_strategy: str, - issue: NewspaperIssue + issue: NewspaperIssue, ) -> bool: """Convert coordinates of all elements in a page that have coordinates. Note: This conversion is necessary since the coordinates recorded in the XML - file were computed on a different image than the one used for display + file were computed on a different image than the one used for display in the impresso interface. Args: @@ -530,32 +480,27 @@ def convert_page_coordinates( Returns: bool: Whether the coordinate conversion was successful or not. """ - start_t = time.clock() + start_t = time.time() scale_factor = get_scale_factor( - issue.path, - zip_archive, - page_xml, - box_strategy, - page_image_name + issue.path, zip_archive, page_xml, box_strategy, page_image_name ) if scale_factor is not None: - for region in page['r']: - region['c'] = convert_box(region['c'], scale_factor) - for paragraph in region['p']: - for line in paragraph['l']: - line['c'] = convert_box(line['c'], scale_factor) - for token in line['t']: - token['c'] = convert_box(token['c'], scale_factor) - end_t = time.clock() + for region in page["r"]: + region["c"] = convert_box(region["c"], scale_factor) + for paragraph in region["p"]: + for line in paragraph["l"]: + line["c"] = convert_box(line["c"], scale_factor) + for token in line["t"]: + token["c"] = convert_box(token["c"], scale_factor) + end_t = time.time() t = end_t - start_t logger.debug( - f"Converted coordinates {page_image_name}" - f" in {issue.id} (took {t}s)" + "Converted coordinates %s in %s (took %ss)", page_image_name, issue.id, t ) return True - else: - logger.info(f"Could not find scale factor for {page['id']}") - return False + + logger.info("Could not find scale factor for %s", page["id"]) + return False def convert_image_coordinates( @@ -564,13 +509,13 @@ def convert_image_coordinates( page_image_name: str, zip_archive: ZipArchive, box_strategy: str, - issue: IssueDir + issue: IssueDir, ) -> dict[str, Any]: """Convert coordinates of an Olive image element. Note: This conversion is necessary since the coordinates recorded in the XML - file were computed on a different image than the one used for display + file were computed on a different image than the one used for display in the impresso interface. Args: @@ -586,16 +531,12 @@ def convert_image_coordinates( """ try: scale_factor = get_scale_factor( - issue.path, - zip_archive, - page_xml, - box_strategy, - page_image_name + issue.path, zip_archive, page_xml, box_strategy, page_image_name ) - image['c'] = convert_box(image['c'], scale_factor) - image['cc'] = True + image["c"] = convert_box(image["c"], scale_factor) + image["cc"] = True except Exception: - image['cc'] = False + image["cc"] = False return image @@ -609,11 +550,8 @@ def normalize_language(language: str) -> str: Returns: str: Normalized language, one of "fr", "en" and "de". """ - mappings = { - "french": "fr", - "english": "en", - "german": "de" - } + mappings = {"french": "fr", "english": "en", "german": "de"} + return mappings[language.lower()] @@ -636,4 +574,5 @@ def get_clusters(articles: list[dict[str, Any]]) -> dict[str, list[str]]: clusters[legacy_id[0]] = legacy_id else: clusters[legacy_id] = [legacy_id] + return clusters diff --git a/text_importer/importers/olive/parsers.py b/text_importer/importers/olive/parsers.py index 0f25cc6e..7b5848bf 100644 --- a/text_importer/importers/olive/parsers.py +++ b/text_importer/importers/olive/parsers.py @@ -8,8 +8,7 @@ from bs4 import BeautifulSoup from impresso_commons.path.path_fs import canonical_path, IssueDir -from text_importer.importers.olive.helpers import (normalize_language, - normalize_line) +from text_importer.importers.olive.helpers import normalize_language, normalize_line def parse_styles(text: str) -> list[dict[str, Any]]: @@ -36,13 +35,12 @@ def parse_styles(text: str) -> list[dict[str, Any]]: n, font, font_size, color = re.match(regex, line).groups() styles.append( { - "id": int(n), - "f": font.replace('"', ""), - "fs": float(font_size), - "rgb": [ - int(i) - for i in color.replace("(", "").replace(")", "").split(",") - ] + "id": int(n), + "f": font.replace('"', ""), + "fs": float(font_size), + "rgb": [ + int(i) for i in color.replace("(", "").replace(")", "").split(",") + ], } ) @@ -64,11 +62,11 @@ def olive_image_parser(text: bytes) -> dict[str, str | list] | None: try: assert root is not None img = { - 'id': root.get('id'), - 'coords': root.img.get('box').split(), - 'name': root.meta.get('name'), - 'resolution': root.meta.get('images_resolution'), - 'filepath': root.img.get('href') + "id": root.get("id"), + "coords": root.img.get("box").split(), + "name": root.meta.get("name"), + "resolution": root.meta.get("images_resolution"), + "filepath": root.img.get("href"), } return img except AssertionError: @@ -76,14 +74,12 @@ def olive_image_parser(text: bytes) -> dict[str, str | list] | None: def olive_toc_parser( - toc_path: str, - issue_dir: IssueDir, - encoding: str = "windows-1252" + toc_path: str, issue_dir: IssueDir, encoding: str = "windows-1252" ) -> dict[int, dict[str, dict]]: """Parse the TOC.xml file (Olive format). For each page, the a dict containing page data is created; mapping content - item legacy IDs to their metadata. + item legacy IDs to their metadata. Args: toc_path (str): Path to the ToC XML file. @@ -94,14 +90,14 @@ def olive_toc_parser( dict[int, dict[str, dict]]: Dictionary where keys are page numbers and values the corresponding page data dictionary. """ - with codecs.open(toc_path, 'r', encoding) as f: + with codecs.open(toc_path, "r", encoding) as f: text = f.read() toc_data = {} global_counter = 0 - for page in BeautifulSoup(text, 'lxml').find_all('page'): + for page in BeautifulSoup(text, "lxml").find_all("page"): page_data = {} for n, entity in enumerate(page.find_all("entity")): @@ -112,30 +108,24 @@ def olive_toc_parser( item = { "legacy_id": item_legacy_id, "id": canonical_path( - issue_dir, - name=f"i{str(global_counter).zfill(4)}", - extension="" - ), + issue_dir, name=f"i{str(global_counter).zfill(4)}", extension="" + ), "type": entity.get("entity_type"), - "seq": n + 1 + "seq": n + 1, } # if it's a picture we want to get also the article into which # the image is embedded - if item['type'].lower() == "picture": + if item["type"].lower() == "picture": if entity.get("embedded_into") is not None: - item['embedded_into'] = entity.get("embedded_into") + item["embedded_into"] = entity.get("embedded_into") page_data[item_legacy_id] = item - toc_data[int(page.get('page_no'))] = page_data + toc_data[int(page.get("page_no"))] = page_data # gather the IDs of all content items int the issue - ids = [ - toc_data[page][item]["id"] - for page in toc_data - for item in toc_data[page] - ] + ids = [toc_data[page][item]["id"] for page in toc_data for item in toc_data[page]] # check that these IDs are unique within the issue assert len(ids) == len(list(set(ids))) @@ -149,7 +139,7 @@ def olive_parser(text: str) -> dict[str, dict | list]: The main logic implemented here was derived from . Each XML file corresponds to one article, as detected by Olive. - The final dictionary has keys ``meta``, ``r``, ``stats`` and ``legacy``, + The final dictionary has keys ``meta``, ``r``, ``stats`` and ``legacy``, each mapping to dictionaries or lists with the file's parsed contents. Args: @@ -160,12 +150,12 @@ def olive_parser(text: str) -> dict[str, dict | list]: """ soup = BeautifulSoup(text, "lxml") root = soup.find("xmd-entity") - page_no = root['page_no'] - identifier = root['id'] - language = root['language'] - title = soup.meta['name'] - entity_type = root['entity_type'] - issue_date = soup.meta['issue_date'] + page_no = root["page_no"] + identifier = root["id"] + language = root["language"] + title = soup.meta["name"] + entity_type = root["entity_type"] + issue_date = soup.meta["issue_date"] out = { "meta": {"language": None, "type": {}}, @@ -179,31 +169,20 @@ def olive_parser(text: str) -> dict[str, dict | list]: out["meta"]["type"]["raw"] = entity_type out["meta"]["issue_date"] = issue_date - new_region = { - "c": [], - "p": [] - } + new_region = {"c": [], "p": []} - new_paragraph = { - "l": [] - } + new_paragraph = {"l": []} - new_line = { - "c": [], - "t": [] - } + new_line = {"c": [], "t": []} - new_token = { - "c": [], - "tx": "" - } + new_token = {"c": [], "tx": ""} for primitive in soup.find_all("primitive"): # store coordinate of text areas (boxes) by page # 1) page number, 2) coordinate list region = copy.deepcopy(new_region) - region["c"] = [int(i) for i in primitive.get('box').split(" ")] + region["c"] = [int(i) for i in primitive.get("box").split(" ")] para = None line = None @@ -226,7 +205,7 @@ def olive_parser(text: str) -> dict[str, dict | list]: para = copy.deepcopy(new_paragraph) line = copy.deepcopy(new_line) - line["c"] = [int(i) for i in tag.get('box').split(" ")] + line["c"] = [int(i) for i in tag.get("box").split(" ")] line_counter += 1 if tag.name in ["w", "q"]: @@ -234,13 +213,13 @@ def olive_parser(text: str) -> dict[str, dict | list]: # store coordinates of each token # 1) token, 2) page number, 3) coordinate list t = copy.deepcopy(new_token) - t["c"] = [int(i) for i in tag.get('box').split(" ")] + t["c"] = [int(i) for i in tag.get("box").split(" ")] t["tx"] = tag.string - t["s"] = int(tag.get('style_ref')) + t["s"] = int(tag.get("style_ref")) - if tag.name == "q" and tag.get('qid') is not None: - qid = tag.get('qid') - normalized_form = soup.find('qw', qid=qid).text + if tag.name == "q" and tag.get("qid") is not None: + qid = tag.get("qid") + normalized_form = soup.find("qw", qid=qid).text t["nf"] = normalized_form t["qid"] = qid @@ -258,16 +237,16 @@ def olive_parser(text: str) -> dict[str, dict | list]: out["r"].append(region) out["legacy"]["id"] = identifier - out["legacy"]["source"] = soup.link['source'] - out["legacy"]["first_id"] = soup.link['first_id'] - out["legacy"]["last_id"] = soup.link['last_id'] - out["legacy"]["next_id"] = soup.link['next_id'] - out["legacy"]["prev_id"] = soup.link['prev_id'] + out["legacy"]["source"] = soup.link["source"] + out["legacy"]["first_id"] = soup.link["first_id"] + out["legacy"]["last_id"] = soup.link["last_id"] + out["legacy"]["next_id"] = soup.link["next_id"] + out["legacy"]["prev_id"] = soup.link["prev_id"] - if root.has_attr('continuation_from'): - out["legacy"]["continuation_from"] = root['continuation_from'] + if root.has_attr("continuation_from"): + out["legacy"]["continuation_from"] = root["continuation_from"] - if root.has_attr('continuation_to'): - out["legacy"]["continuation_to"] = root['continuation_to'] + if root.has_attr("continuation_to"): + out["legacy"]["continuation_to"] = root["continuation_to"] return out diff --git a/text_importer/importers/rero/classes.py b/text_importer/importers/rero/classes.py index d0de5481..e313d643 100644 --- a/text_importer/importers/rero/classes.py +++ b/text_importer/importers/rero/classes.py @@ -19,7 +19,7 @@ MetsAltoNewspaperPage, parse_mets_amdsec, ) -from text_importer.utils import get_issue_schema, get_page_schema +from text_importer.utils import get_issue_schema, get_page_schema, get_reading_order IssueSchema = get_issue_schema() Pageschema = get_page_schema() @@ -397,8 +397,6 @@ def _decompose_section(self, div: Tag) -> list[Tag]: def _parse_content_items(self, mets_doc: BeautifulSoup) -> list[dict[str, Any]]: """Extract content item elements from the issue's Mets XML file. - TODO add reading order - Args: mets_doc (BeautifulSoup): Mets document as BeautifulSoup object. @@ -427,6 +425,11 @@ def _parse_content_items(self, mets_doc: BeautifulSoup) -> list[dict[str, Any]]: content_items.append(self._parse_content_item(div, counter, mets_doc)) counter += 1 + # add the reading order to the items metadata + reading_order_dict = get_reading_order(content_items) + for item in content_items: + item["m"]["ro"] = reading_order_dict[item["m"]["id"]] + return content_items def _parse_mets(self) -> None: @@ -451,7 +454,7 @@ def _parse_mets(self) -> None: self.issue_data = { "cdt": strftime("%Y-%m-%d %H:%M:%S"), - "i": content_items, # TODO add reading order + "i": content_items, "id": self.id, "ar": self.rights, "pp": [p.id for p in self.pages], diff --git a/text_importer/importers/rero/detect.py b/text_importer/importers/rero/detect.py index 386350d7..def9a8ee 100644 --- a/text_importer/importers/rero/detect.py +++ b/text_importer/importers/rero/detect.py @@ -14,22 +14,10 @@ logger = logging.getLogger(__name__) -EDITIONS_MAPPINGS = { - 1: 'a', - 2: 'b', - 3: 'c', - 4: 'd', - 5: 'e' -} +EDITIONS_MAPPINGS = {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"} Rero2IssueDir = namedtuple( - "IssueDirectory", [ - 'journal', - 'date', - 'edition', - 'path', - 'rights' - ] + "IssueDirectory", ["journal", "date", "edition", "path", "rights"] ) """A light-weight data structure to represent a newspaper issue. @@ -64,19 +52,23 @@ def dir2issue(path: str, access_rights: dict) -> Rero2IssueDir: Returns: Rero2IssueDir: New `Rero2IssueDir` object matching the path and rights. """ - journal, issue = path.split('/')[-2:] - date, edition = issue.split('_')[:2] - date = datetime.strptime(date, '%Y%m%d').date() + journal, issue = path.split("/")[-2:] + date, edition = issue.split("_")[:2] + date = datetime.strptime(date, "%Y%m%d").date() edition = EDITIONS_MAPPINGS[int(edition)] - return Rero2IssueDir(journal=journal, date=date, - edition=edition, path=path, - rights=get_access_right(journal, date, access_rights)) + return Rero2IssueDir( + journal=journal, + date=date, + edition=edition, + path=path, + rights=get_access_right(journal, date, access_rights), + ) def detect_issues( - base_dir: str, access_rights: str, data_dir: str = 'data' + base_dir: str, access_rights: str, data_dir: str = "data" ) -> list[Rero2IssueDir]: """Detect newspaper issues to import within the filesystem. @@ -88,13 +80,13 @@ def detect_issues( Args: base_dir (str): Path to the base directory of newspaper data. access_rights (str): Path to ``access_rights.json`` file. - data_dir (str, optional): Directory where data is stored + data_dir (str, optional): Directory where data is stored (usually `data/`). Defaults to 'data'. Returns: list[Rero2IssueDir]: list of `Rero2IssueDir` instances, to be imported. """ - dir_path, dirs, files = next(os.walk(base_dir)) + dir_path, dirs, _ = next(os.walk(base_dir)) journal_dirs = [os.path.join(dir_path, _dir) for _dir in dirs] journal_dirs = [ os.path.join(journal, _dir, _dir2) @@ -105,17 +97,18 @@ def detect_issues( ] issues_dirs = [ - os.path.join(j_dir, l) - for j_dir in journal_dirs + os.path.join(j_dir, l) + for j_dir in journal_dirs for l in os.listdir(j_dir) - if '.DS_Store' not in l + if ".DS_Store" not in l ] - with open(access_rights, 'r') as f: + with open(access_rights, "r", encoding="utf-8") as f: access_rights_dict = json.load(f) return [dir2issue(_dir, access_rights_dict) for _dir in issues_dirs] + def select_issues( base_dir: str, config: dict, access_rights: str ) -> list[Rero2IssueDir] | None: @@ -124,7 +117,7 @@ def select_issues( The behavior is very similar to :func:`detect_issues` with the only difference that ``config`` specifies some rules to filter the data to import. See `this section <../importers.html#configuration-files>`__ for - further details on how to configure filtering. + further details on how to configure filtering. Args: base_dir (str): Path to the base directory of newspaper data. @@ -141,29 +134,29 @@ def select_issues( year_flag = config["year_only"] except KeyError: - logger.critical(f"The key [newspapers|exclude_newspapers|year_only] " - "is missing in the config file.") + logger.critical( + "The key [newspapers|exclude_newspapers|year_only] " + "is missing in the config file." + ) return issues = detect_issues(base_dir, access_rights) issue_bag = db.from_sequence(issues) - selected_issues = ( - issue_bag.filter( - lambda i: ( - len(filter_dict) == 0 or i.journal in filter_dict.keys() - ) and i.journal not in exclude_list - ).compute() - ) + selected_issues = issue_bag.filter( + lambda i: (len(filter_dict) == 0 or i.journal in filter_dict.keys()) + and i.journal not in exclude_list + ).compute() exclude_flag = False if not exclude_list else True - filtered_issues = _apply_datefilter( - filter_dict, selected_issues, year_only=year_flag - ) if not exclude_flag else selected_issues + filtered_issues = ( + _apply_datefilter(filter_dict, selected_issues, year_only=year_flag) + if not exclude_flag + else selected_issues + ) logger.info( - "{} newspaper issues remained after applying filter: {}".format( - len(filtered_issues), - filtered_issues - ) + "%s newspaper issues remained after applying filter: %s", + len(filtered_issues), + filtered_issues, ) return filtered_issues diff --git a/text_importer/importers/swa/detect.py b/text_importer/importers/swa/detect.py index 8404c8bd..755db677 100644 --- a/text_importer/importers/swa/detect.py +++ b/text_importer/importers/swa/detect.py @@ -17,15 +17,16 @@ logger = logging.getLogger(__name__) SwaIssueDir = namedtuple( - "IssueDirectory", [ - 'journal', - 'date', - 'edition', - 'path', - 'rights', - 'pages', - ] - ) + "IssueDirectory", + [ + "journal", + "date", + "edition", + "path", + "rights", + "pages", + ], +) """A light-weight data structure to represent a SWA newspaper issue. This named tuple contains basic metadata about a newspaper issue. They @@ -79,10 +80,10 @@ def _get_csv_file(directory: str) -> str: files = os.listdir(directory) files = [f for f in files if f.endswith(".csv")] if len(files) == 0: - raise ValueError("Could not find csv file in {}".format(directory)) + raise ValueError(f"Could not find csv file in {directory}") elif len(files) > 1: - raise ValueError("Found multiple csv files in {}".format(directory)) - + raise ValueError(f"Found multiple csv files in {directory}") + return os.path.join(directory, files[0]) @@ -92,7 +93,7 @@ def _parse_csv_apply(part: pd.DataFrame) -> pd.Series: For all the pages in the current issue, matches the pages' canonical ID to the corresponding XML file. - Note: + Note: This function is called internally by :func:`detect_issues`. Args: @@ -105,8 +106,8 @@ def _parse_csv_apply(part: pd.DataFrame) -> pd.Series: archives = set() for i, x in part.iterrows(): res.append((x.identifier_impresso, x.full_xml_path)) - archives.add(x.goobi_name + '.zip') - return pd.Series({'pages': res, 'archives': archives}) + archives.add(x.goobi_name + ".zip") + return pd.Series({"pages": res, "archives": archives}) def _get_issuedir( @@ -117,7 +118,7 @@ def _get_issuedir( Each row of the CSV file contains the issue manifest ID, used to derive the issue canonical ID, and the path the XML files of the issue's pages. - Note: + Note: This function is called internally by :func:`detect_issues`. Args: @@ -130,28 +131,34 @@ def _get_issuedir( """ if len(row.archives) > 1: logger.debug( - f"Issue {row.manifest_id} has more than one archive {row.archives}" + "Issue %s has more than one archive %s", row.manifest_id, row.archives ) archive = os.path.join(journal_root, list(row.archives)[0]) - + if os.path.isfile(archive): - split = row.manifest_id.split('-')[:-1] + split = row.manifest_id.split("-")[:-1] if len(split) == 5: try: journal, year, mo, day, edition = split pub_date = date(int(year), int(mo), int(day)) rights = get_access_right(journal, pub_date, access_rights) - return SwaIssueDir(journal, pub_date, edition, path=archive, - rights=rights, pages=row.pages) + return SwaIssueDir( + journal, + pub_date, + edition, + path=archive, + rights=rights, + pages=row.pages, + ) except ValueError as e: logger.debug( - f"Issue {row.manifest_id} does not have a regular name" + "Issue %s does not have a regular name: %s", row.manifest_id, e ) else: - logger.debug(f"Issue {row.manifest_id} does not have regular name") + logger.debug("Issue %s does not have regular name", row.manifest_id) else: - logger.debug(f"Issue {row.manifest_id} does not have archive") + logger.debug("Issue %s does not have archive", row.manifest_id) return None @@ -160,7 +167,7 @@ def detect_issues(base_dir: str, access_rights: str) -> list[SwaIssueDir]: This function expects the directory structure that SWA used to organize the dump of Alto OCR data. - + The access rights information is not in place yet, but needs to be specified by the content provider (SWA). @@ -173,32 +180,32 @@ def detect_issues(base_dir: str, access_rights: str) -> list[SwaIssueDir]: Returns: list[SwaIssueDir]: list of ``SwaIssueDir`` instances, to be imported. """ - dir_path, dirs, files = next(os.walk(base_dir)) + dir_path, dirs, _ = next(os.walk(base_dir)) journal_dirs = [os.path.join(dir_path, _dir) for _dir in dirs] journal_dirs = [(d, _get_csv_file(d)) for d in journal_dirs] - - with open(access_rights, 'r') as f: + + with open(access_rights, "r", encoding="utf-8") as f: ar_dict = json.load(f) - + results = [] for journal_root, csv_file in journal_dirs: if os.path.isfile(csv_file): - df = (pd.read_csv(csv_file).groupby('manifest_id') - .apply(_parse_csv_apply).reset_index()) - result = df.apply( - lambda r: _get_issuedir(r, journal_root, ar_dict), axis=1 + df = ( + pd.read_csv(csv_file) + .groupby("manifest_id") + .apply(_parse_csv_apply) + .reset_index() ) + result = df.apply(lambda r: _get_issuedir(r, journal_root, ar_dict), axis=1) result = result[~result.isna()].values.tolist() results += result else: - logger.warning(f"Could not find csv file {csv_file}") - + logger.warning("Could not find csv file %s", csv_file) + return results -def select_issues( - base_dir: str, config: dict, access_rights: str -) -> list[SwaIssueDir]: +def select_issues(base_dir: str, config: dict, access_rights: str) -> list[SwaIssueDir]: """Detect selectively newspaper issues to import. The behavior is very similar to :func:`detect_issues` with the only @@ -222,50 +229,54 @@ def select_issues( exclude_list = config["exclude_newspapers"] year_flag = config["year_only"] except KeyError: - logger.critical(f"The key [newspapers|exclude_newspapers|year_only] " - "is missing in the config file.") + logger.critical( + "The key [newspapers|exclude_newspapers|year_only] " + "is missing in the config file." + ) return [] - + exclude_flag = False if not exclude_list else True - logger.debug(f"got filter_dict: {filter_dict}, " - f"\nexclude_list: {exclude_list}, " - f"\nyear_flag: {year_flag}" - f"\nexclude_flag: {exclude_flag}") - - filter_newspapers = (set(filter_dict.keys()) - if not exclude_list - else set(exclude_list)) - logger.debug(f"got filter_newspapers: {filter_newspapers}, " - f"with exclude flag: {exclude_flag}") + msg = ( + f"got filter_dict: {filter_dict}, " + f"\nexclude_list: {exclude_list}, " + f"\nyear_flag: {year_flag}" + f"\nexclude_flag: {exclude_flag}" + ) + logger.debug(msg) + + filter_newspapers = ( + set(filter_dict.keys()) if not exclude_list else set(exclude_list) + ) + logger.debug( + "got filter_newspapers: %s, with exclude flag: %s", + filter_newspapers, + exclude_flag, + ) issues = detect_issues(base_dir, access_rights) issue_bag = db.from_sequence(issues) - selected_issues = ( - issue_bag.filter( - lambda i: ( - len(filter_dict) == 0 or i.journal in filter_dict.keys() - ) and i.journal not in exclude_list - ).compute() - ) - + selected_issues = issue_bag.filter( + lambda i: (len(filter_dict) == 0 or i.journal in filter_dict.keys()) + and i.journal not in exclude_list + ).compute() + logger.info( - "{} newspaper issues remained after applying filter: {}".format( - len(selected_issues), - selected_issues - ) + "%s newspaper issues remained after applying filter: %s", + len(selected_issues), + selected_issues, ) - + exclude_flag = False if not exclude_list else True filtered_issues = ( - _apply_datefilter(filter_dict, selected_issues, year_only=year_flag) - if not exclude_flag + _apply_datefilter(filter_dict, selected_issues, year_only=year_flag) + if not exclude_flag else selected_issues ) logger.info( - "{} newspaper issues remained after applying filter: {}".format( - len(filtered_issues), - filtered_issues - ) + "%s newspaper issues remained after applying filter: %s", + len(filtered_issues), + filtered_issues, ) + return selected_issues diff --git a/text_importer/scripts/patching/canonical_patch_5_rero.py b/text_importer/scripts/patching/canonical_patch_5_rero.py index 61cbf46e..7de45719 100644 --- a/text_importer/scripts/patching/canonical_patch_5_rero.py +++ b/text_importer/scripts/patching/canonical_patch_5_rero.py @@ -1,4 +1,4 @@ -"""Command-line script to perform the patch #5 on the RERO 2 & 3 canonical data. +"""Command-line script to perform the patch #5 (adding the reading order property) on the RERO 2 & 3 canonical data. Usage: canonical_patch_5_rero.py --input-bucket= --output-bucket= --canonical-repo-path= --temp-dir= --log-file= --error-log= --patch-outputs-filename= @@ -15,21 +15,26 @@ """ import os -from docopt import docopt import logging +import copy +from typing import Any +from docopt import docopt + from impresso_commons.utils import s3 from impresso_commons.path.path_s3 import fetch_files from impresso_commons.versioning.compute_manifest import create_manifest -import dask.bag as db -from typing import Any from text_importer.utils import init_logger -import copy -from collections import defaultdict -from text_importer.scripts.patching.canonical_patch_1_uzh import write_jsonlines_file, title_year_pair_to_issues, write_upload_issues, to_issue_id_pages_dict, nzz_write_upload_pages +from text_importer.scripts.patching.canonical_patch_1_uzh import ( + title_year_pair_to_issues, + write_upload_issues, + to_issue_id_pages_dict, + nzz_write_upload_pages, +) IMPRESSO_STORAGEOPT = s3.get_storage_options() logger = logging.getLogger() + def get_reading_order(items: list[dict[str, Any]]) -> dict[str, int]: """Generate a reading order for items based on their id and the pages they span. @@ -43,19 +48,21 @@ def get_reading_order(items: list[dict[str, Any]]) -> dict[str, int]: dict[str, int]: A dictionary mapping item IDs to their reading order. """ items_copy = copy.deepcopy(items) - ids_and_pages = [(i['m']['id'], i['m']['pp']) for i in items_copy] + ids_and_pages = [(i["m"]["id"], i["m"]["pp"]) for i in items_copy] sorted_ids = sorted( - sorted(ids_and_pages, key=lambda x: int(x[0].split('-i')[-1])), - key=lambda x: x[1] + sorted(ids_and_pages, key=lambda x: int(x[0].split("-i")[-1])), + key=lambda x: x[1], ) - return {t[0]: index+1 for index, t in enumerate(sorted_ids)} + return {t[0]: index + 1 for index, t in enumerate(sorted_ids)} + def add_ro_to_items(issue: dict[str, Any]) -> list[dict[str, Any]]: - reading_order_dict = get_reading_order(issue['i']) - for ci in issue['i']: + reading_order_dict = get_reading_order(issue["i"]) + for ci in issue["i"]: ci["m"]["ro"] = reading_order_dict[ci["m"]["id"]] return issue + def main(): arguments = docopt(__doc__) s3_input_bucket = arguments["--input-bucket"] @@ -68,45 +75,80 @@ def main(): init_logger(logger, logging.INFO, log_file) - RERO_2_3_TITLES = ['BLB', 'BNN', 'DFS', 'DVF', 'EZR', 'FZG', 'HRV', 'LAB', 'LLE', 'MGS', 'NTS', 'NZG', 'SGZ', 'SRT', 'WHD', 'ZBT', 'CON', 'DTT', 'FCT', 'GAV', 'GAZ', 'LLS', 'OIZ', 'SAX', 'SDT', 'SMZ', 'VDR', 'VHT'] - PROP_NAME = 'ro' + RERO_2_3_TITLES = [ + "BLB", + "BNN", + "DFS", + "DVF", + "EZR", + "FZG", + "HRV", + "LAB", + "LLE", + "MGS", + "NTS", + "NZG", + "SGZ", + "SRT", + "WHD", + "ZBT", + "CON", + "DTT", + "FCT", + "GAV", + "GAZ", + "LLS", + "OIZ", + "SAX", + "SDT", + "SMZ", + "VDR", + "VHT", + ] + PROP_NAME = "ro" final_patches_output_path = os.path.join(os.path.dirname(log_file), patch_outputs) - logger.info("Patching titles %s: adding %s property at page level", RERO_2_3_TITLES, PROP_NAME) + logger.info( + "Patching titles %s: adding %s property at page level", + RERO_2_3_TITLES, + PROP_NAME, + ) logger.info("Input arguments: %s", arguments) - #empty_folder(temp_dir) + # empty_folder(temp_dir) logger.info("Fetching the page and issues files from S3...") # download the issues of interest for this patch - rero_issues, rero_pages = fetch_files('canonical-data', False, 'both', RERO_2_3_TITLES) - + rero_issues, rero_pages = fetch_files( + "canonical-data", False, "both", RERO_2_3_TITLES + ) + logger.info("Updating the issues files and uploading them to s3...") rero_patched_issues = ( - rero_issues - .map_partitions( - lambda yearly_issue: [add_ro_to_items(issue) for issue in yearly_issue] - ) - .map_partitions(title_year_pair_to_issues) - .map_partitions( - lambda issues: write_upload_issues( - issues[0], issues[1], - output_dir=temp_dir, - bucket_name=s3_output_bucket, - failed_log=error_log, - ) + rero_issues.map_partitions( + lambda yearly_issue: [add_ro_to_items(issue) for issue in yearly_issue] + ) + .map_partitions(title_year_pair_to_issues) + .map_partitions( + lambda issues: write_upload_issues( + issues[0], + issues[1], + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, ) + ) ).compute() - # free the memory allocated + # free the memory allocated del rero_issues logger.info("Uploading the page files to the new bucket") rero_page_files = ( - rero_pages - .map_partitions(lambda pages: [p for p in pages]) - .map_partitions(to_issue_id_pages_dict) - .map_partitions(lambda issue_to_pages: nzz_write_upload_pages( + rero_pages.map_partitions(lambda pages: [p for p in pages]) + .map_partitions(to_issue_id_pages_dict) + .map_partitions( + lambda issue_to_pages: nzz_write_upload_pages( issue_to_pages, output_dir=temp_dir, bucket_name=s3_output_bucket, @@ -130,11 +172,12 @@ def main(): "patched_fields": [PROP_NAME], "push_to_git": True, "file_extensions": "issues.jsonl.bz2", - "log_file": log_file, - "notes": "Patching RERO 2 & 3 data to add the reading order." + "log_file": log_file, + "notes": "Patching RERO 2 & 3 data to add the reading order.", } # create and upload the manifest create_manifest(manifest_config) + if __name__ == "__main__": main() diff --git a/text_importer/utils.py b/text_importer/utils.py index a3d688e1..3e09be36 100644 --- a/text_importer/utils.py +++ b/text_importer/utils.py @@ -1,14 +1,17 @@ +"""This module contains generic helper functions for the text-importer module. +""" + import json import logging import os import copy +from datetime import date +from typing import Any from contextlib import ExitStack import pathlib import importlib_resources import python_jsonschema_objects as pjs -from datetime import date -from typing import Any logger = logging.getLogger(__name__) @@ -80,7 +83,7 @@ def get_page_schema( """ file_manager = ExitStack() schema_path = get_pkg_resource(file_manager, schema_folder) - with open(os.path.join(schema_path), "r") as f: + with open(os.path.join(schema_path), "r", encoding="utf-8") as f: json_schema = json.load(f) builder = pjs.ObjectBuilder(json_schema) ns = builder.build_classes().NewspaperPage @@ -102,7 +105,7 @@ def get_issue_schema( """ file_manager = ExitStack() schema_path = get_pkg_resource(file_manager, schema_folder) - with open(os.path.join(schema_path), "r") as f: + with open(os.path.join(schema_path), "r", encoding="utf-8") as f: json_schema = json.load(f) builder = pjs.ObjectBuilder(json_schema) ns = builder.build_classes().NewspaperIssue @@ -111,13 +114,13 @@ def get_issue_schema( def get_access_right( - journal: str, date: date, access_rights: dict[str, dict[str, str]] + journal: str, _date: date, access_rights: dict[str, dict[str, str]] ) -> str: """Fetch the access rights for a specific journal and publication date. Args: journal (str): Journal name. - date (date): Publication date of the journal + _date (date): Publication date of the journal access_rights (dict[str, dict[str, str]]): Access rights for various journals. @@ -127,9 +130,11 @@ def get_access_right( rights = access_rights[journal] if rights["time"] == "all": return rights["access-right"].replace("-", "_") - else: - # TODO: this should rather be a custom exception - logger.warning(f"Access right not defined for {journal}-{date}") + + # TODO: this should rather be a custom exception + logger.warning("Access right not defined for %s-%s", journal, _date) + + return "undefined" def verify_imported_issues( @@ -147,9 +152,10 @@ def verify_imported_issues( actual_ids = set([i["m"]["id"] for i in actual_issue_json["i"]]) expected_ids = set([i["m"]["id"] for i in expected_issue_json["i"]]) logger.info( - f"[{actual_issue_json['id']}] " - f"Expected IDs: {len(expected_ids)}" - f"; actual IDs: {len(actual_ids)}" + "[%s] Expected IDs: %s; actual IDs: %s", + actual_issue_json["id"], + len(expected_ids), + len(actual_ids), ) assert expected_ids.difference(actual_ids) == set() @@ -173,8 +179,8 @@ def verify_imported_issues( assert actual_content_item["l"] == expected_content_item["l"] logger.info( - f"Content item {actual_content_item['m']['id']}" - "dit not change (legacy metadata are identical)" + "Content item %s did not change (legacy metadata are identical)", + actual_content_item["m"]["id"], ) From 8b414d8e763adb72efbf43ed1afe20dee86ffe96 Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 30 Apr 2024 12:00:46 +0200 Subject: [PATCH 48/53] clean and comment the patching scripts --- text_importer/importers/generic_importer.py | 163 ++++---- .../scripts/patching/canonical_patch_1_uzh.py | 367 +++++++++++------- .../patching/canonical_patch_5_rero.py | 24 +- .../patching/canonical_patch_7_find_issues.py | 70 ++-- .../patching/canonical_patch_7_rero_olive.py | 355 ++++++++++------- text_importer/utils.py | 109 +++++- 6 files changed, 667 insertions(+), 421 deletions(-) diff --git a/text_importer/importers/generic_importer.py b/text_importer/importers/generic_importer.py index 0fcc30ee..4267a193 100644 --- a/text_importer/importers/generic_importer.py +++ b/text_importer/importers/generic_importer.py @@ -32,15 +32,16 @@ import time from typing import Any, Type, Callable -from dask.distributed import Client, LocalCluster +from dask.distributed import Client from docopt import docopt import git -from impresso_commons.path.path_fs import (KNOWN_JOURNALS, - detect_canonical_issues, - IssueDir) +from impresso_commons.path.path_fs import ( + KNOWN_JOURNALS, + detect_canonical_issues, + IssueDir, +) from impresso_commons.versioning.data_manifest import DataManifest -from impresso_commons.versioning.helpers import DataStage from text_importer import __version__ @@ -83,7 +84,7 @@ def get_dask_client( ) -> Client: """Instantiate Dask client with given scheduler address or default values. - If a scheduler is given, a Dask scheduler and workers should have been + If a scheduler is given, a Dask scheduler and workers should have been previously launched in terminals separate to the one used for the importer. If no scheduler is given, the created Dask distributed cluster will have 8 workers, each with 2 threads. @@ -98,26 +99,24 @@ def get_dask_client( Client: A client connected to and allowing to manage the Dask cluster. """ if scheduler is None: - #cluster = LocalCluster(n_workers=32, memory_limit='auto', threads_per_worker=2) - #client = Client(cluster) + # cluster = LocalCluster(n_workers=32, memory_limit='auto', threads_per_worker=2) + # client = Client(cluster) if n_workers is not None: client = Client(n_workers=n_workers, threads_per_worker=2) else: client = Client(n_workers=24, threads_per_worker=2) else: client = Client(scheduler) - client.run( - init_logger, _logger=logger, log_level=log_level, log_file=log_file - ) + client.run(init_logger, _logger=logger, log_level=log_level, log_file=log_file) return client def apply_detect_func( - issue_class: Type[NewspaperIssue], - input_dir: str, - access_rights: str, - detect_func: Callable[[str, str], list[IssueDir]], - tmp_dir: str + issue_class: Type[NewspaperIssue], + input_dir: str, + access_rights: str, + detect_func: Callable[[str, str], list[IssueDir]], + tmp_dir: str, ) -> list[IssueDir]: """Apply the given `detect_func` function of the importer in use. @@ -133,20 +132,18 @@ def apply_detect_func( list[IssueDir]: List of detected issues for this importer. """ if issue_class is BlNewspaperIssue: - return detect_func( - input_dir, access_rights=access_rights, tmp_dir=tmp_dir - ) + return detect_func(input_dir, access_rights=access_rights, tmp_dir=tmp_dir) else: return detect_func(input_dir, access_rights=access_rights) def apply_select_func( - issue_class: Type[NewspaperIssue], - config: dict[str, Any], - input_dir: str, - access_rights: str, + issue_class: Type[NewspaperIssue], + config: dict[str, Any], + input_dir: str, + access_rights: str, select_func: Callable[[str, str, str], list[IssueDir]], - tmp_dir: str + tmp_dir: str, ) -> list[IssueDir]: """Apply the given `select_func` function of the importer in use. @@ -155,7 +152,7 @@ def apply_select_func( config (dict[str, Any]): Configuration to filter issues to import. input_dir (str): Directory containing this importer's newspaper data. access_rights (str): Path to the access rights file for this importer. - select_func (Callable[[str, str, str], list[IssueDir]]): Function + select_func (Callable[[str, str, str], list[IssueDir]]): Function detecting and selecting the issues to import using `config`. tmp_dir (str): Temporary directory used to unpack zip archives. @@ -166,14 +163,14 @@ def apply_select_func( return select_func( input_dir, config=config, access_rights=access_rights, tmp_dir=tmp_dir ) - else: - return select_func(input_dir, config=config, access_rights=access_rights) + + return select_func(input_dir, config=config, access_rights=access_rights) def main( issue_class: NewspaperIssue, - detect_func: Callable[[str, str], list[IssueDir]], - select_func: Callable[[str, str, str], list[IssueDir]] + detect_func: Callable[[str, str], list[IssueDir]], + select_func: Callable[[str, str, str], list[IssueDir]], ) -> None: """Import and convert newspapers to Impresso's format using CLI parameters. @@ -185,20 +182,20 @@ def main( issue_class (NewspaperIssue): Importer to use for the conversion detect_func (Callable[[str, str], list[IssueDir]]): `detect` function of the used importer. - select_func (Callable[[str, str, str], list[IssueDir]]): `select` + select_func (Callable[[str, str, str], list[IssueDir]]): `select` function of the used importer. """ - + # store CLI parameters args = docopt(__doc__) inp_dir = args["--input-dir"] outp_dir = args["--output-dir"] - temp_dir = args['--temp-dir'] + temp_dir = args["--temp-dir"] image_dirs = args["--image-dirs"] out_bucket = args["--s3-bucket"] log_file = args["--log-file"] - access_rights_file = args['--access-rights'] - chunk_size = args['--chunk-size'] + access_rights_file = args["--access-rights"] + chunk_size = args["--chunk-size"] scheduler = args["--scheduler"] clear_output = args["--clear"] incremental_output = args["--incremental"] @@ -207,73 +204,91 @@ def main( config_file = args["--config-file"] repo_path = args["--git-repo"] num_workers = args["--num-workers"] - + if print_version: - print(f'impresso-txt-importer v{__version__}') + print(f"impresso-txt-importer v{__version__}") return - + init_logger(logger, log_level, log_file) - logger.info("CLI arguments received: {}".format(args)) - + logger.info("CLI arguments received: %s", args) + # start the dask local cluster client = get_dask_client(scheduler, log_file, log_level, int(num_workers)) - - logger.info(f"Dask cluster: {client}") - + + logger.info("Dask cluster: %s", client) + # Checks if out dir exists (Creates it if not) and if should empty it - clear_output_dir(outp_dir, clear_output) - + clear_output_dir(outp_dir, clear_output) + # detect/select issues if config_file and os.path.isfile(config_file): - logger.info(f"Found config file: {os.path.realpath(config_file)}") - with open(config_file, 'r') as f: + logger.info("Found config file: %s", os.path.realpath(config_file)) + with open(config_file, "r", encoding="utf-8") as f: config = json.load(f) - issues = apply_select_func(issue_class, config, input_dir=inp_dir, - access_rights=access_rights_file, - select_func=select_func, tmp_dir=temp_dir) + issues = apply_select_func( + issue_class, + config, + input_dir=inp_dir, + access_rights=access_rights_file, + select_func=select_func, + tmp_dir=temp_dir, + ) logger.info( - f"{len(issues)} newspaper remained after applying filter: {issues}" + "%s newspaper remained after applying filter: %s", len(issues), issues ) else: logger.info("No config file found.") - issues = apply_detect_func(issue_class, inp_dir, access_rights_file, - detect_func=detect_func, tmp_dir=temp_dir) - logger.info(f'{len(issues)} newspaper issues detected') - + issues = apply_detect_func( + issue_class, + inp_dir, + access_rights_file, + detect_func=detect_func, + tmp_dir=temp_dir, + ) + logger.info("%s newspaper issues detected", len(issues)) + if outp_dir is not None and os.path.exists(outp_dir) and incremental_output: issues_to_skip = [ (issue.journal, issue.date, issue.edition) for issue in detect_canonical_issues(outp_dir, KNOWN_JOURNALS) ] - logger.debug(f"Issues to skip: {issues_to_skip}") - logger.info(f"{len(issues_to_skip)} issues to skip") + logger.debug("Issues to skip: %s", issues_to_skip) + logger.info("%s issues to skip", len(issues_to_skip)) issues = list( filter( - lambda x: (x.journal, x.date, x.edition) not in issues_to_skip, - issues + lambda x: (x.journal, x.date, x.edition) not in issues_to_skip, issues ) ) - logger.debug(f"Remaining issues: {issues}") - logger.info(f"{len(issues)} remaining issues") - - logger.debug("Following issues will be imported:{}".format(issues)) - + logger.debug("Remaining issues: %s", issues) + logger.info("%s remaining issues", len(issues)) + + logger.debug("Following issues will be imported: %s", issues) + assert outp_dir is not None or out_bucket is not None # ititialize manifest + if config_file: + newspapers = f" for titles {list(config['newspapers'].keys())}" + else: + newspapers = "" + manifest_note = f"Ingestion of {len(issues)} Newspaper issues into canonical format{newspapers}." + manifest = DataManifest( - data_stage='canonical', + data_stage="canonical", s3_output_bucket=out_bucket, git_repo=git.Repo(repo_path), - temp_dir=temp_dir + temp_dir=temp_dir, + notes=manifest_note, ) - if config_file: - newspapers = f" for titles {list(config['newspapers'].keys())}" - else: - newspapers = '' - manifest_note = f'Ingestion of {len(issues)} Newspaper issues into canonical format{newspapers}.' - manifest.append_to_notes(manifest_note) - - import_issues(issues, outp_dir, out_bucket, issue_class, - image_dirs, temp_dir, chunk_size, manifest, client) + import_issues( + issues, + outp_dir, + out_bucket, + issue_class, + image_dirs, + temp_dir, + chunk_size, + manifest, + client, + ) diff --git a/text_importer/scripts/patching/canonical_patch_1_uzh.py b/text_importer/scripts/patching/canonical_patch_1_uzh.py index 9ac43966..5ac6a4f9 100644 --- a/text_importer/scripts/patching/canonical_patch_1_uzh.py +++ b/text_importer/scripts/patching/canonical_patch_1_uzh.py @@ -16,40 +16,56 @@ """ import os -import boto3 -import json import logging +import copy +import shutil +from typing import Any, Callable +import git import jsonlines +from smart_open import open as smart_open_function +import dask.bag as db +from filelock import FileLock +from docopt import docopt + from impresso_commons.utils import s3 -from impresso_commons.path.path_s3 import fetch_files, list_files, list_newspapers -from impresso_commons.utils.s3 import fixed_s3fs_glob +from impresso_commons.path.path_s3 import fetch_files from impresso_commons.versioning.data_manifest import DataManifest from text_importer.importers.core import upload_issues, upload_pages, remove_filelocks -from smart_open import open as smart_open_function -from impresso_commons.versioning.helpers import counts_for_canonical_issue -import dask.bag as db -from typing import Any, Callable -import git from text_importer.utils import init_logger -import copy -from dask.distributed import Client -from docopt import docopt -from collections import defaultdict -import shutil -from filelock import FileLock IMPRESSO_STORAGEOPT = s3.get_storage_options() -UZH_TITLES = ['FedGazDe', 'FedGazFr', 'NZZ'] IMPRESSO_IIIF_BASE_URI = "https://impresso-project.ch/api/proxy/iiif/" -PROP_NAME = 'iiif_img_base_uri' logger = logging.getLogger() -def add_property(object_dict: dict[str, Any], prop_name: str, prop_function: Callable[[str], str], function_input: str): + +def add_property( + object_dict: dict[str, Any], + prop_name: str, + prop_function: Callable[[str], str], + function_input: str, +) -> dict[str, Any]: + """Add a property and value to a given object dict computed with a given function. + + Args: + object_dict (dict[str, Any]): Object to which the property is added. + prop_name (str): Name of the property to add. + prop_function (Callable[[str], str]): Function computing the property value. + function_input (str): Input to `prop_function` for this object. + + Returns: + dict[str, Any]: Updated object. + """ object_dict[prop_name] = prop_function(function_input) - logger.debug("%s -> Added property %s: %s", object_dict['id'], prop_name, object_dict[prop_name]) + logger.debug( + "%s -> Added property %s: %s", + object_dict["id"], + prop_name, + object_dict[prop_name], + ) return object_dict + def empty_folder(dir_path: str) -> None: """Empty a directoy given its path if it exists. @@ -63,10 +79,7 @@ def empty_folder(dir_path: str) -> None: def write_error( - thing_id: str, - origin_function: str, - error: Exception, - failed_log: str + thing_id: str, origin_function: str, error: Exception, failed_log: str ) -> None: """Write the given error of a failed import to the `failed_log` file. @@ -79,34 +92,52 @@ def write_error( error (Exception): Error that occurred and should be logged. failed_log (str): Path to log file for failed imports. """ - note = ( - f"Error in {origin_function} for {thing_id}: {error}" - ) + note = f"Error in {origin_function} for {thing_id}: {error}" logger.exception(note) - with open(failed_log, "a+") as f: + with open(failed_log, "a+", encoding="utf-8") as f: f.write(note + "\n") -def write_jsonlines_file(filepath: str, contents: str | list[str], content_type: str, failed_log: str | None = None) -> None: - - os.makedirs(os.path.dirname(filepath), exist_ok =True) + +def write_jsonlines_file( + filepath: str, + contents: str | list[str], + content_type: str, + failed_log: str | None = None, +) -> None: + """Write the given contents to a JSONL file given its path. + + Filelocks are used here to prevent concurrent writing to the files. + + Args: + filepath (str): Path to the JSONL file to write to. + contents (str | list[str]): Dump contents to write to the file. + content_type (str): Type of content that is being written to the file. + failed_log (str | None, optional): Path to a log to keep track of failed + operations. Defaults to None. + """ + os.makedirs(os.path.dirname(filepath), exist_ok=True) # put a file lock to avoid the overwriting of files due to parallelization lock = FileLock(filepath + ".lock", timeout=13) try: with lock: - with smart_open_function(filepath, 'ab') as fout: + with smart_open_function(filepath, "ab") as fout: writer = jsonlines.Writer(fout) writer.write_all(contents) - logger.info(f'Written {len(contents)} {content_type} to {filepath}') + logger.info( + "Written %s %s to %s", len(contents), content_type, filepath + ) writer.close() except Exception as e: - logger.error(f"Error for {filepath}") + logger.error("Error for %s", filepath) logger.exception(e) if failed_log is not None: - write_error(os.path.basename(filepath), 'write_jsonlines_file()', e, failed_log) + write_error( + os.path.basename(filepath), "write_jsonlines_file()", e, failed_log + ) def write_upload_issues( @@ -114,7 +145,7 @@ def write_upload_issues( issues: list[dict[str, Any]], output_dir: str, bucket_name: str, - failed_log: str | None = None + failed_log: str | None = None, ) -> tuple[bool, str]: """Compress issues for a Journal-year in a json file and upload them to s3. @@ -130,22 +161,23 @@ def write_upload_issues( Defaults to None. Returns: - tuple[bool, str]: Whether the upload was successful and the path to the + tuple[bool, str]: Whether the upload was successful and the path to the uploaded file. """ newspaper, year = key - filename = f'{newspaper}-{year}-issues.jsonl.bz2' + filename = f"{newspaper}-{year}-issues.jsonl.bz2" filepath = os.path.join(output_dir, newspaper, filename) - logger.info(f'Compressing {len(issues)} JSON files into {filepath}') + logger.info("Compressing %s JSON files into %s", len(issues), filepath) if os.path.exists(filepath) and os.path.isfile(filepath): # file shsould only be modified once logger.warning("The file %s already exists, not modifying it.", filepath) return False, filepath - write_jsonlines_file(filepath, issues, 'issues', failed_log) + write_jsonlines_file(filepath, issues, "issues", failed_log) + + return upload_issues("-".join(key), filepath, bucket_name) - return upload_issues('-'.join(key), filepath, bucket_name) def write_upload_pages( key: str, @@ -153,7 +185,7 @@ def write_upload_pages( output_dir: str, bucket_name: str, failed_log: str | None = None, - #uploaded_pages = UPLOADED_PAGES, + # uploaded_pages = UPLOADED_PAGES, ) -> tuple[str, tuple[bool, str]]: """Compress pages for a given edition in a json file and upload them to s3. @@ -167,24 +199,25 @@ def write_upload_pages( bucket_name (str): Name of S3 bucket where to upload the file. Returns: - Tuple[str, str]: Label following the template `-` and + Tuple[str, str]: Label following the template `-` and the path to the the compressed `.bz2` file. """ - newspaper, year, month, day, edition = key.split('-') - filename = f'{key}-pages.jsonl.bz2' - filepath = os.path.join(output_dir, newspaper, f'{newspaper}-{year}', filename) - logger.info(f'Compressing {len(pages)} JSON files into {filepath}') - + newspaper, year, _, _, _ = key.split("-") + filename = f"{key}-pages.jsonl.bz2" + filepath = os.path.join(output_dir, newspaper, f"{newspaper}-{year}", filename) + logger.info("Compressing %s JSON files into %s", len(pages), filepath) + if os.path.exists(filepath) and os.path.isfile(filepath): # file shsould only be modified once logger.info("The file %s already exists, not modifying it.", filepath) return key, (False, filepath) - + logger.info("uploading pages for %s", key) - write_jsonlines_file(filepath, pages, 'pages', failed_log) + write_jsonlines_file(filepath, pages, "pages", failed_log) return key, (upload_pages(key, filepath, bucket_name)) + # adapted from https://github.com/impresso/impresso-data-sanitycheck/blob/master/sanity_check/contents/stats.py#L241 def canonical_stats_from_issue_bag(fetched_issues: db.core.Bag) -> list[dict[str, Any]]: """Computes number of issues and pages per newspaper from canonical data in s3. @@ -197,107 +230,150 @@ def canonical_stats_from_issue_bag(fetched_issues: db.core.Bag) -> list[dict[str pages_count_df = ( fetched_issues.map( lambda i: { - "np_id": i["id"].split('-')[0], - "year":i["id"].split('-')[1], - "id": i['id'], - "issue_id": i['id'], - "n_pages": len(set(i['pp'])), - "n_content_items": len(i['i']), - "n_images": len([item for item in i['i'] if item['m']['tp']=='image']) + "np_id": i["id"].split("-")[0], + "year": i["id"].split("-")[1], + "id": i["id"], + "issue_id": i["id"], + "n_pages": len(set(i["pp"])), + "n_content_items": len(i["i"]), + "n_images": len( + [item for item in i["i"] if item["m"]["tp"] == "image"] + ), + } + ) + .to_dataframe( + meta={ + "np_id": str, + "year": str, + "id": str, + "issue_id": str, + "n_pages": int, + "n_images": int, + "n_content_items": int, } ) - .to_dataframe(meta={'np_id': str, 'year': str, - 'id': str, 'issue_id': str, - "n_pages": int, 'n_images': int, - 'n_content_items': int}) - .set_index('id') + .set_index("id") .persist() ) # cum the counts for all values collected - aggregated_df = (pages_count_df - .groupby(by=['np_id', 'year']) - .agg({"n_pages": sum, 'issue_id': 'count', 'n_content_items': sum, 'n_images': sum}) - .rename(columns={'issue_id': 'issues', 'n_pages': 'pages', - 'n_content_items': 'content_items_out', 'n_images':'images'}) - .reset_index() + aggregated_df = ( + pages_count_df.groupby(by=["np_id", "year"]) + .agg( + { + "n_pages": sum, + "issue_id": "count", + "n_content_items": sum, + "n_images": sum, + } + ) + .rename( + columns={ + "issue_id": "issues", + "n_pages": "pages", + "n_content_items": "content_items_out", + "n_images": "images", + } + ) + .reset_index() ) # return as a list of dicts - return aggregated_df.to_bag(format='dict').compute() + return aggregated_df.to_bag(format="dict").compute() + # define patch function -def uzh_image_base_uri(page_id: str, impresso_iiif: str = IMPRESSO_IIIF_BASE_URI) -> str: +def uzh_image_base_uri( + page_id: str, impresso_iiif: str = IMPRESSO_IIIF_BASE_URI +) -> str: """ https://impresso-project.ch/api/proxy/iiif/[page canonical ID] """ return os.path.join(impresso_iiif, page_id) -def to_issue_id_pages_pairs(pages: list[dict[str, Any]]) -> tuple[str, list[dict[str, Any]]]: +def to_issue_id_pages_pairs( + pages: list[dict[str, Any]] +) -> tuple[str, list[dict[str, Any]]]: issues_present = set() for page in pages: - issue_id = '-'.join(page['id'].split('-')[:-1]) - issues_present.add(issue_id) + issue_id = "-".join(page["id"].split("-")[:-1]) + issues_present.add(issue_id) issues = list(issues_present) - if len(issues)!=1: - logger.warning("Did not find exactly one issue in the pages: %s, %s", issues, [p['id'] for p in pages]) - if len(issues)==0: - return '', pages - assert len(issues)<=1, "there should only be one issue" + if len(issues) != 1: + logger.warning( + "Did not find exactly one issue in the pages: %s, %s", + issues, + [p["id"] for p in pages], + ) + if len(issues) == 0: + return "", pages + assert len(issues) <= 1, "there should only be one issue" return issues[0], pages -def to_issue_id_pages_dict(pages: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]: +def to_issue_id_pages_dict( + pages: list[dict[str, Any]] +) -> dict[str, list[dict[str, Any]]]: issues_present = {} for page in pages: - issue_id = '-'.join(page['id'].split('-')[:-1]) + issue_id = "-".join(page["id"].split("-")[:-1]) if issue_id not in issues_present: issues_present[issue_id] = [page] else: issues_present[issue_id].append(page) - if len(issues_present)!=1: - logger.warning("Did not find exactly one issue in the pages; issue(s): %s", issues_present) - print("Did not find exactly one issue in the pages; issue(s): %s", issues_present.keys()) - if len(issues_present) >1: - pairs = [((k, [p['id'] for p in ps]) for k,ps in issues_present)] + if len(issues_present) != 1: + logger.warning( + "Did not find exactly one issue in the pages; issue(s): %s", issues_present + ) + print( + "Did not find exactly one issue in the pages; issue(s): %s", + issues_present.keys(), + ) + if len(issues_present) > 1: + pairs = [((k, [p["id"] for p in ps]) for k, ps in issues_present)] print(f"Here are the specific contents of the pages: {pairs}") return issues_present -def title_year_pair_to_issues(issues: list[dict[str, Any]]) -> tuple[tuple[str, str], list[dict[str, Any]]]: + +def title_year_pair_to_issues( + issues: list[dict[str, Any]] +) -> tuple[tuple[str, str], list[dict[str, Any]]]: keys_present = set() for issue in issues: - title, year = issue['id'].split('-')[:2] - keys_present.add((title, year)) + title, year = issue["id"].split("-")[:2] + keys_present.add((title, year)) keys = list(keys_present) - if len(keys)!=1: + if len(keys) != 1: logger.warning("Did not find exactly one key. Keys: %s", keys) - if len(keys)==0: - return '', issues - assert len(keys)>=1, "there should only be one key" + if len(keys) == 0: + return "", issues + assert len(keys) >= 1, "there should only be one key" return keys[0], issues def nzz_write_upload_pages( - issues_to_pages: dict[str, list[dict[str, Any]]], + issues_to_pages: dict[str, list[dict[str, Any]]], output_dir: str, bucket_name: str, failed_log: str | None = None, ) -> list[str, tuple[bool, str]]: if len(issues_to_pages) == 0: - return '', (False, '') - + return "", (False, "") + upload_results = [] for issue_id, pages in issues_to_pages.items(): - upload_results.append(write_upload_pages(issue_id, pages, output_dir, bucket_name, failed_log)) + upload_results.append( + write_upload_pages(issue_id, pages, output_dir, bucket_name, failed_log) + ) return upload_results @@ -314,36 +390,37 @@ def main(): patch_outputs = arguments["--patch-outputs-filename"] # initialize values for patch - UZH_TITLES = ['NZZ'] - IMPRESSO_IIIF_BASE_URI = "https://impresso-project.ch/api/proxy/iiif/" - PROP_NAME = 'iiif_img_base_uri' - patched_fields=[PROP_NAME] + UZH_TITLES = ["NZZ"] + PROP_NAME = "iiif_img_base_uri" + patched_fields = [PROP_NAME] canonical_repo = git.Repo(canonical_repo_path) - schema_path = f'{canonical_repo_path}/text_importer/impresso-schemas/json/versioning/manifest.schema.json' + schema_path = f"{canonical_repo_path}/text_importer/impresso-schemas/json/versioning/manifest.schema.json" final_patches_output_path = os.path.join(os.path.dirname(log_file), patch_outputs) init_logger(logger, logging.INFO, log_file) - logger.info("Patching titles %s: adding %s property at page level", UZH_TITLES, PROP_NAME) + logger.info( + "Patching titles %s: adding %s property at page level", UZH_TITLES, PROP_NAME + ) # empty the temp folder before starting processing to prevent duplication of content inside the files. empty_folder(temp_dir) # initialise manifest to keep track of updates nzz_patch_1_manifest = DataManifest( - data_stage = 'canonical', - s3_output_bucket = s3_output_bucket, - s3_input_bucket = s3_input_bucket, - git_repo = canonical_repo, - temp_dir = temp_dir, + data_stage="canonical", + s3_output_bucket=s3_output_bucket, + s3_input_bucket=s3_input_bucket, + git_repo=canonical_repo, + temp_dir=temp_dir, patched_fields=patched_fields, - previous_mft_path = previous_manifest_path + previous_mft_path=previous_manifest_path, ) logger.info("Fetching the page and issues files from S3...") # download the issues of interest for this patch - nzz_issues, nzz_pages = fetch_files('canonical-data', False, 'both', UZH_TITLES) - + nzz_issues, nzz_pages = fetch_files("canonical-data", False, "both", UZH_TITLES) + # compute the statistics that correspond to this logger.info("Computing the canonical statistics on the issues...") nzz_stats_from_issues = canonical_stats_from_issue_bag(nzz_issues) @@ -351,37 +428,40 @@ def main(): logger.info("Updating the page files and uploading them to s3...") # patch the pages and write them back to s3. nzz_patched_pages = ( - nzz_pages - .map_partitions( - lambda pages: [add_property(p, PROP_NAME, uzh_image_base_uri, p['id']) for p in pages] - ) - .map_partitions(to_issue_id_pages_dict) - .map_partitions( - lambda issue_to_pages: nzz_write_upload_pages( - issue_to_pages, - output_dir=temp_dir, - bucket_name=s3_output_bucket, - failed_log=error_log, - ) + nzz_pages.map_partitions( + lambda pages: [ + add_property(p, PROP_NAME, uzh_image_base_uri, p["id"]) for p in pages + ] + ) + .map_partitions(to_issue_id_pages_dict) + .map_partitions( + lambda issue_to_pages: nzz_write_upload_pages( + issue_to_pages, + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, ) - .flatten() + ) + .flatten() ).compute() - # free the memory allocated + # free the memory allocated del nzz_pages logger.info("Done uploading the page files to s3, filling in the manifest...") - issue_stats = copy.deepcopy(nzz_stats_from_issues) - # keep track of the issues, default dict allows to prevent checking if key exists in it - issues_with_patched_pages = defaultdict(list) - # fill in the manifest statistics and prepare issues to be uploaded to their new s3 bucket. - logger.info(f"nzz_patched_pages[0], nzz_patched_pages[1]: {nzz_patched_pages[0]}, {nzz_patched_pages[1]}") - for issue_id, (success, path) in zip(nzz_patched_pages[::2], nzz_patched_pages[1::2]): - title, year, month, day, edition = issue_id.split('-') + logger.info( + "nzz_patched_pages[0], nzz_patched_pages[1]: %s, %s", + nzz_patched_pages[0], + nzz_patched_pages[1], + ) + for issue_id, (success, path) in zip( + nzz_patched_pages[::2], nzz_patched_pages[1::2] + ): + title, year, _, _, _ = issue_id.split("-") # write to file to track potential missing data. with open(final_patches_output_path, "a", encoding="utf-8") as outfile: @@ -390,12 +470,14 @@ def main(): if success: if not nzz_patch_1_manifest.has_title_year_key(title, year): logger.info("Adding stats for %s-%s to manifest", title, year) - current_stats = [d for d in issue_stats if d['np_id']==title and d['year']==year][0] + current_stats = [ + d for d in issue_stats if d["np_id"] == title and d["year"] == year + ][0] # reduce the number of stats to consider at each step issue_stats.remove(current_stats) # remove unwanted keys from the dict - del current_stats['np_id'] - del current_stats['year'] + del current_stats["np_id"] + del current_stats["year"] nzz_patch_1_manifest.replace_by_title_year(title, year, current_stats) @@ -403,16 +485,19 @@ def main(): remove_filelocks(os.path.join(temp_dir, title, f"{title}-{year}")) elif not success: - logger.warning("The pages for issue %s were not correctly uploaded", issue_id) + logger.warning( + "The pages for issue %s were not correctly uploaded", issue_id + ) logger.info("Uploading the issue files to the new bucket") # write and upload the issues to the new s3 bucket yearly_issue_files = ( - nzz_issues - .map_partitions(lambda issues: [i for i in issues]) - .map_partitions(title_year_pair_to_issues) - .map_partitions(lambda issues: write_upload_issues( - issues[0], issues[1], + nzz_issues.map_partitions(lambda issues: [i for i in issues]) + .map_partitions(title_year_pair_to_issues) + .map_partitions( + lambda issues: write_upload_issues( + issues[0], + issues[1], output_dir=temp_dir, bucket_name=s3_output_bucket, failed_log=error_log, @@ -424,8 +509,8 @@ def main(): # finalize the manifest and export it note = f"Patching titles {UZH_TITLES}: adding {PROP_NAME} property at page level" nzz_patch_1_manifest.append_to_notes(note) - nzz_patch_1_manifest.compute(export_to_git_and_s3 = False) - nzz_patch_1_manifest.validate_and_export_manifest(path_to_schema=schema_path, push_to_git=True) + nzz_patch_1_manifest.compute(export_to_git_and_s3=False) + nzz_patch_1_manifest.validate_and_export_manifest(push_to_git=True) if __name__ == "__main__": diff --git a/text_importer/scripts/patching/canonical_patch_5_rero.py b/text_importer/scripts/patching/canonical_patch_5_rero.py index 7de45719..ddae0a33 100644 --- a/text_importer/scripts/patching/canonical_patch_5_rero.py +++ b/text_importer/scripts/patching/canonical_patch_5_rero.py @@ -16,14 +16,13 @@ import os import logging -import copy from typing import Any from docopt import docopt from impresso_commons.utils import s3 from impresso_commons.path.path_s3 import fetch_files from impresso_commons.versioning.compute_manifest import create_manifest -from text_importer.utils import init_logger +from text_importer.utils import init_logger, get_reading_order from text_importer.scripts.patching.canonical_patch_1_uzh import ( title_year_pair_to_issues, write_upload_issues, @@ -35,27 +34,6 @@ logger = logging.getLogger() -def get_reading_order(items: list[dict[str, Any]]) -> dict[str, int]: - """Generate a reading order for items based on their id and the pages they span. - - This reading order can be used to display the content items properly in a table - of contents without skipping form page to page. - - Args: - items (list[dict[str, Any]]): List of items to reorder for the ToC. - - Returns: - dict[str, int]: A dictionary mapping item IDs to their reading order. - """ - items_copy = copy.deepcopy(items) - ids_and_pages = [(i["m"]["id"], i["m"]["pp"]) for i in items_copy] - sorted_ids = sorted( - sorted(ids_and_pages, key=lambda x: int(x[0].split("-i")[-1])), - key=lambda x: x[1], - ) - return {t[0]: index + 1 for index, t in enumerate(sorted_ids)} - - def add_ro_to_items(issue: dict[str, Any]) -> list[dict[str, Any]]: reading_order_dict = get_reading_order(issue["i"]) for ci in issue["i"]: diff --git a/text_importer/scripts/patching/canonical_patch_7_find_issues.py b/text_importer/scripts/patching/canonical_patch_7_find_issues.py index 460d4860..67ee7362 100644 --- a/text_importer/scripts/patching/canonical_patch_7_find_issues.py +++ b/text_importer/scripts/patching/canonical_patch_7_find_issues.py @@ -14,8 +14,6 @@ import os import json import logging -import shutil -from typing import Any, Callable from zipfile import ZipFile, BadZipFile from docopt import docopt @@ -30,40 +28,18 @@ logger = logging.getLogger() -def empty_folder(dir_path: str) -> None: - """Empty a directoy given its path if it exists. - - Args: - dir_path (str): Path to the directory to empty. - """ - if os.path.exists(dir_path): - shutil.rmtree(dir_path) - logger.info("Emptied directory at %s", dir_path) - os.mkdir(dir_path) - - -def write_error( - thing_id: str, origin_function: str, error: Exception, failed_log: str -) -> None: - """Write the given error of a failed import to the `failed_log` file. +def extract_zip_contents(zip_path: str) -> tuple[list[str], list[str]]: + """Extract the filenames contained in a Olive data `Document.zip` archive. - Adapted from `impresso-text-acquisition/text_importer/importers/core.py` to allow - using a issue or page id, and provide the function in which the error took place. + In particular, return the files names of the images contained in the archive + and identify the ones which are likely to contain the resolution in their name. Args: - thing_id (str): Canonical ID of the object/file for which the error occurred. - origin_function (str): Function in which the exception occured. - error (Exception): Error that occurred and should be logged. - failed_log (str): Path to log file for failed imports. - """ - note = f"Error in {origin_function} for {thing_id}: {error}" - logger.exception(note) - with open(failed_log, "a+", encoding="utf-8") as f: - f.write(note + "\n") - - -def extract_zip_contents(zip_path: str) -> tuple[list[str], list[str]]: + zip_path (str): Path to an issue's `Document.zip` archive. + Returns: + tuple[list[str], list[str]]: Filenames of images and ones with the resolution. + """ zip_contents = ZipFile(zip_path).namelist() pg_res_files = [f for f in zip_contents if "Img" in f and "Pg" in f] @@ -85,8 +61,36 @@ def load_json(f_path: str) -> dict: return file -def fetch_needed_info_for_title(title, og_data_path, img_data_path, out_path): +def fetch_needed_info_for_title( + title: str, og_data_path: str, img_data_path: str, out_path: str +) -> tuple[dict, list, list]: + """For each title in the RERO 1 (Olive) collection, fetch information needed. + + The information fetched is necessary to perform the March 2024 patch 7, rescaling + the coordinates of certain titles. + For each issue, the step to get the information required are the following: + - Identify if an `image-info.json` is available along with its image files converted + to jp2, in the `img_data_path` directory. + - If the file exists and is not empty, keep track of the information within it, + in particular the source file used and strategy used to perform the conversion. + - If the file does not exist, take note of the issue. + - Look at the contents of the issue's `Document.zip` archive in the `og_data_path`. + - Identify what image files were present within the original data, and potentially + their resolutions. Keep this information for each issue + The fetched information then allows to identify which issues need a rescaling of + their coordinates. + + Args: + title (str): Alias of the newspaper title for which to fetch information. + og_data_path (str): Path to the directory containing all the original data files. + img_data_path (str): Path to the directory containing all converted images. + out_path (str): Path where to write the fetched information every 500 issues. + + Returns: + tuple[dict, list, list]: Dict of fetched information, and lists of issues with + missing `image-info` files and more than 1 respectively. + """ # resume a listing in the middle if os.path.exists(out_path): title_info = load_json(out_path) diff --git a/text_importer/scripts/patching/canonical_patch_7_rero_olive.py b/text_importer/scripts/patching/canonical_patch_7_rero_olive.py index 3d8d19ab..0e7678d6 100644 --- a/text_importer/scripts/patching/canonical_patch_7_rero_olive.py +++ b/text_importer/scripts/patching/canonical_patch_7_rero_olive.py @@ -16,139 +16,168 @@ import os import json import logging + +from typing import Any +from docopt import docopt from impresso_commons.utils import s3 from impresso_commons.path.path_s3 import fetch_files -from impresso_commons.versioning.data_manifest import DataManifest from impresso_commons.versioning.compute_manifest import create_manifest -import dask.bag as db -from typing import Any, Callable -from text_importer.utils import init_logger -import copy -from docopt import docopt -import shutil -from text_importer.scripts.patching.canonical_patch_1_uzh import (write_jsonlines_file, - title_year_pair_to_issues, - write_upload_issues, - to_issue_id_pages_dict, - nzz_write_upload_pages) +from text_importer.utils import init_logger, empty_folder +from text_importer.scripts.patching.canonical_patch_1_uzh import ( + title_year_pair_to_issues, + write_upload_issues, + to_issue_id_pages_dict, + nzz_write_upload_pages, +) IMPRESSO_STORAGEOPT = s3.get_storage_options() logger = logging.getLogger() -def empty_folder(dir_path: str) -> None: - """Empty a directoy given its path if it exists. + +def scale_coords( + coords: list[int], curr_res: str | int, des_res: str | int +) -> list[int]: + """Rescale the given coordinates based on the current and destination resolutions. Args: - dir_path (str): Path to the directory to empty. + coords (list[int]): Current coordinates to rescale. + curr_res (str | int): Current resolution. + des_res (str | int): Destination resolution. + + Returns: + list[int]: Rescaled coordinates. """ - if os.path.exists(dir_path): - shutil.rmtree(dir_path) - logger.info("Emptied directory at %s", dir_path) - os.mkdir(dir_path) + return [int(c * int(des_res) / int(curr_res)) for c in coords] -def write_error( - thing_id: str, origin_function: str, error: Exception, failed_log: str -) -> None: - """Write the given error of a failed import to the `failed_log` file. - Adapted from `impresso-text-acquisition/text_importer/importers/core.py` to allow - using a issue or page id, and provide the function in which the error took place. +def convert_issue_coords( + issue: dict[str, Any], res: dict[str, int | list] +) -> tuple[dict[str, Any], bool]: + """Convert the coordinates present in 1 issue if they are present - Args: - thing_id (str): Canonical ID of the object/file for which the error occurred. - origin_function (str): Function in which the exception occured. - error (Exception): Error that occurred and should be logged. - failed_log (str): Path to log file for failed imports. - """ - note = f"Error in {origin_function} for {thing_id}: {error}" - logger.exception(note) - with open(failed_log, "a+") as f: - f.write(note + "\n") + Typically, coordinates are present in an issue if it contains content-items of type + "image", for which the coordiantes are stored. + Args: + issue (dict[str, Any]): JSON Canonical reprensetation of an issue. + res (dict[str, int | list]): Resolutions according to which to rescale. -def scale_coords(coords: list[int], curr_res: str | int, des_res: str | int) -> list[int]: - return [int(c*int(des_res)/int(curr_res)) for c in coords] - -def convert_issue_coords(issue: dict[str, Any], res: dict[str, int | list]) -> tuple[dict[str, Any], bool]: - # Convert the coordinates present in 1 issue if they are present + Returns: + tuple[dict[str, Any], bool]: Issue after operation and whether or not some + coordinates were indeed rescaled. + """ scaled = False - for i in issue['i']: - if 'c' in i['m']: - i['m']['c'] = scale_coords(i['m']['c'], res['curr_res'], res['dest_res']) + for i in issue["i"]: + if "c" in i["m"]: + i["m"]["c"] = scale_coords(i["m"]["c"], res["curr_res"], res["dest_res"]) scaled = True - elif 'c' in i: - i['c'] = scale_coords(i['c'], res['curr_res'], res['dest_res']) + elif "c" in i: + i["c"] = scale_coords(i["c"], res["curr_res"], res["dest_res"]) scaled = True - elif 'iiif_link' in i['m'] or 'iiif_link' in i: - iiif = i['m']['iiif_link'] if 'iiif_link' in i['m'] else i['iiif_link'] - logger.warning("%s: No coordinates but a IIIF link for item %s: %s", issue['id'], i['m']['id'], iiif) + elif "iiif_link" in i["m"] or "iiif_link" in i: + iiif = i["m"]["iiif_link"] if "iiif_link" in i["m"] else i["iiif_link"] + logger.warning( + "%s: No coordinates but a IIIF link for item %s: %s", + issue["id"], + i["m"]["id"], + iiif, + ) # return the issue as-is once it's been scaled return issue, scaled -def convert_page_coords(page: dict[str, Any], res: dict[str, int | list]) -> tuple[dict[str, Any], bool]: + +def convert_page_coords( + page: dict[str, Any], res: dict[str, int | list] +) -> tuple[dict[str, Any], bool]: + """Rescale the coordinates within a canonical page representation. + + Args: + page (dict[str, Any]): JSON Canonical reprensetation of a Page. + res (dict[str, int | list]): Resolutions according to which to rescale. + + Returns: + tuple[dict[str, Any], bool]: Page with rescaled coordinates and sanity check. + """ # Convert the coordinates present in 1 page scaled = 0 # count the expected number of coordinates to rescale on page - coords_count = len(page['r']) - for region in page['r']: - region['c'] = scale_coords(region['c'], res['curr_res'], res['dest_res']) + coords_count = len(page["r"]) + for region in page["r"]: + region["c"] = scale_coords(region["c"], res["curr_res"], res["dest_res"]) scaled += 1 for para in region["p"]: coords_count += len(para["l"]) for line in para["l"]: - line['c'] = scale_coords(line['c'], res['curr_res'], res['dest_res']) + line["c"] = scale_coords(line["c"], res["curr_res"], res["dest_res"]) scaled += 1 coords_count += len(line["t"]) - for token in line['t']: - token['c'] = scale_coords(token['c'], res['curr_res'], res['dest_res']) + for token in line["t"]: + token["c"] = scale_coords( + token["c"], res["curr_res"], res["dest_res"] + ) scaled += 1 - return page, scaled==coords_count + return page, scaled == coords_count def find_convert_coords( - elem: dict[str, Any], - title: str, - to_patch: dict[str, dict], - to_inv: dict[str, dict], - is_issue: bool = True + elem: dict[str, Any], + title: str, + to_patch: dict[str, dict], + to_inv: dict[str, dict], + is_issue: bool = True, ) -> tuple[dict[str, Any], dict[str, Any]]: + """Find and converts all coordinates within the dict object given. + + The object is the canonical dict representation of either a Page or an Issue. + + Args: + elem (dict[str, Any]): Page or Issue in which coordinates should be rescaled. + title (str): Newspaper title of the page or issue. + to_patch (dict[str, dict]): Dict with all issues to patch which had a non-empty + image-info file to base it on. + to_inv (dict[str, dict]): Dict with all issues to patch where the image-info file + was missing or empty. + is_issue (bool, optional): Whether the object is an issue. Defaults to True. + + Returns: + tuple[dict[str, Any], dict[str, Any]]: Rescaled object and patching information. + """ # fetch convert info if issue/page needs conversion, and save info if is_issue: - issue_id = elem['id'] - key = 'issue_patching_done' - patch_info = {'issue_id': issue_id, key: False, 'num_pages':len(elem['pp'])} + issue_id = elem["id"] + key = "issue_patching_done" + patch_info = {"issue_id": issue_id, key: False, "num_pages": len(elem["pp"])} else: - issue_id = '-'.join(elem['id'].split('-')[:-1]) - key = 'page_patching_done' - patch_info = {'issue_id': issue_id, key: False, 'page_id':elem['id']} - + issue_id = "-".join(elem["id"].split("-")[:-1]) + key = "page_patching_done" + patch_info = {"issue_id": issue_id, key: False, "page_id": elem["id"]} # for LCG, only years later than 1891 need to be fixed - if title != 'LCG' or int(issue_id.split('-')[1])>1891: + if title != "LCG" or int(issue_id.split("-")[1]) > 1891: if issue_id in to_patch: res = to_patch[issue_id] # keep trace of whether or not we fetched the information from the image info file - res['used_image_info_file'] = True + res["used_image_info_file"] = True elif issue_id in to_inv: res = to_inv[issue_id] - res['used_image_info_file'] = False + res["used_image_info_file"] = False else: return elem, patch_info - + if is_issue: elem, scaled = convert_issue_coords(elem, res) # there may be no coordinated to scale in an issue - res['scaled'] = scaled + res["scaled"] = scaled else: elem, scaled = convert_page_coords(elem, res) # sanity check that number of regions+lines+tokens=coords scaled - res['all_scaled'] = scaled + res["all_scaled"] = scaled # keep trace of information about the patching performed. patch_info[key] = True patch_info.update(res) - + return elem, patch_info @@ -159,7 +188,7 @@ def main(): if arguments["--local-path"] else "/scratch/piconti/impresso/patch_7" ) - final_patches_output_path = os.path.join(local_base_path, 'final_patch') + final_patches_output_path = os.path.join(local_base_path, "final_patch") log_file = ( arguments["--log-file"] if arguments["--log-file"] @@ -171,9 +200,7 @@ def main(): else f"{final_patches_output_path}/patch_7_rero_errors.log" ) s3_input_bucket = ( - arguments["--input-bucket"] - if arguments["--input-bucket"] - else "canonical-data" + arguments["--input-bucket"] if arguments["--input-bucket"] else "canonical-data" ) s3_output_bucket = ( arguments["--output-bucket"] @@ -189,81 +216,87 @@ def main(): init_logger(logger, logging.INFO, log_file) logger.info("Arguments: \n %s", arguments) - RERO_1_TITLES = ['LCG', 'LBP', 'LTF', 'DLE'] - PROP_NAME = 'c' - temp_dir = os.path.join(local_base_path, 'temp_dir') + RERO_1_TITLES = ["LCG", "LBP", "LTF", "DLE"] + PROP_NAME = "c" + temp_dir = os.path.join(local_base_path, "temp_dir") empty_folder(temp_dir) - logger.info("Patching titles %s: rescaling %s property at issue and page level", RERO_1_TITLES, PROP_NAME) + logger.info( + "Patching titles %s: rescaling %s property at issue and page level", + RERO_1_TITLES, + PROP_NAME, + ) - logger.info(f"Fetching the list of titles to patch") + logger.info("Fetching the list of titles to patch") all_to_patch_path = os.path.join(local_base_path, "all_issues_to_patch_4.json") all_to_inv_path = os.path.join(local_base_path, "all_issues_to_investigate_4.json") - with open(all_to_patch_path, mode ='r', encoding='utf-8') as f: + with open(all_to_patch_path, mode="r", encoding="utf-8") as f: all_to_patch = json.load(f) - with open(all_to_inv_path, mode ='r', encoding='utf-8') as f: + with open(all_to_inv_path, mode="r", encoding="utf-8") as f: all_to_inv = json.load(f) logger.info("Fetching the page and issues files from S3...") # download the issues of interest for this patch - rero_issues, rero_pages = fetch_files(s3_input_bucket, False, 'both', RERO_1_TITLES) + rero_issues, rero_pages = fetch_files(s3_input_bucket, False, "both", RERO_1_TITLES) - #### PERFORMING THE ACTUAL PATCHING + #### PERFORMING THE ACTUAL PATCHING logger.info("Fetched the page and issues files from S3, starting the patching...") # extract the title and convert the coordinates logger.info("Converting the coordinates inside the issues...") patched_rero_issues = ( - rero_issues - .map_partitions(lambda i_list: [(i, i['id'].split('-')[0]) for i in i_list]) - .map_partitions( - lambda i_list: [ - find_convert_coords(i, np, all_to_patch[np], all_to_inv[np]) - for (i, np) in i_list - ] - ) + rero_issues.map_partitions( + lambda i_list: [(i, i["id"].split("-")[0]) for i in i_list] + ).map_partitions( + lambda i_list: [ + find_convert_coords(i, np, all_to_patch[np], all_to_inv[np]) + for (i, np) in i_list + ] + ) ).persist() logger.info("Converting the coordinates inside the pages...") patched_rero_pages = ( - rero_pages - .map_partitions(lambda p_list: [(p, p['id'].split('-')[0]) for p in p_list]) - .map_partitions( - lambda p_list: [ - find_convert_coords(p, np, all_to_patch[np], all_to_inv[np], is_issue=False) - for (p, np) in p_list - ] - ) + rero_pages.map_partitions( + lambda p_list: [(p, p["id"].split("-")[0]) for p in p_list] + ).map_partitions( + lambda p_list: [ + find_convert_coords( + p, np, all_to_patch[np], all_to_inv[np], is_issue=False + ) + for (p, np) in p_list + ] + ) ).persist() #### WRITING THE OUTPUT TO S3 logger.info("Uploading the updated issues to s3 bucket %s...", s3_output_bucket) rero_issue_files = ( - patched_rero_issues - .map_partitions(lambda i_l: [i[0] for i in i_l]) - .map_partitions(title_year_pair_to_issues) - .map_partitions( - lambda issues: write_upload_issues( - issues[0], issues[1], - output_dir=temp_dir, - bucket_name=s3_output_bucket, - failed_log=error_log, - ) + patched_rero_issues.map_partitions(lambda i_l: [i[0] for i in i_l]) + .map_partitions(title_year_pair_to_issues) + .map_partitions( + lambda issues: write_upload_issues( + issues[0], + issues[1], + output_dir=temp_dir, + bucket_name=s3_output_bucket, + failed_log=error_log, ) + ) ).compute() - # free the memory allocated + # free the memory allocated del rero_issue_files logger.info("Uploading the updated page files to s3 bucket %s...", s3_output_bucket) rero_page_files = ( - patched_rero_pages - .map_partitions(lambda pages: [p[0] for p in pages]) - .map_partitions(to_issue_id_pages_dict) - .map_partitions(lambda issue_to_pages: nzz_write_upload_pages( + patched_rero_pages.map_partitions(lambda pages: [p[0] for p in pages]) + .map_partitions(to_issue_id_pages_dict) + .map_partitions( + lambda issue_to_pages: nzz_write_upload_pages( issue_to_pages, output_dir=temp_dir, bucket_name=s3_output_bucket, @@ -273,43 +306,68 @@ def main(): .flatten() ).compute() - # free the memory allocated + # free the memory allocated del rero_page_files #### KEEPING TRACK OF THE RESULTS: OUTPUT AND MANIFEST - logger.info("Aggregating the patching information of issues for future reference...") + logger.info( + "Aggregating the patching information of issues for future reference..." + ) # extract only the "patch_info" dict to keep track of which issue/page has been correctly patched patch_info_issues = ( - patched_rero_issues - .map_partitions(lambda i_l: [i[1] for i in i_l]) - .to_dataframe(meta={'issue_id': str, 'issue_patching_done': bool, - 'num_pages': "Int64", 'dest_res': "Int64", - "curr_res": "Int64", 'zip_contents': str, - 'used_image_info_file': bool}) - ).compute() - + patched_rero_issues.map_partitions( + lambda i_l: [i[1] for i in i_l] + ).to_dataframe( + meta={ + "issue_id": str, + "issue_patching_done": bool, + "num_pages": "Int64", + "dest_res": "Int64", + "curr_res": "Int64", + "zip_contents": str, + "used_image_info_file": bool, + } + ) + ).compute() + logger.info("Aggregating the patching information of pages for future reference...") patch_info_pages = ( - patched_rero_pages - .map_partitions(lambda i_l: [i[1] for i in i_l]) - .to_dataframe(meta={'issue_id': str, 'page_patching_done': bool, - 'page_id': str, 'dest_res': "Int64", - "curr_res": "Int64", 'zip_contents': str, - 'used_image_info_file': bool, "all_scaled": bool}) - .groupby(by=['issue_id', 'page_patching_done', 'dest_res', - 'curr_res', 'used_image_info_file', 'all_scaled']) - .agg({'page_id': 'count'}) - .rename(columns={'page_id': 'num_pages'}) - .reset_index() - ).compute() - + patched_rero_pages.map_partitions(lambda i_l: [i[1] for i in i_l]) + .to_dataframe( + meta={ + "issue_id": str, + "page_patching_done": bool, + "page_id": str, + "dest_res": "Int64", + "curr_res": "Int64", + "zip_contents": str, + "used_image_info_file": bool, + "all_scaled": bool, + } + ) + .groupby( + by=[ + "issue_id", + "page_patching_done", + "dest_res", + "curr_res", + "used_image_info_file", + "all_scaled", + ] + ) + .agg({"page_id": "count"}) + .rename(columns={"page_id": "num_pages"}) + .reset_index() + ).compute() + logger.info("Merging the two and writing the dataframe to disk.") - patched_info_merged_df = patch_info_issues.merge(patch_info_pages, how='outer') - patched_info_merged_df.to_csv(os.path.join(final_patches_output_path, 'all_patched_issues.csv')) - + patched_info_merged_df = patch_info_issues.merge(patch_info_pages, how="outer") + patched_info_merged_df.to_csv( + os.path.join(final_patches_output_path, "all_patched_issues.csv") + ) logger.info("Finished with the patching, creating the manifest...") - + # create the config for the manifest computation manifest_config = { "data_stage": "canonical", @@ -324,11 +382,12 @@ def main(): "patched_fields": [PROP_NAME], "push_to_git": True, "file_extensions": "issues.jsonl.bz2", - "log_file": log_file, - "notes": f"Patching RERO 1 data ({RERO_1_TITLES}) to rescale their coordinates (patch_7)." + "log_file": log_file, + "notes": f"Patching RERO 1 data ({RERO_1_TITLES}) to rescale their coordinates (patch_7).", } # create and upload the manifest create_manifest(manifest_config) + if __name__ == "__main__": main() diff --git a/text_importer/utils.py b/text_importer/utils.py index 3e09be36..4188caac 100644 --- a/text_importer/utils.py +++ b/text_importer/utils.py @@ -5,12 +5,16 @@ import logging import os import copy +import shutil +import pathlib from datetime import date -from typing import Any +from typing import Any, Callable from contextlib import ExitStack -import pathlib +from filelock import FileLock +import jsonlines import importlib_resources +from smart_open import open as smart_open_function import python_jsonschema_objects as pjs logger = logging.getLogger(__name__) @@ -204,3 +208,104 @@ def get_reading_order(items: list[dict[str, Any]]) -> dict[str, int]: ) return {t[0]: index + 1 for index, t in enumerate(sorted_ids)} + + +def add_property( + object_dict: dict[str, Any], + prop_name: str, + prop_function: Callable[[str], str], + function_input: str, +) -> dict[str, Any]: + """Add a property and value to a given object dict computed with a given function. + + Args: + object_dict (dict[str, Any]): Object to which the property is added. + prop_name (str): Name of the property to add. + prop_function (Callable[[str], str]): Function computing the property value. + function_input (str): Input to `prop_function` for this object. + + Returns: + dict[str, Any]: Updated object. + """ + object_dict[prop_name] = prop_function(function_input) + logger.debug( + "%s -> Added property %s: %s", + object_dict["id"], + prop_name, + object_dict[prop_name], + ) + return object_dict + + +def empty_folder(dir_path: str) -> None: + """Empty a directoy given its path if it exists. + + Args: + dir_path (str): Path to the directory to empty. + """ + if os.path.exists(dir_path): + shutil.rmtree(dir_path) + logger.info("Emptied directory at %s", dir_path) + os.mkdir(dir_path) + + +def write_error( + thing_id: str, origin_function: str, error: Exception, failed_log: str +) -> None: + """Write the given error of a failed import to the `failed_log` file. + + Adapted from `impresso-text-acquisition/text_importer/importers/core.py` to allow + using a issue or page id, and provide the function in which the error took place. + + Args: + thing_id (str): Canonical ID of the object/file for which the error occurred. + origin_function (str): Function in which the exception occured. + error (Exception): Error that occurred and should be logged. + failed_log (str): Path to log file for failed imports. + """ + note = f"Error in {origin_function} for {thing_id}: {error}" + logger.exception(note) + with open(failed_log, "a+", encoding="utf-8") as f: + f.write(note + "\n") + + +def write_jsonlines_file( + filepath: str, + contents: str | list[str], + content_type: str, + failed_log: str | None = None, +) -> None: + """Write the given contents to a JSONL file given its path. + + Filelocks are used here to prevent concurrent writing to the files. + + Args: + filepath (str): Path to the JSONL file to write to. + contents (str | list[str]): Dump contents to write to the file. + content_type (str): Type of content that is being written to the file. + failed_log (str | None, optional): Path to a log to keep track of failed + operations. Defaults to None. + """ + os.makedirs(os.path.dirname(filepath), exist_ok=True) + + # put a file lock to avoid the overwriting of files due to parallelization + lock = FileLock(filepath + ".lock", timeout=13) + + try: + with lock: + with smart_open_function(filepath, "ab") as fout: + writer = jsonlines.Writer(fout) + + writer.write_all(contents) + + logger.info( + "Written %s %s to %s", len(contents), content_type, filepath + ) + writer.close() + except Exception as e: + logger.error("Error for %s", filepath) + logger.exception(e) + if failed_log is not None: + write_error( + os.path.basename(filepath), "write_jsonlines_file()", e, failed_log + ) From 5af00e4994d89175e7a747bd6a235347f4ff6b1b Mon Sep 17 00:00:00 2001 From: piconti Date: Tue, 30 Apr 2024 14:23:20 +0200 Subject: [PATCH 49/53] Update documentation --- README.md | 2 +- docs/_build/doctrees/architecture.doctree | Bin 171431 -> 182714 bytes docs/_build/doctrees/custom_importer.doctree | Bin 63665 -> 63655 bytes docs/_build/doctrees/environment.pickle | Bin 2449997 -> 2554758 bytes docs/_build/doctrees/importers.doctree | Bin 85591 -> 133358 bytes docs/_build/doctrees/importers/bl.doctree | Bin 85706 -> 85679 bytes docs/_build/doctrees/importers/bnf-en.doctree | Bin 122868 -> 138048 bytes docs/_build/doctrees/importers/bnf.doctree | Bin 198141 -> 198108 bytes docs/_build/doctrees/importers/fedgaz.doctree | Bin 33508 -> 33498 bytes docs/_build/doctrees/importers/lux.doctree | Bin 139416 -> 139406 bytes .../doctrees/importers/mets-alto.doctree | Bin 126407 -> 126405 bytes docs/_build/doctrees/importers/olive.doctree | Bin 284162 -> 284152 bytes docs/_build/doctrees/importers/rero.doctree | Bin 97552 -> 97542 bytes docs/_build/doctrees/importers/swa.doctree | Bin 96854 -> 96844 bytes docs/_build/doctrees/importers/tetml.doctree | Bin 129185 -> 129175 bytes docs/_build/doctrees/index.doctree | Bin 6247 -> 6237 bytes docs/_build/doctrees/install.doctree | Bin 3677 -> 3667 bytes docs/_build/html/.buildinfo | 2 +- docs/_build/html/architecture.html | 32 ++--- docs/_build/html/custom_importer.html | 12 +- docs/_build/html/genindex.html | 30 +++-- docs/_build/html/importers.html | 118 ++++++++++++++++-- docs/_build/html/importers/bl.html | 18 ++- docs/_build/html/importers/bnf-en.html | 52 ++++++-- docs/_build/html/importers/bnf.html | 14 +-- docs/_build/html/importers/fedgaz.html | 12 +- docs/_build/html/importers/lux.html | 12 +- docs/_build/html/importers/mets-alto.html | 16 ++- docs/_build/html/importers/olive.html | 12 +- docs/_build/html/importers/rero.html | 12 +- docs/_build/html/importers/swa.html | 12 +- docs/_build/html/importers/tetml.html | 12 +- docs/_build/html/index.html | 12 +- docs/_build/html/install.html | 12 +- docs/_build/html/objects.inv | Bin 3884 -> 3943 bytes docs/_build/html/py-modindex.html | 10 +- docs/_build/html/search.html | 10 +- docs/_build/html/searchindex.js | 2 +- docs/conf.py | 27 ++-- 39 files changed, 277 insertions(+), 164 deletions(-) diff --git a/README.md b/README.md index b359d3f3..be92479d 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The second project 'impresso - Media Monitoring of the Past II. Beyond Borders: Aiming to develop and consolidate tools to process and explore large-scale collections of historical newspapers and radio archives, and to study the impact of this tooling on historical research practices, _Impresso II_ builds upon the first project – 'impresso - Media Monitoring of the Past' (grant number [CRSII5_173719](http://p3.snf.ch/project-173719), Sinergia program). More information at https://impresso-project.ch. -Copyright (C) 2023 The *impresso* team (contributors to this program: Matteo Romanello, Maud Ehrmann, Alex Flückinger, Edoardo Tarek Hölzl, Pauline Conti). +Copyright (C) 2024 The *impresso* team (contributors to this program: Matteo Romanello, Maud Ehrmann, Alex Flückinger, Edoardo Tarek Hölzl, Pauline Conti). This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. diff --git a/docs/_build/doctrees/architecture.doctree b/docs/_build/doctrees/architecture.doctree index e7d3be3aa36c24e8c615e51575c36c3ea07f8311..6bba5cb9631abb1bedc634d8f506d0a15d9a8327 100644 GIT binary patch literal 182714 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 DyEc;5 diff --git a/docs/_build/doctrees/custom_importer.doctree b/docs/_build/doctrees/custom_importer.doctree index e60495428d31efe38379d4038a162b40e8319262..2d060e4b0fb71e5c6383e47d2726fedb27730e68 100644 GIT binary patch delta 27 jcmdn^k$L$?W|julsdF~6q<`34`e8OBW9nwXPYc)pv`q|s delta 38 wcmV+>0NMYi@dL5(0|bBtm8P)-XYdM)25)tAaCLN)PP1$9n*jl4vj_5^21=X|qW}N^ diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle index 28dc0dbc3ba2a112caf202d5a47247c6a1a358c4..a5ea4c8c28ec820951d50bb69bea60d83dd2990d 100644 GIT binary patch literal 2554758 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�bIFEN7>nZR2KlbR~HDJ$2X!+xVmI2N7Fo*!;;|!$)91?pcrs=Y> z`8Sy+Q+OsDvjF*uU4;(h^U{T*w?kc6m8%O`yX=zdLWB}tQOIvYRo($Y9{{}KHgqfu zUU44E2>%(wEA}(TzbtG;YJ}m_k#}N~ZrF;|>k@1QR=|vhlOwwUgj85_vlGrjDNR_1 zV}&!5U2GmC9i9F5omby%eP0=no>`HW3h7I<#dZ?~aRF{n_yA+*wt-qqD`cosIf^qB z;*hI;O>CI0`bpizP+@IZsKs!sn89#d=ja?U*TMQmiQOdnX`(D-Or&gU%AXa@d_lFW zkZ6l5q2YM>&Olq>O=-xg0Lr5+l!3#txc84aUvdz G|I`bTLZy*^0ATKp4sBeej; zTxvGfBF%Vx2P>EF)*Y#@nuIFXF1BUnfrYfm3K**SD1I5cb>8Z=rH!J!l3p^4|> zBkPHIuits~Et8!)uYT;M_N`F=@kK}&X?M$)`Y?k_kY#y|J zx$KAiwOSs^k?}9su#cW8&Yrr(v+wV#F`u*xkKO@YDvzrY5VCSUd{3a)K3mM1A*3V2 zKSIs!XgAj;q|&0yDp6Vo?=IKf)tFIreGU!Fb$4|(p(|(Jx;vA6iFJ2;j<36)i3-Tp z-PvE;)w=tWlc;ke0(jll+>a}_Hm!cD&h_?7)K8VD^D@H5H@Oxs3!q`;_KVYJUnfZ= z+w$=1jdZ8yvq*oj`MLfri*}KoA<>i7>sz&*ls#1CP#2x`n@W(|GPzLa!h8nPqZuS2 z>DrgIt}!tGnjlYkQ;HtG06F@=E^*s(5Of;m#lo1 zTB2KH7N|z?f+)kNxpapERomK7 zZ2~BW|NrRFRk{GE&V7eJ(gSURb1$V98S(#K4rKZ11t-EQ{AWxXQsm zfh}Yw{xb#z?xn7|X8{6JE884Tb|E&Gz#<^fFJElwF{jSXPR}QZDt=scXKor!)drwd zBRQe#+|$-;kOk74%4DbKph8)uoZjrkJ@xHlpM{I;GKB{qGQHZ+?d%9Df!0*SGS%n> z4LHNT0g`J%HLQtn7V2aH)`Xq-mmuewEq)H*5CT1M+c9W%gRPxruNSsPgAG&guUOT1 z^Y}wJ9(KEp&ODqIINq4+hQYbh%`M?bFxQ%HUK$RCqnk&AwPCM25=^u~MOy71)aV*- zPlQ2yE-ok&evqR`$HUc)O@JmHXD}EzmJm*BpKZW5G+p|?-dRQcU!;g$-O0C2x5}u0 z^g?Wy?RYJ-DA~5bnch@whb5Y30UKs$8tD>oox_SVt>8rWV{q>-C|L!J*KCyaDQFWL+_T}Sg7sQ~t>Vf;chtoxL69{p*C;Jj3hZ}v5&Ii2u|_|J*ULlJ zqTgdTe8gI!6xfRU?AAt6Ul9{Y3!uuC&pTCvWm{2-!9Kh$au0xpX^_9P!BQpr2<(QB zWQz^<=(;#X6>OPOV5ina>`4O^>uKI%Ev&qCQC*y}3=D+?P1Z~h3lj813MAGVBUlqD zir<2&Pb|y=>^w|7MHHntpu9GWSv@6-;vM9nMYvpf`t#dZ6Xj2IKMdz=8JSLaqPv*8 z<;IX@HW*)3t$N59@)gjqHHO5SR{f|n)Oc&om%PT$H?#gxn(!Hr-j3ZUHz1`MuiIrV z->r68{MP{<2?ej@F6MYkLgjGu zB=t^z=k;N4uG8wONQC59MS{Ps)|TXa{a$RCGGEsh#bDWrpUWPvT*2~JJW${gSK=o5 zoVNIJrQiQsZNFva)Pt^?D|H50H)x%4$(%aqPj-(_aiYR>rS2Sv4g2U$)nMJ#74_6( zxX7p^*aF88WiL5A1_+KvYQ2&m-lGrHY$Rbw6UC0s$Z3qC13sMLaGi1^X__%cB|#6) zWb7kfya-q$X83g8Buc|qmyt6Ll>{$a1S~Kr36?dRxGtb5z?rKYe8v>Nk>;6003rvI zVc;Kag+%yLIhdd?tzQxXrJbm(0FXQghu5{b>=g^QTftvyVKm zBQk^I(YKKVYer;u8`z381sz(yibRcUM~7|iZL||V%cqDL_Z6}Ik}ft%66Ph?iUWm}se zqy+L%&el+tPUIx#-pONVeFkxebuwR2on=M*VjMp5nS%UTnIJsmz;Xho?uX{HLb`$p z!qENnP@JHif-0)tdYA)M1ivMy(kBS}X=^!}`141OhvK2>e4pM-e>Pp|4eozzhMO?m(5F1pGrln*a(zzzK)0(gi^Er5y*V2)?wGWtwVn zwF6a)RV@l)zXgY`($#_*`@L}xRM|o{a$I9;C`%;@V?VdXEOo((ArwYkFm0ttK@E9? z8=rHKXcqql8g6_Vh;|4!e(2Cwf=vmk$l=C44rF{<3JV3FzZ`!vS~*_)UnQ?$8_zWMjg){ z1XXV1yTHH8x?qJPyYZcm!XD!Q>Vo|z;`&%!ur0_={AXMjY)=JunpGAo1@Gj^1|P@P z+{%I-xju%5vlL{2hvPYB4JZ(`6$&`bh8-v-)tw8qe7n+bDa&V9IA~6m1q(6DQ$e=} zgfW-Yyut#An)JlS3(lNtjrW>R;|q#%O$3|fht`ZwH@e+5V^`$^@Yz#PZ;RFOS{F<2 zEEL{YBE4aOg*85+BJ!?Ns8ka+bcAL9v#RL zi?ZcumCStF1OTMkMj zOG5pQ^^np;+8I%cqTPqxNF|#}uatHw?65|`dW~JhHI{b!i(M*|ZVG}VrvyRP)dG#u zK0}Zza%I0LihFQf#6BDvCXP7iF=hq4o;9MdAtyR{NDN86MrU)_TZj(9Zup3`L{YTk z>S7gDuH_8&jJn8uGBi9m$v(wbvc(2_NnMUrJ#qvnLL*t^?mMiB@L&S zH`qFpNW%}HhB;QiQ`efzGRhK~uefsgv%=N8t5qF2bND$l98ZCbnFHRm>Q3nZvs`u7 zhjPB<6%RkadPr#^J#&bNA?9DP8|6Yw!Ft_|To02@MJ19FHll#beh=BDLg}V4a}WfT z7cATDQ)tbY5vavuc^4=%zy0=rSKU{9=zQ&hp0i7?NpgxDK*Ub1uv{&z{dTqh^8 zXAM-o>wM)~BGjCygl8G7A`2LgDpCyB+@zeVi`dRU<+{~ZuElZP%j@ElWrQetbu0nTZa@xSW29f*PK`Mc?UU)@Qyy8#KHVV>={>VS{1~c5f7G(JmY)- z#s1?T6>fg6T0N3c=cl1zYt)H1t%_7WIKhZr^>G<_kw41$mRBtGebz%t6FyPrPp}*1 zf)l}da@6^Ux;P~WDlgblQRlM%wJQ-tDn^jJ1R5S()VUnHQLkVhQ5UDEf-MtuKBg{W zhX*RwRlZ^^5p14T7pv-!>xR0BJ#V0LeTJ`Gi-XPSx;SMS7z%^U`I-sJ3^tKiYm8tG zHjCdnI5(tHf}^~~yh6=SkV6McsOdn3JTu^%uFI{mWG@0gJWKX|jIY?%b7sK%s?}VH zczHKAOhLTp(7Bbd8ct#%*kHWff&(z&Fw9;!)6HIB<{&1*dx|c9wpvce!q1S8i~s;H+PGoHR#5IzT^^LwU30_S(Jy3>XC ztRsnaQo@FqjK%KV{%UWHGVSnU4WA&Dn>A#zhND68R&lL-1A1`iB47cZO3*oAoTT8P z9p;Ma@r!{oY~`2EnWgcW0Adp=moYqKi{D6VlS4=%2b2G1W=t_7V=zHqTFGGI&=Glx zfQtB4p|%bU%Tol@>?62+W6oDtp#FSa6itl!e zz;XG|^r>(fa!$IV>Zj^FN8lgTPYq+&HZ~{L1mL5g{(oz-y>YVBp22ek#E}B*1`Zc+ zjsSZ)oDOFo(u89I@YmEq10%us^jx;M#e&fx)js9F_ElSfnfO=VW`8j}lk9v_gw^yt+{g7x@-=|NgZ%(&r5 zull)Lnhg1vUJz(!@mTx>mq7BX!ut2nSx-3v;GNJs?B5vqmIdXVKH)$WAq!McMa>cZ zssmMo9AQC~E=^^%JAaGQiT)jQmRfj0UfA;y+?KV~gGhH8?m!DRELx z4UV@nstl7nB!9GwP0*~sw!89T{2Vt`?VcjhWq+ej90@%88}BC7VE1;QieRvUDyqT$ zodZ<_gB4Wi2D_(>Zdk+=%eJr-8$PYBIUMcK0YCHQOBG)By9$WT^7#d&*v@e1D?x<> zRa6Ihz5`W$67YARHUShA=Gg4eRk{GEHazD*6~Tr}sYS;6-HRN^^3#hNEwi8|$4w4> zB`bz)m?n>1wuZ8}?=ar8*xEY#mA+md6?Ru%jGq(Zy?Rd>??2$cd;-tj1I=eS>w*CN zGY(V{R6^l$ z4t*snhHaQ8k6pHgvbgUs-si7o$K$v;zh4o*7(c>?dik?5%5QdHIe}B-(7eMaKj%Od zK@kL1R1fhY2dW4jLQtiT^83kIz1+-*W$Q4tztN!=St>oyq5hQ?Jt`RL-|o;?f*uK~ zs6+jS9H{b>fd5c$6F|XG|7C}+(gi>@1 z{>7oMbj6@r<}OROzaZQufPz@-kq%v@3&2>;FQDo%gP_V5xRK);TSHk!Sr`GjHD;-@ zUcA$+S6M$mh8fw=hSy4K&br8W5couok$pafiMVtVvLnF9J5mE5ZF` z4pjL`z(3Hi37{a*c!@(-=>nhz8aFslMF=#?sRiOG6}5PS163rosIR!UO*;yPl@B=d zn5-Rc4qO00Wf|>4*KK^~MX+@~fHK;zh0pLZ+FwU@;y>du+Iy)8tgJfPDV!6B+r9*w zORBko&;8r(N;%@B(C4sGxB0PaLnxOBg|l%&jHQim8zYGKMgT|HYs1ZMv$c6T4B9Qd zpf>$=#eBZ&piEijBP1^?Yi}3$J1)F$DLeq-eSC!?y#D!TK{8M`m)7&)I!va(R6x(iYZb9vBkXYClpBn0fo1q@}>e)u8vANTjomxHJY=x_P z?MuT}Qufx+hdMtds(A04(I3^T-lMQYg-q+Ew)@K&FXB}kzCyROr8fAxoCYa70z%JZ z5pARtM8+`iC-}%iF^jzZ8!n4dj{4`w2ZoSb&Xw;jM!y3KJ_bsfrN118o_uE=Fy~mc zuyV>7mCy7WdOB$FOo)(VRnqAj3J>?&p_=)p5V_3I+8Y~Y8Cud0qYkXKbTg`;!u4Tn zbGbPY3Qlzbb57T`k*W6_Ce3!?C3p zwde4r9KbY#w}$FkS&Kf4n3X=7as25yUve-5S-|R4);~%U>B$(iX7!oajZ|U}3nN%1 zeW9&yn2Ei~{IP!4TMESyz z^9l-7l%AK#7R+2v2Us8s#*NN=u(jEnQlhNjtkb(g!*TV=k)Scr9bE~-d2nes--Um+ zCRlz3d%YRrKp$*rOwYlY8L=xgOQxd+s)k6>ZNXG&{+Dy6GMzHa zA8*>eT6NL-SLS@np@-4|yn^+R(nQ(;L>Iy9gzDxuU^mJ+0Kt0ATNc;AetTV<5(HK1 z*Ke(h*bhR(w4`<)v3|r?tR-s2e6ucAQRT`PIu+lPvG#dqUF80Bppw1ISF**`VjirE zQ$n!JdFvqr-n05{YY0*N)-!4vGL_*jj|{IcWR9#`-eJfYY>tqu+ziW22>k1kWY%yb zL=bWL>UIaBjV&Skv&&OK^Fj)x{W$a}2EQJ$%&s7m(b5Ph4uyuTGYY(ERm;*NPdN{D zLe94wdMHDR<5>?WP56vGPr`1L3n>Ka$sxu0b#Y1%R9diQri$Zr5j!?ev4*~4EfG>Y zuP#!Y7=#8@o|1oDi%hhZFx>7pDY4r3G6i zoY;Lwy9!ZcA{1AObqE?B+zQ2>*bN`CmIx;fuZvYwxr)Mb>4SVTz^0F{i`-)eD%sr7lic28Kd{UQ{zdnPDpuYmE`CVQcYQ z&#EyfQ$njeQoMrQFOmZfOR(!eg)DM>sOy@XEKiGj|DF{E_h3y#<)IHduMc~3otB1s zNPbl$__1pBUVd;82|$z~QmHvJ+X=hfb_&Zd+3w6V zddY-=N8}FgE1daSwVaW4D!&X3)1oh-+fu8Lxr5d!m!ikNs+NaRyY9h;eY9&2x!8?| zHO7n64k>+0mfM{;B}(_0IrYUfv0RY-5K|s*B>Lfjc+WZ_StmDas4|f=2P^{4@UV@} z7^Bp$4`&Pz_{d`5j9%(D2N9&GOkB8El={tMjKkXA^?cc1_0;p#xiK95{wqtD z^!IB|H@AeVc*)#V0P37-PjnZ;NCC2nrvUYgg>_*Okw4Xlmh6bT=L|d=e`AQ(15DNF@54#Cd}ewT1R`x%`Rpvj@c)Pd&BZeTJcGeX{7eoRkIW@w z2U@Vu->KW4>x@IqR@RdXpSEPll?%hsv*7Hq<;TlFN1qKpt&1*U*N5W| zWANw77cAgg_rSl`MdR$=1iOapI?1k^;Tlchr3sg(EsY}nzf0kN>!N9PonhA&-q;~} z3^ez&o=wQ5Dg?FOov+LFD`T}-+A-leaU0=+uFM%s(-AmcO z{~rH+4PHi|p=+XR;ZNx5%iw=&_c}5<41Qb}Jqo{ZC|<6^C;NCgg57yE{&+ZEUXCxW zn7a1b=oNVTAMo;6xN%Li3U80%<$Cz>+GrU5yf%6zKEE_t$^P+3yn6^<{*L`Sfa^8U zQTX@Nxrmv56+FEzx&f}B>o>yxu3U(2;{Usu|L-;Yf3Ic#8;*Kt(3j!mx_5!bUxSx@ zKLMA$@bWFNyXfn9nZ`$&aG8t#IrZwb?6qszE7!8euVs&3I~P3&RH5>)#+iw+#*aZQ zKNl}A**Q_LJK)&_-yPjA-ozH58~CthT&gwqmp8jJ?TLkJgg(C*8fI49e|gpAl*gwb zT>=o4v-4{v+T#G$#OVlZW#m`+7H`G%leyPnj7T7pAiL)xQ=ZQ_^J@6(Wq3LAHE)m+#@_$9UQNTDVN(<;d5;Wdtuj!pj|ax%8jmGK-hL;AP3{;c^{b zuE)zG-T;>XFZbc)LA-qZjd1x6UOxCHxO@aJm%SA(Tkx{)+u-sryu1l7Z^O&@+u<^W zmtFo9E_>kRX1u%(FDu^xmr=ajikFY$<*a{$%R0Qg{GD)l6<(Ouy#kf+9e63LBKWYQ zid^*Q#xgFOK4 z_gK7?WgCfSE9bQiUFSwkJm%HzQ%dhdW>^E>(ftPpu4leJ;%QJot zmy7Z8(LcfEQ+UBt%Lp?x=c0G=vFzXZ|K0=tgW=+R{C^+d|N9XC->v+AAL0M|82k^$ z)Q{u;mPDU`|3#mI%axG)wSFPG4gQ37KF|O6Mfl&k=u7+`Ut#}9bK#dez{@{@fl7o* z>PU8%<;HMyuX?9{;QBMxpBt+t#O)HLEY8;xZyXgMZ-yNl{6C z5wxn~su)f&`r|{^=gfxtXVLv9j-0FGn7F&T6L#989XyAZ#oG|*8rg*(+Asx~it#H! zcsZPOHqnKw#xU3%w!%(xJcD&S1)|vtwnFB^M7SlKZqKqb$Dt|6jexY&b5C0zw8E|3 z*#=G(*c#((r@f^)!9fU2B5+)?6bfHq42JQ$u=Qz^?g$be6%acy^X$y4Ko$kF-zS*Ey|nIzD#(h!C@rr8Ni&dpeCdO@}8~ zqg7^!F}M1U;g7Gf#kR`RQ|GViEi}ZudX&pAqo4f>!!7*&YHC2P5Y4m=r%n2P>Q z@cy%d6&uYKcQ+FuoVM7+oGcdhgYk1WA!hiou4$HEL3@g7?zB^-9$wnua%`B{;L#W1g5h(H3*4 zjHhs?o8q(KhFOJyxmI(s3I2KNZX^J8OcgAZTJ6r5nfFGc)ZLOsN{ZDR$X7 z*&St!IKA0f84r%b(cSBmPxp_x_{77o-)n*+8NZG^X(TvtBJ7TDgt`$MA$S@O*96HU z!I3AO1i>>$K8eB&%h`KsWrZi?C|E?T@pE!cW44_GLX97!)m#Kt*PYeD*Qhl13t%eO z_hU_3`>&(N(26E%!L-+)n5DjAL;&yqW=;DJSLhgxBXm5_4uX2&Y&ADp@*Tw=Z7{i( z7>h;1K1Z`<2q`)rN#H{xFydB?5&M{C7+V34H?tm4y12=ui?;z8(dXb2X3pRYJ^T-R zo^3}Jck#DTaNW2j-jGi^ar?D9OR$jR!M=U-(Z^ ziH-d&1om2F4Ff2pMi1_PAB(Rgi*(>l%GX|dOcKWi%#1LCWizJXWRA2lo(8E7lk}!y zY2R1K^p9k1W^%ZdS*JA zKF&-J-8~HPws=8d?H6SkFQ|A~Z>#&d%4bzpS6y917jLVty6*bws(3-sT}53H*InP| zsj73To;p?4r_MRuWcK&VADx~)r>dTM>Uo~})>BVCRUWC$G?{~(m5d>HGUbl~q6n9i z-wBfrTdo?e+(IE!{TvJx15em|pEyW|52d-Jt=h4Jb~3CbVFc-_{F(IhOh=oxsn;~g ziOE2oygH5b)Tas+p!nwe>cws-v5#!R!kk~~OeLX(It+T!4(BMafiva(YG10fJ*^m% z0diHGzy%IpxfmcBbhy%cfJ7BCI2%>o&uJj4I3H{RpKbnwRpBEhq6!&wNbRboHl6Xb zU5@Dn(;!@fr$J`I1aI5MjFk*j$BYg)Coj@F+!jIgk@e8L0O1U(ntJLFUnU|ui*LlR zj}C}sq)TD;RemGcoHF^sR#T$QDJbCwpE&|XaLiFL;^WrQNq-2Ai-gQAlS8D1{CeeF z_y+{me+rZcftEO0B8Qb+Nv25;y>Rr}t#KYqj-$743rdxNeFKmt_lj*h``2ACIM_e9 zu77ZSe|dOV?&b=&=JD3zbja@_6w)D2WTidI)O0Lw%)}n1(?*-S$I)D3wA&j+nX?xA z5zo)S4I#YGE(nO^iL<~QNm53hrp3(SXK|RX>Q$**|NHl;WoE;5lq9Mq-k1!w;ff6WCt)jA>L4W1x1ZtmkDTf)zQhO=<7WA*RGR{>F zk^UxdJ^o7eB0rw6Uj!O`Gd_2C^JuG!}vN^?89BNR&!=OX~SZcKaOT{dV zeUN5A6>}~^ReeB{163W$P2LftuU@EX{m$Afgwu_d8x=?b)P$ok0UXBEP^j`Eb4Sma&F<`gV)v_S#i1|>R#MIwf@ zbM7pUUW~BF$AKmX7CD-mygw&>^}-@&$emIM6uoH%EucjIV(OzTIwd=_$FyNcONC*_&QdsO)B1a;}NVRBi{IVK|Sjx-uP)jG&}Id z@m%751T7h?i4Weu5w=dPu8G@(Hw0NO>e5ZRDtij*Gnb*eI!Fd*;i=9A?16`cJ4cvOtOD}Y> zem6=jgziA3JsfJO!6i*_prr|Ch0xc59uDU3fQu9Gq#>Sf(Xht3=_+8Ur)3pHCaxvq zQ`=P#_Be?%YBy=Ao7iI?X*qWvCH5G@it}QRS*#=@_IP1IJ?kd+*k2IM4(xFXm$(^d z$zWxCum_H?b((ce;wJ1N$SM;CWFk;v52{sEwk_zuMYhBq)UN4L4%XXJdnx===wB-A zfiH6G@j?8?um}4#zpw`w=E{?dJ&=TsVh`3pWTkZM@i`nlf<4&lWS)5l-{~Iqcre+V zf;~<(DBY)^M2D~kXI?w(!QP6n$G3qR2lhCX8@RtGz4XE!t9Q?gOu!Yc>?ATajmME` zs3+e|alJ@J_y>d-YKaO$5~uTSe~+}(O(gL+X*qWvC6f3JR-6J!tiqvcR>AAUg-U3h z9?P-3M{1-ymP4zMjOf8wi=WOK=axL^LZi<+!+n^AYe-Rd$0LmO#q?a4PYe=bpdpqX<%lda2&vST6@*mQaHdR?mb!^lUP)Tc-A9R3UXB%~Kq{8h==zDb z@by?9SK$n4)8AZBH}gQ0WZLvLCuHt%5Y0iio>1Lc!VU0}3Dy7F(Y%lNt2_(e2bHG3 zM%+7C*U12W;;S#9(bmo)ZFrVQwf@k7z%xD7dVVp}lCZm2W`SWYkY@^fJDVHE*8)Ec zlb{b|Mnhyphc2>AkXexR5;Dx02$YbS8h>hnGjLNvW;)gC#u&hbrS=ln(ljB_sLmWR zFE|LkG04om%`3>f9j}gV6oZyGWkt)3hE4+IrM!g*2kRL5SR6nVBa8P5Bsv~n>K;5k zI@z28kNXXZcNml?03IJ_fX9M;ZIH3xS_F#wfF1`Z?&oIi2+~h4P<%e#kcv*Zjgazg z<8T~^v>QZv7>;T;n&qic5bnKo$WY0W12C8$?+}VZW-Fp<=;U|~A&lB4{Wx^%C=&@W z6#qKhh@hkz3pZias=Tctv{O1RcF_`V|MndwHWCzA)wiEeQNHGu!#qz>5LuDVn;Og z2%_0}Y7J(unH{ldF$% z(KC(}YomcCf1`s34++QkaBp z9aoC|(2}2Oi#p{QI~-&yc5=yy2~Y~MDue-<2$X=5Di#&*3@R)EC7q^pL5JzsQhO=9 z6Z)6R2a7LqK=~p3#sDSzHm?BXmG$bramYSfNLc&ktca2m(Md?TOf(T$D;-mQ0mo3C zQ22dF>unnq=_!hy+?*T=^Fqv`|oWqUX zXGu@JU}f)?Q9zB2KnQYqdK!0uM00MaKUTLMF9W8Kz^C^*@g@}a7mJ> zZ4gS5ml0N(1}Ys`g;kz0S!tG8yhC|nqBhHd;^D$>apyz5vJVm!_CQ?w z+N*b713$CNy&*gbF4}im59WFMus^PffBG&<4;X+25w5gPA75Q7z2c{ z)L!CRnr6ot)tLkKC9B{Y1MKYEyaMc3mg~eTaeSbb57YS_6{RuOz7RS_vUpLB*=H6QdK z*k+m|F6C@Hm$cMP@VtYxoV$+_JUG|w_N_Ch#6oa7cF-GPI6JPzizA{-$1Js% z!i|_IRot>?&60{QaaO!NW(E6J1S{BOhG?cboh5Y(@rhwDmH#!4PxW`7VnaqeR?sbu zmnFJY0vNh6Xf*46!PLy;NCob41ltS~d3B+}W`hYH7m*>YR*Pa(PCS5)DqLxX8C}x5|YhiV_SVYxXQ0YMD+N`)TKTsbsPfQCK~+L4^Gugt%0O~XtweHjY2`yqW(v?s z&d|QH}N z?V*x_szO!rlnf+4*(!Mg&|C;`u63%I)=718%w z=7~>@kJwd&3B_zasy9_f8+gnWr9L1-Q6W^&%^KSjZL(HkDC9=u2v*ECBBiXNXGJ?g zIeu+K=V$1-;`l)+9JV~sbIY_&0sm1@0)WDA^Ayj#Y9~j7_^v}urQ3-ps{EO7@%Y2+O5-Dk2~|BBDz;9c_^<#~ zRsA$vRq+?nzX3-}8Tuzx^=#HZpDOaG>L7L_y{f9r*HzWYd#kFpFLg>gi6!IWsxO!J z9^pMMxwUtba5YKgy%eubm3NBFj#uiHfN3!*ZzCwfpA$96{aqPzXmaeVB8nXUO88kR zR?g*=TW0*UKw*5?9X)=&qa`0D6oW1Gc$W3gr;0p!{6g$TdOcQ|uj{ds_f|b_U#ibb zc!?0YD$k|V&-ET8i&AHx;(-`vH?jf)nc)W4;C?Yoq9g183tKP@Wu;<$y!@k%4vNn^ zbT|5C*^Xo|_!nOwzho*26x13`BL~6`lPi; ziIVv!eHMNLRbD-afiDxa`6j;Mw_M6|8v1L==ECQKQ1P&;gAHEiFGC4Gk88-*__*R= zp40G&Rj~FrDiNK9!y=umEkk$~V{LTw;!&VO=!0~;Dx~u<{R-(HV9-B8<4TZ@GLBld zj@G%-4ZtLC`?jD|8Q3=v3=dziZD;?w3kC=K2iNrvuJ14RPY(_bo;y6eMw!5w)jiHu z2Oads2$h=TX&L=mtL^R6{jFonWYVUea5U{R5sIOZ+5NCjG3E!x5Gi+cID=W1he)IV z9Kr?Q-%0;~pmV_TnFJu*V0lmqH_<1pC43F7>hY32`==|kkH;SFt;xpNA;DScyFCgk zX1-h5TV(ZB8kxkas7ZI&i5M&)r7?6oT2^y$YQG=C1f8`+(YOi%Ehxy1-JDi$;{-ruZ zf-mxu6T|q89U@`hia0sJ&{Q{0PHd0-w%IUHpyH?u$&_(M91y{W9mG(=oI-Z)v z5mfDPRDjGj`|+*rVXB#Aa|)(9!k~K7P@(`#Mbjy(L(~RWMO=%pRSW2GV5=jzp{tXA zdSNT-$|WO6ACYmqn>0wRP6n)Evf)SZuDyn|)J@dz9@28|K1$T^POMm)8jPqx-Sf~< z!~IxEI@FNZDEW9nJ|ewdZAmB?i2@L~B8(wwpNx>APBJS*|V?jeY!$>tOU zahyR77eI*u5X1>duZ+UQI0DY-oKbie;fND~DhG}@j+?$kq_dqSDva(*{>=~UyMk>>4Z>fzY4-1t2s*o(o#3^$N8k?+S*3a{8gTX&xT6VhpBkG zeVhznCV!EBQ<<|!8=fWd#_c~4c&6u#%P(eH-Z&S_EYQmZ>`V)@6S-l$4ESl7{(N9E z8X_wqbdhC($%3qPWSBD%C}A=+{?r6#;HHGhbfVRbF~AB-?Io_IY4XOQI&+x(LHx#G zGW#~KVDi)039Tk2H6mXF(fd(~Ux*C9?w$xqD2c$GbRN9Rm>&orD`QLloh2dd3}p5$BvJtlxA2lP0D8@+2tU%jA5 z+lX&6v~AxeBi)!^9pBN8J|kY1`!C% z$dCvh-68{mhEAf4_&iKxtaOm^2o9nOGU5_s#`!6});)~zqhxam#yHEMe&2%<1z-%C z3ZINp8;B9}EkYQ70E!$4<1B9O9wt5YLKuE$QOF?s6(y3e_$Y5laM>;s_UPlRSwLFq zCiXa%v`p=D27D#4$5B{uUhL6}m1M*oeFgQbo7iJbK{PwChdA(ZCbVR*Ek4)-N7y>e zx)i~LJp@^Y69!}=P+||NRaCYu=)gs`#2(a==~50h+fqA3DntKLVGn$fV~=6{#;^zb zHm|V9j>^<%Z49pq%y3V4Vm~FTk0T-A9Q)-iZ~bKp?BYZP6FhrkikyX?eoB3v)kK$Mv^e&`~mj)5iuNfj#3Do=uBO_6*#?6yLBYFES4r`y0*5qz>&o3lo{RfDlbBn$N`MSn6-!?0G6_ zshiO5D$;W9K1yhJC03jQ+AZBXQ7hYTQ@jzYN#$M=6_F7fDX4cUH1Un6i8`2mduym5 zsvUB-k;~l;(2~K{_}~&8XX_N~i%SGqR}cndB2eNIs#{dTEy%$|w!|gWrs=W{Hr!Hs zDV&D>rNSlnBF81K!*2|iuy6AUm+UByjSaDg%+i|7z+|%09IuT*TA{3`+F~L)2}&lD z(`(#riLqc~@M0URf-W|O%8)>EV05C~XjB>lSHUlsi;l(&LL%7B!LHg=CBi+W#O-P; z<5r`0_;OJBofygw5RIcsVwvjlMD6@#vbolK4(KcW1{8+IciQP!zVX2H0!BCxhu)q&nT@%vG%*Ih$UW)0H|L!)eUO zG~FC1<*A5B{}lyEzrL88cX)}9?KZ*=7+dWmP3u{E#^G30d19hA+XyP<(Q!VKFs9S> zN~2Q0xe|=lrVv8UlqcZ2oxQdCWEuB`;O9x)#@<`6P4=`O;10G!#{U_l!>N$9(faA* zA6|zQGn>8G!rB6IDXLfBL~a-cB-96_zhSZcl@6tSSTS=bNrQo4Qp>9trwmO<*2PTE zgHmEK)gQz^aa+J7tX!Q!af(mt#h%6F1vqKZ^`8{W8=3k01yC`tf}fbzFTufw(wtHW zw9n@HB{+#=VX9kQe@G-^&e6UNZ<&yd(11GR(c}Vu7wff86)J@A^y7uE!)_=}Je(3k z`_(w=km&iqog>26X2HX^-mP6o-M-YB>ExJR+8;`GAhQhQXw6PbjqUolYv9hfk<(VZrc`GdtM6P3%zPBgT)cJ#O5kSPK>{j zy~VwU--7qK_mF+-jQ21XFX$C~oEP%$GZ!z|O*%`N$IQ+Oe2KH-k(d?iTM?}I@lvQ9 zXQtRy(xJ@kmjm*-xl(Tq$!)I&x>VuP!O7a#%tR%yxfbh{dhAsMAL@oQ?+qAQGx|{uGXRBe{=cG46+tY6yX?$HpBgQw#2ppPa1Rg|=em4CG z{Ece_!b6aR*yW;olD8Eh_mQNQLfwrF9D;0YRITLOy327He>-}C3x4z?;p>_HeD!gy zoeI?h%=L92pOb+q5BIU4%GZ6oB?DC+?qflfK9&b5hBO+`cCnI6unf(fe{BZhO_*K< zq^ICmC%=vpVFx`V!@8I3@HRm7BK$V9m2&#O|`2!iK@-XLuD&3qPro-Q8WZT+w zEJ$toH!}3VB+wo0S9aNuTxwbC@RUYFn zsPZ)pGa0DzFb#q#eSQ~e7;U||Clr(Acx48%O`@BAQZK@Cyo^bXZ65Fz!9sO3TVC+Af&BwLm%$N;sb&>I z!S+Mh$>|vsu)Sr7=i8*vD$cvEWzF+H`g*wRvAxnrCU04bMKziG|4_e)UK~RNg6`Pb zP*)ng2#>~!)u6P5=-GnON**`-s=;RwG4M`@IBa>6(AKB&{kWi{?B&27cYm-G_GwVxZGnn`ZRQ9zd}uS{=_A-U1SNemK;vgS+RC6BGFt6=)Qp3hL#PH9u{L7=X@r9!qwP{=GR{PZ-Le#N$JGNn41aVoXI3Hp)p}bIMEg=_ zq?sq_^yRGdG`iAj9Wty=vcpts4pyJhMxI+NlTVG2hLd{1NQ45E9A~LypH&>8_gQ^L z8<~Fc`g};_(h_Kg%;v7n=K_wO@}A9e1IKN0!#F5KA@uzLItlSf;FXBW00w3CRMge{ zVuZP`Eq2f)7`zKAwz?hgq1BwE#tdoamiIYY%Y_@>!}{n`MIPMnzp)$XaD&QxeKF|d zz15VrFXfv}j)^aKAKEB5gN>sv4v(Jh#U*dKvhhj8>APPoHC_-K=b zZ>cEj_ITmV*VOr{BK%r#g53oASQIhJ@^t8bWtDg-Ysm6$%i2L))&lODQy2K;3E|;M z#qv7HtaisdnJ8mFNv zyz6K*+gMn!cV=o7&&t|QN8@|^bo8H~$g(t(>|19}N9)`ug^Tg%){%Hfxqoz~(X37O ziwuZBGjX!>mjeD$5WQ3m7XXNXFr-E7KQ18STe;W#vv@k2#(;$1#NXI9^-8b=KZK*( z<}KNu(kHE>uet1+tKmk8CT10i_^JU9%R6LBxU(S3AXMQeKql|(tCAHfojnvf`}hiT zXT#q?B`=yQg};aYft7^ql)tUz5SLM}RvL3R!uyM1Q?Ye6KYxCq`vCeFW87TpEdF-C z^SrfqdHmX4&AGA|QgH>5p_i>Y3O>=yn;)3ozj3TK3g^o)Pz5(TAo0*pb#l5^Z&vC9 z(U-;mJ76zJW|FlFc&%f+9W1wzII0i3@=eGw(+I{Z6PSf67AFLJd#m7GVZwIx)@y>X zaGhB0Y^NrvBWz{v1G#2+wpwfcU393Msc9$=x_R=$NkX|19>5IX^- zP|vz>MjC$7wOzU*M2!E@UySqQT=+t=xz+(bx;p?#=y`<0FJV8_WsWwggvbAy>~uR* zktd0;fyd!@kc23!f{ru72jQt_tnTS4q;oFB7>y`Ym$8h6>OO|V;GZH?mwgvgwoHGh zkj(AiKVY#Ku)v_CkIUV(1rnv~8*lc{R`5SQalqs?GYTFOh|sKo(}1PXn`#E-HW?6e z)uH8)tz2aU5>=IjmTnM1D(mtF|i5#;QM=c&}wB0CuMde1u%D_Hj1In=%=YyCMq|Vfz7NcNYH~n;)P`V znhFeng#x4X3KYP)YExB+h7GD?81lNe3URXW9Tz-p9d5VqJeK<)F*vn{|3msjL}tl} zsupxaWEOLMONh)TDMW7FyB(5CmKznse==+6^vnpvUyg$qO~UnZRg|P=brM395zLL1 z_xFK@K@!VKofVy^GyyR7Kp1r-EP(J}$c4C6#;Pob<lgG8U*w0FWj+CtY!Y(rM2L+i0y|q=wud9$KP-E&SQbjxXg^lWT%&O(cc9bnDu{eE z!Pl^WuznWVm(dnhb|WdsZo!AB|^8OSdPh^&?}gXSpu-} zp@byV7^e@wP7^&f>}V+iQjpP8H?scuRH3?EeZ#Luup5dynn6Kx__fM-eHYZpck3=_ z`%rg;kM00(L#1uL{Jz!Y9tn1Nen$=5Cz4 zVfMwm(d-?-V+?0G)N~2HNOhF|MA-KR9Qhc%Q&sViKGAonpmV(MY}6O&Iezh>t=04j zx{5H0c2#>u`uu)J>lykimG*tC&puVCN*m5VagV_I4`4UaDXlsfy3#tw!m70GORaVY zEg241Rk<|yF7M&Ut-+T^$T*%_$30RvEAB zv6JssJ#JrWOFIb`E0aDr?ZVa~?8_)DN@8gyQt4_jm%4w)d(?8PJ47Bf;quxDK<~!C zz)ynCld)=@JEJ3q4bRRGTO#Pn?`;IpPKj#@$Lgu3FJV6sh7O)&S2!OTPE6r_P%$h* z%u_f%?2ejW=4dHH|D>8<%KGP1MIJT30=tn;%~i(hYVPE_Rn6O%+K^s&LA6!!CBo=x zD3^Ax@gA()+I?{yz0z>7EYj~Sqj=gs5I+MJXNr!you%4z1@Z)pPSoJK-`MWOn@G>0 z>-g3(UIkU{=tsiO7Zxl05})6~_-RpYeApf3zRS^)k7g$3@H<)me5%N!+;?L)(kZvf zcwM=je77oh`%-ItC6~yctNvU%J>xw_7M;#Og^VNKoE107kS_G!?LGpywrQ?&P!pTf@|;6 zx((-^*SBtc-}((}VEw{YG6QSj`C52dW?%4)b?et}7#duM>l`$by=$o5d(x=jLe6>j zFCdDI5u3Sq92i$N#)BtvBt8nSrWlSL$7hlk8D>YfUHM=ud$ZQsOWe({vfP9qp%F+@ zV~?{#e*}-S`z4w;7H7x46%l900!paM8%4D6crO-2b1@Hx;kP>b+(F_OMnhUj8%7hy z9C!q)+(ud|!A@{x2o@Y013)UUk;h=l5XTteH&n>@70~Hk{Gk_|i<|jfCDvP>>v$Cl zZ%&J{iSwvESwS)@Pqw2Jizep@l3AK;uJ!r@Cj^R!gyTYzRoaUrT!ua)DB!qc_hZqu zay$DaBnEDW$07+)xJ;;DLUKW7HHhY;h3?lMIowSIO``Cj-f*bY<#- z#C_Wcqrs6Zvz00?Ou2MAnRe-!OsFi1?k}A_h&_R^8aO*1vfUOab)VCLZqDKiV57qV4mxuBr^s!_k7-~yJFHlO>$ zy+ANzZ5_)nc=@meC&IBrXm7oSfoRjRvdx7Cw`Az6$BZYaau2~F+r{}gaXyS_(kn6$ zm~SxFr#PjfIoswsGIX&Ek0Yst+9~=sX5ftX&}GwqVVUGr2yHb#(ar*xjXQq4YBeG& zcD(9C$V~h*x(VS>;%qbBfuQ)i_*}LKYl|bgKpE4Ywc4ht;OuCbZPX*>;dRi)WjyJZ3*loG*5e000l zERlwX2s?5IA;;1X{sUIb9L9@NGXBgxS8uy|V^AK0<7-oUYk>&zz@u=uxx)Fa=|svh zq|4vze3zG5x@U>3us=$e5W@uK zb2jP-=wU+60kDP%wJ+80C&WY)U9~5mm(MwzKZ1VSeVWQ>$0H;_fjo!v2UbP;b2#Ud z9f72dlg9&L111TaARKC~?IRG~)}9frUYkz=be2d3znaOtMZxi5cNBcw(UOl+iXoSJ zU19z6sY2E3a1e@n9K{K-8|m~~WxTG}PQF|9x_zk?kzP~Bo3r58AvcwB^t5%%@NiP> zhqXk=bl;6>25quc?0A0-h~y~8*%!vTWkN@Zez}U}vRXU6*lvHb6>GCAZ^?4Ig&LrhV4Yl8-@3*t7>&|9q;*W7GZtyOGYO3C3#|Wu&-K z;a@w&DnXIeE2xvYB2pIWd6z#EtMQ9Yk^S?oj__mNBg_n`K5IEO%Q|bdFI7rwv*;A# ztXNO$zkHY(JZ&0V%6_EP9(k(We)y=Q&}w;kfsKI*3f9<{;=rC`Eiu}kN9F@gT=j23OCUwtvIzeLH+Ts z#IjsotdvPF$yTh`$4jDnBnNSy44sJNGypis8qM+)9E<9+Wqd6033)|So{H@Cjt+;q zwjMf68rQVemFsQD(M&Gwb<)qY_XHP@$l$x`%AhzhmBk+DeWoR}R2-C_ZYvJSQBIVH zJRv-MMX>`d73gJHaUKOal5I2-0fyx{_eqxQZZjrBhAreb71Yrj5Ydpw+~LrAJ(D1A z4D>wa_{<-iGuF7Y{ys;wK3boLM;d)85jA0!wkzvmRxUF!$41D9F=BF*W&*XO5HpdX zBk~{|-Cm;(>I@ajLsxYFRW}E&+~&-olp?n|zQ@m8{RD-Vow;J)3O#cbAKd8Bd8-Ry zlX5*vbWmF3pRzbjqV0jWg-)Kc8jf2jO+zXM^Z6=vy%rv%$`C{(Cx9m>&+x+2?1tB3 z2=W}E`mdxxZryzV9ifV()^U7lWh@fW*zqY%`JDig!?JKV`>`nlhr>gW1naS>liiXX z^)h)jYGqtdzN3fu@^mExyBRW=JjI_!J||92gX&>3)AuCBxf!VPh<*}O`G#s=nt>{h zxKu%vK90H0L+!~xvT0vWg<<*5L$wcf+|6XhVgmhz&j)oljys*qs7f4nI+E`p{0TP$+Ju<9~&x$Vd%+z2^Jnd27l_m|cloK(x13hwCTR6q?N3t&HoT{^#Ff*W!q3 z>MAJE|NP&MW^A_0Bt?ncBcwkH)C=13gqV9**Ao(#GGdFxU?0dUIxdjECc^>zdvao8 zPdeQ$d>N!dI;&S>#mrePbrHp+lQFR;;&M=nm4odf<^^)Zj-*>5ZYY*hGG^pls5sJ} zM4&4^v>FF1ywYO}$l8e+BlZi?#j6}GWsn3J2X`gwpHCI)D$6&%^=j;f5;MXk4s(2~ z%6L7Zz{z*U!KILF_U?e1^7f?;*^MG9g^~<`t9Vv($kMW{^B#i4=J^1tB7Fesd1OHd z8RcO?$il$z3kHSN_p^&c_-i7er9(-zWntVeKU3=o#Lvj*A>^vd8^{K zFLjbzZpnzaYRjd^FZLc0iymj7LI%N{os|l?Nr&hl=O*2;tO}3IcH(dsVmsnr$q0TI zD!1vck3tCcOIY-6tk?$@=|M10g^NhE0GQfH2!XZy%KIqAH$y^r0~$}krx;dlMz9t- zcm2}QzjHK`fwMA*%}GD&^qD~_P7y(H)tW(xWI)Wn0UvS(#Gov7TZu^63sEHU zgz)f`GdiS9Ct}5Ulu3__5XbYZxSENPR)}=<+=9BAVzbX5RuNHn=*KVuFa`OCPT$Cc!PSObkTJMZ!j>GB6tJegE(GFS}H|HcSSG<+)TN*x{vRfnNsKGOOYGAFxXX_im%~% zvNGk~isIBvUSZM&_YcC%Yd9TvZE2@%4Phu=mj3f7X3&R^6?BK<`MewYu*iCg+xBaWKKPrSXQ<%_}CD2IKRE?qEtWz(tDXKCGAg}45S~<&{q%p zBdBr@fiqz4feZxZ)9bDW%-M!6J_0EY25M*BzL|kD{zK0E6Jw z3#m%~Is;Xm=hece_=9wRUb&TT3G=~`70sa;$nsLT6MG;V>B@R#Z?jzAS82|*I1#z# zQZ5+TW-YN9Dx*%@&R27m5uW{Nyj++IA;Z$H)+ zJ3hhblRolvKqSoNjaF~2OzaQr$#dg9Uap(Ig^6sQSCDLqA&=9c9`HU`K4$Al_hHPm zUHE4%grtsp;EL^$%6R$aYORiWtZuGANK>OaS)C}?F%!i`%$>V&n50-9W*LszrM^=`GQQA{Ndp_3 zhlhqR7^`RROdaa22S9VVI?=$?v*Wc{Y&9xX5$!{qK`d_~y++WEA#|?TMtDp&+4K;p z*3m|0#*>kjDK6ZFb*sMeM51dV?YkYMSe6Z?a0L9&JUDUr^!|PvYs)p3LI-4`xSi0P z-DzMn@yo!9kJU!!!bcYgn?6>XELW%I!e>Ket>tLs_fJ>A#4|!nVOO)|`qbPVVH3y> znO$Q+k>Mm2XE5nA+P7SryZR4L2>Pi4QkqRcgw#gH_W&$$4o(tZOZqDo=tK(x$?f zLWf#srm{tILinbk2sg$m_s_{A@VBY^_XHO-x&u@hXLA`%9HHcReM%D z#u#zG_Xr7I%4F9cVpXI+E3l5EOXZNjRu&S&PLV5T1vvDj(DON3s=Ejo5cn7UNM?eA zdhOceBcP)DQiqpA#a120hxv7w?zJ%yG;FB_jtcN~Ts9GHU1mwd6Q5l2{ zFV@@^;c3{7bXu&kUe{tL=dD`YzEsdwSc%xVD$J$0OS}gqx8fc+S?aGZR@9|3U(2}4 zT`GwQ86W0X<}?%Xgrg-NJrqMORk_Og=Tk)YWAk zHR~~jWT}5mvEwb*vd+FR))}+ZyNczqTD!gF&tkPQP$7>r-Io>g$k26M4&@A8zn9~t z&gAudpje5L>%dQ8#Xjpm&6(QjB^gO&u3Gdvj#L8SzZ_Hijm2f<{pVu2B99||-BB^0 zO;$U4N)Usqsti&u4{UrDX!LVL3f+@ZkAvTBsV66m2+k)@2oL}07!IGwPcT4A{~xg8 zJkr0~kd!xnaK%w2J2LU zsMnfqqGYI8o_(YH;JV3loqc2Of)r=p@I8L^Z5Abvoqc29I&=0-=SC^~ixa@Fadf>> zul4g?U$!lX`>YMv8zh?v{59NY#iQHNEvt>Wa<~94l+rFu8{f)zLO#1RVF$x+;%{tg z?$D(0w2o!OOv0tB{bjgxwLjVmhIS}9?8N|IT#GM8psVgC=m94|i~O)fYgwZx&*SljVfZR84EqWEy(=7L z&&Jqig?;X2pZnl59LJ9;eC${lhWx+h!GF8L3HCY3KBw@(qVQ~}ZpYHFhJP&$r`e;M z*k>I+Zx4In`S@(ufIq?}e$3#<&G<1J|8@(0+m9d5$B!4_$672n9KH~~?;1aLSNJ0M z0 zw}<`scmO|M13%somf-6h;a&Lt!f*}y<0O1~GJc%G{tn>t_V6tHK7REsnDPG%-rg17 z4WA(OuZ90!G#9>}|921n?@j!_d)a@b@OD)0+wkLd(6{h5{J0hWd=Y%igl|<|yMw)Q z2YY2ndVPpo7 zCFkJB)N=S};KyMr;NxigcpHAa3qJ-|!pC{|@hSXx5I=VJz{hp?ao9odaWsB>20tFc zk39#&$FuO`s8#TBJbv7VA0NSwFCPXUU&D{-!{K8FKNcJTA4~D$Q~2>9e(XLHKCZ)$ z@8QRf@MHQY_?W?uV~&Q86Y%5R`0Y z9e(4^%g|wGOi+jYL1f4m@B^=>5818unC&rSNg^=Y)%a`v z--r2s_w)Zg%K!UY{@*9yKd=v<#QzqAe+U1CpN5YYK}fK;DA1bXsfpD1cJD^v}J&{ste`tA73V%wUv<|=a`Yi#wmOP#z$->g`oGdRthvLKt z`!s$-O>cMJ))jh!vlmy&&m=2uJb83Nk^2#c!(TXycbdf=AH33lxzT+h_N z%go^+#;)xy?}K=@GTZ}MoeCJ_;At}ldbT%%*>VG*5yU&zrYmFU1{#M{HgNqp-We|g zAX)eulxE|`O9#{S+RfE5C^W*G8NPOOe6|9&(ATSt(Q#<;`f9xi-^zQNjdFc-yn1s` zg0WxIbJ2LSIo;T}cJ1u!?7+;(z|2$?4i*oLj<1~=>7R`Y^#kkHLcAPy{!(DVL~z{{ z1ZLJ7)#mT<*L5$JVI9AgOq?;?(bZ8=4 zmaJIm`by~L<0~xFFcm8ob6d+RQ-EMAjky~ErZ2|O)UyFbL+JB`RzQn$S;VW>S^RD5 z^*!p#m1HJoxMAi*^k^e^f#bwwnvD9Up*cRO)54XKmCZ~C6 zaC@wYELbUgWEY{Swf=^SPDgfZ40;(CdpGw57mZaKqeG4Az9Hc9Xk}v%zY_Fr-VEzc zKI@93ElXKB3LLL@NL}cAoO8%gnax6a98pAClip$1Oq|I0@?&5sSAuJkw6@;lza%9B zO4p`&=17B3ix~oDgVZAt8HDbEXd-P4kRrrBHIodB705EUc~YMl#wTnb z34EdiHhff5P4sS1dlSh&@3Y}K>EKJVbTIn`bL~6$1+9ZSi?lQmZ<5#19j`P)jg>5; zXX98Imx>*94~I#-tM9jhe>YJGnB_-H}fkAt!nLI-#05_$53O&Fmw2$(JX?-dhU_-1?PIXxQQf;AqjsuO++d=Pl}Bn^ zX}N8URjm~22VRwhVzkm=TA=(+So>$ivR0myyORkcN3e;La`;frGgK$iA6-94VIP$w z@`UvFJ&u-b%L#|($dhyLWc~H2LJg+(J<9%G?1pl3j?MR&!bP3v<#I_K4E^++b1azb zn2xizFI7q{%w&LE6(>*?iCVSq%(5lptGowDSVwu1{dQJG`jhM%qXnJ3g>nygscRS( zqTCY;n?c}@u9qS7mj|IJlW9`J<#LcHxcr&0^SdZ0F`}cI<|C|v7gE3fFXsglkc58Y zLtCrq^_#4pSVPHW(r+9sW$2&O=f_z8e5z3OIs7;j_c+x4TkJ-9eO8&T>$8*hR()^(phea=9IJSuuyRx0G7IRqho+cBae=R>j!4%=?ZN`*Y>L6;NM zP!u1`!8SIxRmXyo%d^P5e_#v~8xlde34tN7kg34t=7vmF3{=OA`$>zcs_3v0^SW`R=O8B`mLIzPV zn+JDOpu?hdRAOXtSfmSsMdHSe1qPMpTF9+e&V_$KkH;9$A+$ldH5AghdR$5R=jGN| zyA^{os-qB`!I}@YZwpG5fqeszobifnJNwsNFgVyhxUPS2eSdj)IC0DS@z&yWz@JAb z)U;2}DR7jj=~&vBi9bxIjW*YCH0Lwr3D(Pe0@x4WFQZ`*aDzy-tCJawuxTZHC?D`~ z(!XMstxI@58dYzX?7=LHq>p!zmgzud;H+G#-iZ~nrK%Kf!&0?HK$GT)3Ez&Dh#vSl zq~Bdo&s3P?x$F_0OOKP@Q4q~S&P@_`IG4D$LQ4ia*?xf{aR82Rb{wFIh9K)zgaMfd zln{Yx6_xD_`YR8EQv0k+Im|zn+DqYwpnt8EF&ePsTpfQUdy$_8{T8x>od#v!vY!TJ zXFJ%^l3@mN%A-SwX?tai2X7Vtn>J;GO@e|>VyER%8Le^m=8Bxen3+h=nQOG$3(d>}8W4Xkw zg_aC<#s`Jq2wNvymyj@TgeDq-tP=z*&?Pm;|kDCBs9@;x6)bOwb){AlCQSsuL?p^(=BMZ(ybygJ8oWA{SRQ!f;9M(k6< zLpV3hKqv&?d95-$5@6c;c$1amj&w!H$lpr{q?XEpKV6o}wE8@Ov*L}UrEVgP`$)^V z`zVpdUtz`CbmEOPK8=-RL>iwhsAt_o8V?jivjb_I#3kp-v_|UM1GR{p`0fZ~6RS=2jC*)JBRS@bpl{4xr(o#23#}?9Z z?mkM?u^B7Qi#nc;m1IO6&nT#8-9#PN7DTfHb)3#6?rLbsU}Jnx2ad3Hf^|*bCe$Iw zx{xp+6M+(SP_3e}ZNUXDvL))Ec1@RZu-%s0OW_#wFBR&*7dh(qQ~btI2m3a^PzPjl zs62_N1Do$C>R=5-7D`7QZ^w~S-5N{S>tv332foui)bZA2a|-G>)1Yu~gc6-W9h`S< zPzQS{LLDCgS{$h3Om5oVO#0}BI#%zV8JU0(6?Pm0o5nL43}|TR!u28<-%k)?s0}Iz zL-g`)e~h%$O$_l>(sJ%TN(}L3tT+XRScOB?tb(VB%aqVLJ!a$kSY0YKp;bsm^v4DD zHV>}Q=<|qo{Xs$0I}pYiE|1@ZmJIg92Vvm&TPIW3RBb{Sf~+qP24o^oA`Gf?RPrqt zz(ux17}SR8IskUsQhO=<4fHP+!oU|f!dN{3-x$JR-{uvAyTPW2 zAuvAWPNEHdevmg4*)bh+ti!QXF^5`+*s*i)&F&$OwaMlbNumCU@yfbY( zujPiZ2K+Qkfj(dv4UrWWy2vttWkJ>rWSBD%D8Vu{{?r6#;HCu2bgI>jF+dAT?Io_I zX@Z_nojF+k7Jg%3nSGmAVEGD`UZMz`d|@`6%-ZiHL|)2Uh!C)jjvvFpQ_-<_pTMDC z6QQSaYMBpm1v@93Q-JY0gVH?;B?$0mWX;GWwjCwLaY;bm)`aCjXza6c!#^n%0Z z<3)bxh}#HRgEtPx0Y|$bSIRIXcW5-rQ=^dMcW)iCdpFsY0EnMskDbUhx+1EE+W1vK zzHJ$##(k&ULjGBONB}Gh41t&D{|;F}VUw zvk=Z=M>O^bqFL?*4Pc+cg$l+32H6XlxG*zH&upxkI!+k zJizDm1rhBKy7RbFTnjDvskW$7o}a@(wxTANoS2}bAnRhnfJ_8RC`lEIigyMTmQa#T zQ@WtTWNfLu6plmxQu$r+MGhtJ#BU5rvTySWNQmDBfG3L;-N}xFnp6_~*ux5!WI>`7xkJ zm?Tro`uW_AkxcLeUiYSv5uk^_v?rO4=invh8?l@I)K z*!>GQLq17b>L%Fu25C8WA0^oMKUi@Ju;DzZ|0Ap<71l`RM0&KKp5=iWymRTjsgD#y zvqR!8s6M+(GP_3e}ZNUOAvL(`>_Dh#?u+x^> zOX2UJf2oiLzQ~cr$%F8XAr1CzULlR`B9mJYP$T0>eL+DdvBvVKjmT8#VBmd9 zxfem1ZvkBnD6@^5zfY0=dO?}FJ#p6MXgiGy_}_br9gA z2P)UX&z1B;kT|XTx6M+ zT!O4$kXb4dfpW=3CtEt@W#FbX3Xy+TCL;>rtQ<7WP8aLfbF^z}OYV1Qmontk&lUv3& zl3shQ#!lYKr99wLZVQ0qSgzg6WaR&r5JgRy4>EI147+&u|Aw^G%@xW&l9qG#QLa$F zh!v+;p&ZFpDDk-pV>-$Ar_ec~+P295mG5>bNx zbhPiI1S-?QUqPiF%M3U!07fuCT=k`!*jIC-Iu( zim6%?j;R!or6A+!E7pD|*C0!I3(@*QUwiDw!Bf{B;(apdzQEra=2>&hCYw{%9M>3> zt_~#%SaTRoUkUEH*BXLn(Hi4UpvJMrxP}|JCh4Wu8e?D=lUg`_H3^$K7`nnYeD=YP z3~bem!H!K8_I0Mqb$qDp#rPH`lJHtWG__bh@XKNU_i*<78EL7TYmoPnmUH(}u0h_7 z6{lE(tYm8t+#0b)rF;~t%DnRUWI;Vt0b$2i9)If~+Pv~`mS}?rf%_C*e#6nckL0U7 z3%>}JdMrGAL9~+rMnvc;VZjV8AZ0pT{|v6*_e0kWmfQ#5p*~rmjEgK2z7u4!LCu1?AW~q zD5O5oP+obz(7_INk?~S?6RYKzF5Iq$FL72JAG3meD}oivkanCDc7>1QC(Lw@uOR0( z<9k@c-@ywn0(biF;eVJL9J0Vplc>ETPZT%;ZR=z7cxYRH9kf!!^;gS_&W8 zMOfWhf5SzmV=p%by^J}%oBM)`#wv}`p+l&4MW9@Vm{rT0k)ye5PocyZAU;1$X`pcs?%~TugKESm^?(n@@ z9j;butZmGe*G9J}Pw(FdC*fh}5f0-eYo00bnXEDu+yMq7e5W=lXQZi*4qUn-L{P3y z@+;O0%h;B}4?Q-%l>E4Z5y7wTG z``Pr}`x}q$eF5H8PspE{%6>$BE`?v!%CCsZ%U8asy|NfyiJo!=XTK0WHT?M${s7u> zs`x*_3w+pXP(6tFPc+IFZlrX<--N1{WT5Id?WlT0r^?qHzB&U{9_CQ+Dlv+XCV2%`INt437h#BvH#Si#Q1w;wvR9oP{kGwY@yMZu8daqR!8UVXsra3IawQ< znW)T#{{kqawE}+NP-vI1P1}dqNE8`cVceqw6zmt(W z_FOx#E`T>jLiHIY$}=-i<)N5@DqqDsCj(U;iYcf{C}y6C^0Evh=TuB2Hfi+n?huIJ^YPrLA+Q8_5UN3RTc@cI|d>-0fs40T5IB5a^j55 zQ~>LxpmbHG*;tFGir3(2oXK)i+mi7pN6g>N1?h)iO6;wj4{LNjSPjAf7_4(>&a-&1 z!VQ)OrEn8{((2h=uEPzf=wYCb4t)SG9s1SUHr#zF2F-HsKr9V02GI3sD9 z-DEF6LnShPPzr}FPZC0W3M^2A64=2tL4SX+HBN7iTa3Z>ZL#Q&Vu^Z}lDuQN(@Bo@vKKR#7F2vPo-Y|g7=iQoX|^rXlvC&FV)2#YltleN&i-yXKyJ( z|D;|ng^K;Q301uezXBXczXKfhU^mk1rOJF=FP*$+Tr}xr`%(uJ(IkW5Dx6Crf6jy~ z!#HNw$o273RZf>)Pt%y;WP=mpY}L%o4eC)t5_w zH+YYWWlGIJ#RKuId{0(0BGdFDV)xrV6&+zQDGXlRmX!(_ctV#&bT;o$c)?a&HKf@j z)prnsauYUfM`oH82r)Q6OAQ0Cqc(o+uI5~s^KyK^v^dnba1Vf4W$hlL{!;iB`lPi; z*|7U4eHMNIHCzp!#g~c7+=p-Y0joT*v+qwf7rq<>if`%m7?8?eKnXvuOvqLPxXHx5 zE$DY?9jT8ij*4`|7U}uIOeSsmYmTOUW<4~WJxh2N z3RR1j?AbqEp?!Rev`hyz181d==ijkn=Hrp#j;z*vJmNYkL8az-3>Q9C+MzbvUkJa* z=qhs6caEacoIaffh;`y&(l3?nQt0giBXOrVK|AnRvD z91Ui@y+cF71*%n4wlnCj49ukVT9PB$p6_I}@3;rPp%KID=&V~ab6rzJDD)Kr-Xh}y>PshXna zT{dR{9Ro21o%Fq|l&wVON(T<#$6-`~gHndrtsmfv-NO#wO*W@shhq#X_$??=0CrfN z@Wd!gj3Z-A4jF}W5rX(F(Bwc6$8e+fZPHgS1X0?JaRp=LW;yV=S3P|(A_MF;xs@vj`wF{7w^~V-nQ>!eW|={ zhiEmDG5v8t{Z-MM??#eU4qxODWAz63#vlg!Hm@MY_Pvl~ zZ3<3`6o4|kFTq5w$v{LvCxOO_xRJ<$=~!bOj-iS*WD#P%&cXM(=YLz9Y)*k2ry11m zEGSU`+#uIr(s6R{-bubipyP6&$N@S|d;J9npqwU*d zyssi;Q43{(mo5usf*@yb23$#6>Lvs!la_P$Q9_XCV#O&S2u}$ov`@z!H(@2Ia7XeW z)6E6-EDzY=olC#V*C>c)2f}y?m$(|VWUwGU2m?phI+?l@!GtgbSvL>{WFk-^460RB zwk;UIMYcp3)L!XQ4wl(cdntT5^e+{{z!y2fcprXa2!nl_R|w;2BjrYAtXeMsU|f{R zjUn3aB)(W4wGkOA9bSAI2T+98!QDabOR4nfw>h&W~<(U=~50B+fqBkBEtZuLLK-bM;#mS8$%uJ+q^;@+bdI}wJ}Ving2Xj zHorzhKqqm>inx&oTY@=MAY)(NQ0|xcG5=s<+Jd%D5 z$wDXekbH}<$6lbwfjz|ey`7||UfAR4%OV*D-$vp9Hs49HyhFxzl8`{HQ9)>8klXf< zwA4*B@ejD$4Am3( zPWNEge<=T`*34&La5U*~eO_AAmuFZi`)4{Rqf zdA;tM?Lm_#!D-jy%#u-U$c2Pk+Qz3?LT#N5k(**_5>~Q*Q z;1YKpv}CX*KIj5R*gBEA6v2cp1X-sN24o^oq6?~3RJJYizl&^%E~pLDr5voZrS?*I zCG;;9y1*AXx|qgq3|+8q^9o&TFOQ84v9QR}nv5U=Ql^gA#vqALw)12$0iA>y&rVF9 zakC}X`;EbiZTKjf;%x@f{_cot*jFTs{s+K2%h*TuZSNtKDk zvNB5+e1q`}UytJ(o2Cy!_IxGLTx*FsS=f`@GLymICBpN&P7BY8kk^e#R*1`%l3rC+zQS6t-d`jA5ik(B~M=~^A%g8A9o=rQ1YfU z6()#~EGp*Y3I+G6XFygwo>Xnt0)yph?juY-z#6>MY@ zx$5zu&HUJ>as3Vn9T*a6&>s?sm~yml16n3XBQ&5M-z@Pw)@z?CRN(39XbY#Y8%jtw z&W54=W(jpj^qknv5#dWN!_5-yOP!fcj>+h_YE58~_P#sjfo1`i*LaVP7+{%C3RT|f z>2g^~=Kf<;Y|cuB^zqRti(2pu9Fjgh3UYMY=DPj+WzehEGVZssU;mA7aR=Xr@EddR zv2XLr!FSncm0j#(TM5El$^(&yOGll68EUX^)7%fvy=>>v5_>?O~5^Cn}boPN{y zdGe<3sCWLCeXod$(hPZDLOHQ)MTWj^k8e0Cvl(pfT+++aLG`|L%nz z{=nZs^#Hqk6{F`OyOtH87=kL_&CKI6P-TK?D#mg<_t@CxcF*wc`b@-rc7O_KqMgrx z40OXiW1T0MYi0usp2;;MbUuooxBy-gwohJGpeP_;NPd=R4G4JpizIumsWfNmQ_Q=< zmg!UIdZ|pp{d>`2T|A>3ucf$UN?nTBu1g9MSiz7uc4Gb*em-2A2p?d$beGE4uc+Q! z!7(W}*|nZ5XW>{an5s1caEO(w6L6xbSsk6I^iNc7u1o}t%Dzc_fN`yTJ<g4pq{-9o&o+yv<7f+i$tp~h6d#kquv(@H!a9Z^=O@h8X-@Ewmt#6)w_||(#H{rJ` z{HEPmaWxrkx*W019ws}ji+#8YD^?v!3Bj^Clu90#lrHBMLc5_f{*`F>Xt7}Y{j9XPd2AF(I`qeD9UvkZT6I7fjqD8K`ku`p>S_qO{bKl6S z@Q{gvqFDnkFCmU4@`Uj4u3~v8W#dk)IFD?okk#&AV{RVyD(Tj{3+k4N!aO})$wZfk zE*!3|KZnPeq)rO%TeDUn1sBJy5W!7Ay!Ku%=`5l)lUnBt6-#Fk1!cN`x|~HH0>;3; zxA276-9`8wcNcvNzcF_a`!=uKMLY0__E6=PY6EusvYi8rSOrd_OR{*KSb8HtLML5E z$xT+J?~xrXh5C}ZlMbX`qvRCjK>ANdWj3Va;Xp!K{S1a<;rNRrt*v(-zz!rLDY5rO zcOxA{UQ^F8kSAmYJ8Y9(1T3SL$E9Roe0V655O+$K|0N@}T6|c~-=}n%fB$@ZN+(10 z8GK5E8L0Bm6+xBTr^Fdcgkf_AvP_E=70$SwN}Pz?UM1dPpBdZFI8Yf)wDTyKlWusG z+DswLxAa^XLz8cbn1IN`G=$r9;WlQj#oyXhdEimg-~aH z1aet*Af|qZ3xS6sC9UQy$XExF?7am^w!RFXZ?gs;&{4t-7|?FJ3SK}6pt##I>$o7|x0L&ehLKqqNkMqM6< zF9KuIcpUIO?s51Peq$a7_HABy9Cksl`4FBN9;)KW;QYtXwq|lVP%1j`a?3A9kdxF#o}SGO!Rq{m&=pQwcdLGuj_Tb#;aic zpxL6*nwaaqgM^7Mnk$9BhyQP!3mw)cvB>#6oQ2{L(7>tic;sG8f_|VRm!rIt9GG^I zD@MSl&Rg!4WijCjX8_%PQ&)oL@-#_Xo+l=NMbLaV7M7>9u@Q4TqxjQ+9MjrTHv!SP zl2hAM3qqY(eJVo8C3OiA9k12rIa})$Nc+jKyq+dv@65zR5Y{040S+dxOyq1n(AJ)r zY9JQy>4bbb;ae=AQSuWj&-rK9_;~Ij40#e3pL+=bxkp4U4o0zJwm6VN4Avw4;6DsyInL@NBR7XbUgzCeriViO`=ZfW_l%%_` z;yjX+YqEZCLETa(Yu`mC?|`z##JR_@#(Wn%&LnkGaQ{4O6;g0<+zJug1QKwdJfS{v zSDRW`YTzFT-uG{p;3R^W+++8i6b#X;x{rk{T4^Rd#lD@C7U~-qe=xA}Mph*{&qd;#GD4D%;f z&@3l+v-|@S<>&7YMk?dwo2#&oJXwJegwcigdECTfLo7}=@XJ6jk{+VQPzWkxJu}mo zjQ|}5n3g~+yjUTgjG`|XnP~>KDTs2fO^Fcq;b9@F!^1uNTqj$o>PF z*b;Z<-u&e7lb&n5nv6Qdui@7rSV7HmbC5C?v5S<2>n@MX2~n-<9hGPB$9xTAC)n9# zq#7iYHGPCZDhL&>E+(vcd+g5^F>JkfC3cTZ%S$;~I~Z9RdiDuWHIo){*dMpEE7N*M zt|_P`=annw-dcN!uPECdt9~&uWZ4nWmp{R8Y)!$wb!JVGVelj`&ORprd%|7#TZFSL zwU@%LV79EK>|nJ$WEfxKtZ2ooVBd;h#j-U}If`KH*Vlm~3qgk8m z7bn4iWFq72oq)d+MDLWt1)yuN;jxJQ$EUXOtz1MuQ%Z8~8~7XJ+)B{P9|CEL-G~#9 zl)|6VC#|DkffH=2G%*p;-kGUUoENkMFy>c?hQA3}TmXa>Q}|DSNZ!>qBr6VOR}Y1* zK29gexT|6Mouk%rI4)DKRvL3R!cNm-*fwgNHCY>*nW$VCxb{287~AGrYxv9kDc&8w zc2{$*EQV8D$zkYY>y^ds<8ev{ruT0gtBu0$CuWbq$sbY`!Ork>t=_EE2cj>H0nA$< zI>%%R%wFQ;-j!_fa(h~y4LoZ&a+&BcOz4^5F78;nx;7W$-hklOz^Uf`YDTc@{Y zx3+{zRf34_xBW%86n+p04ex`GiqTz=Sc|^{-)HFVpOekC4(M^+0q)pP3*-pDg}opL z1Jv;({QX(7!|hB%p2!UYf5S(S1S|CUKk(Gk*Kt~Vxtt3xhQySh0Z*)gp=bPJK8^oI zTJXt5FJQ$S*f$46O|@_#2)bcM8X2F4u-tRBTnE- zW#&?vnwcD_)Hepb<=%Mw_g3o-$Zgs?693fGsDP7dtS_iEN2Ms}E~I%vHgkf!tNSc| z_|`X+AHMY-oz3v`i1IUKHIdNMJ1?Ot`OI(%wB)x_AZ8osm!U$=T_>^e#084&ScgYg zgqk^&AP4mUVd17?Stx@dHe$uh$rX2U2YTGBXFEPaU4;s)9kx74Ea^^;BE(8C^J>XQ zw{MH}G?`>{xhSZ)B%@ym$DUa%$7G_>Jxs>9OEQsY6dy`xLXENgV1k}#lmwbsvLsA8 zTFQVFWJ=MH_0OjYbwkiIBqgk2HJ)o~jP{L1~n+Na< z)6CsCdBg0B+c@kUz*-atC)IQbzDRYH|3nn@T{!YFil?gLBYmRpQbFI&`_4u^p`iHC z)@phMT}2p0yQ)1Seg2rE^$dNMN_#)+vriSO(uOaE;_2`73_p(DNT;;wVCYKg91E+` zwlB5XA+%&TTvg@L;J0`WM{W(iJVM6tq1iDgkt6U>QC};TuBaHLl55 zG(e?&dGsq`>|-b(ozU%nV~n*-zWC7AYI@yHGx;vtVsFVu3B{;OJzfkI`vKTIdb|R= zkxq|Q#_M|QBR`{iI-^Tc9QEq(L9p!$Dqa`2BOw8eLV*T@}B9C&v4ZD#}xmCvN z%I)O4Rk_=jTI(yhL+zEeYS3R+ez+&g5Tpt~WRL72~9Rh}d4r&0zZ-n>XC{{nD z{(qJ6-lG5bFu(pULEh3X@}Ec#A9b|lqrM3X@CfUnPZfDAz|XN8=`4WCdffszId8Q9 z?MvO*Sve;o>Y^r>z4)^Ch+6DL1}Ywiy?Q&ct~2CeI`sEj!#|Dj8=@oU$9GPhpOp%E zn6B+6%g^3PV9PWA_re7WHgDd{&0XYt6OmoaFWG}ntHGvVy9iX`OQ2FuApVI>f=#{U z`beeP+*g6^!`{C5G1j>$s+hSyOY1He+|U=C-xr*_M!qN99Z(IpOwa;v>K#}ML1$|r z$_(NL8nvO?Xnk#CbbPj49~y*z=UuRF-Qdu+Yo9T=ZU}1}cz$(SX_v#XO}&WXhS*Li z_+nzJn5o`$gX{%%M(sgAR@pyze*fUP{pJ2?Xm6cToW1R!Wo;W;hj9K`d+C=*(VwBO~Zru=B5%uWUal#-QX+BO$c0t<0%z;&^P+adC>QE z;0}*}XWxnl`u>0HeF>ahMU{8+aj# z@ACS+=DpWR8Vn+cCgXFZ`4n7m8^>K4ml>Q97gQV_$7K{99UVV(MqF?k9Y#iHzH?61 zy>-uB>fNe)Uw1O!@Y9>ucd0sc&iPlJI(1GJMLY5(SzLE6JOG(uJGYAX+kWy5HE`o5 zFeDA)JMWw~2fAkXueH8?9GDFDm6c@;^$a59yzC*B72YfeSaSQpFSZ%CWCBx~X4Czm z@1=XRT@xBA*WR3=8GMX^=y?(S*X_+l%;q3!pqZ~dPt4|@#H(xFdAP*lGO3GD+@z4r z*7C@U;polCGiG1j5N~{-ohdzJp7_lKUV9Trf*-%>MYEKj+E&w*vVIj-*2Au$}b;MTMQO8SG#!sfx;zEqLj-x29JaBR^t6a=zWIi3ZK))~zf7$*?u!)Mk;{6%RDI zmvkxx&*sW$GUbaIO}CHwf#1k8CgpEiNcjgq zd9r0FXOs^GO?yZ*%@o9sSWsmEVo8-Li2vP!DgzKps-lw31>z;!j7^9{lRxamVmm`% z_&+^tmg=xQN%2y~z?F#eWziiH&LlPWnQ_1#5Ky;sw zP5BL3{VH=|ylfpM!7$_gD~^ zZ&2P~JZqZnh~$E~m>ARMUdI_TX2BR^$=RV_;$CfR+7|b*dY%V;W~Y<48aTR9^46`$ zOZ-zNZ?%qLld7a`NJDcMVbffTr3G_Wu;a65a&6i!lF1`sZLmj4&G!rAL78${OeGWr zD`P@K?2BOr(m@NwNChUAi#*37zVv8jV;)cX3+^Pp~Ar6O9}mrUJpjv2yq{6z$29E@3IN6m*L56bBuw2)pgs`5oV&ak|he)XF|cLxWQV5jG#$%Gb22)o?(@ScLz@l3tgg{4!G5ZZ9(02w?R_SndYO><+LgdQ_6 z5UM*0R<@}Xz-978aZbJjc-rNl1GGzj1f^%z`v1Vd>+S1vN8=iO>>c zWi%3gM*1T?7F1o@-#@IJA785m=5RyJR>iL4VLBn88cbjc;f?{LGj>NGK~~FWI>g|y1%5#G*axf7E~ETib<*p zjTC#A1<6Tsny@9;=}56ZBb`Y>uetJ>m@b-d&IVBxL*=sYK&HSO72+cCn*V~Z<^7wi zNBfe6#0;Y6{#G=@6p-JspvnM{k}6X`{@8*l13*fuqOx3oHcKE$eb|?HmkIbkEr`ws zNkK%e_^roCZ3@7o;Av+u$^Fh`PWs|1F2wpN7n#qI4RCrg6mKzb_F7P7fKy48DNdhd zL6recB~?+J<_fXiX+d&6l(x&0Xdhy|gLI|tv1B^E(-$l;d!P*tkWbHxXPsiIpP<6p9N!#C1;0z z=_geo)@^Ygt7j+JEu9Xr9z}zWLaZM|UgDoYh&6LvB!yUqyays+@d_->F2uS|hFFVA zVOJqItuZ~^I*3mZdSUu8vuVF%p+rgOk`fQXX%BNpmXO#NSmVKHi!A+KGZ!-MF~~PK zoD*Vt_qz2dr%@g+S4wnX#%6SKZysPF`2)036osYY$(von<&hD$?uzp_;b6~!0Wqr_ z7;yXm;D*zvjk``A&M2eEdI&^<(;3w6bIn?pI9{vIx!QG|0SLH%|9SuHAMLIxSOQKWglFs^eTDl=&Q}$*k{}WuA>BMifn!8B?^`$@_Rtu}oC4Mpp$*SEeFblU-w7*RHskmRMx`xv1j zvgdtl5N0EsvIFZqv;*)qn%7Xw3e1;EFoVOR43p+K&yU0~`%8Un2=i~U(dFZ)9Krmb zqsc@;W%Ct=?-R`@#fV`TAEwos9m9NOjA}|)0Q;JzE{hf_6n~joXi`KN#opcUdD2`c zCClUM*o(LwwbK=t3%DOO7D{g59zF?MOD{=>m5H9lz`PI& zP92c(VSX^P?$R*o_;kX`*clu`V%TOZQo>UTdDQ{-yL<7F4K&ts&dKQ-zk}bSP(N`4v=z z!EvcUY8JD0g1f8-nJiYL)I6|`1=AEb<2*3x+UQ8a)=Ml zzbIjgesv~cE6R;-?<42IWdDT5zEZDPa;G&}Tm^5y!Yo_Kp|^068&=63KNYPpT}s-h zWNe#o+<&G!26FEkc#oDvkBO^J>o_C@kMHR9SKy%rP6xh1Crrux^4z+fh)-#Wm}fRd zwiY#Nll37uCYsuD&1G}u9GLF*{uutfY>sy={WB?X@=vgIyz-t0Q{5- zSdYNpySyQKHcVHSt|N3Eg{wD)moi)~o9lVvpX=eDU0#K*<8-a!gL&SWP~2q;y&C?t z(3_w~H_){X*Mr^~cs{nzYrr2~6EBl^*^8Hb!M*+XYYHzn;^ij1tiulnycfXlU1Lw! zs~^CzZCyIh?gEH=%9B9 zenM0K6#m(H>>6)1+}!0o73)}qmzUw2ExepgPo9Q1Ps7X0@xvu!hYoqKz{jt|%X)Zl z(Cfv=eRz2l+&tuU!_PzBtMUE0-dg(O6nuIrUYUjkQ)4F4g^z_OqL_;LhQ9L24L zK@{4g@8SS9fp<<6rpsmc^Mjz~I4v`wL)f}GriU&Wt`yu~R#^Ch%KN@`i z{XY4PpG7Bn8e&4=E(VG1LWW$8mrKrs%TB!f1TVk9%Z)v7c_Ci9df{>mUhc-rJ$N~z z4=(HQ@)NxL0xxe`2bZ_urEfi4&ce(6czFOXuiF5ZH{zwf5ia}ivi2;v^x@^5XT#-v zc)9c$aJd36-^R;7jsEAaA5y!;j~w`_*X?Rfdj zv*7Z#c=^e>aQOvZ=52+`V!S+xmtW%rb;8 z3&LvhHDt%P@PfP0dbD{h21|MvV>#))PoVk(;-7orAArw?#6KSq|NN!+=RWbzUyFY} z4*x*!azFk9f`os(Pr>B?1dv=c?R^%0LOBnLfBqK!+2wsf{PB16N0`E&L-3OKUBF%_ z>Omp|}GSw3nEbJ&MXiB$n_O_6zNjO?4qd zI0_075625`U23jJ*psLLzI9C(BKZ31c(5Xw9WdfYLlaxq(oV0Vl0$xf{Lpkl1{5_x6h_SsRDbfJ===^pMZwS6EYD zT5MC5M!q;+aH#o9ps9r)Z#5rd3wy;;R?kWu2!&Gn{A%@x_j}XEP5%1W)w`P0B{8;- z^$8g!w6yhdbL=q!q99ynI^1)zN$1cSi@GS+Y!iBe2pua`hbsc2qNBsDmc=&05hP~* zimBS$;~G&6s@@i$YHpH5I-zRQsA}5@fxFy&jfv8PTc`c`ooOE3RdV5(612jaB0-f~!V zLy0OZhC}cO#g5WDJsU23oe)Ms){j`v^usvo0?4F@InRD836k=DfFzJ*fULL=x|fjA z+B!Y~jJ(~6Z?H!G+NP181wy>f!NpCqLu2arSNJ}KTeWXw^d!5D=Dkb6gXkGq$A`-D zyCVPPcn0^<;BEgMp-iFsjFn3+H8<9x5dhylp=fBmI8pNPX8%xA*Ji(Bu6ZTvB?-$KX*` ziFmjXG#U&>MD)O)WF0h zPTfTl52oqpTP#I0LD9w)6NSV{f@K*De`5vVPckc*h#x_Qk&va!7y+`3PUOyc&IDvn z)&@OXV=7n6&2p(yzLAnH$>}mut5jzt5RUK`>Y;OV%ppP7yYF!GG+CD~XZZB6L zHE%B#Oh(fD6t-kOf}?dxxjO9bht;qO@rNd54*0*wE!7C@fC!(xKSdJ4sh(}hVT@^Y zo-qprV-7-1X3KLDA}ZMZ_BgRh0O(5?Sip_ci@an+8+P?HezODHR_N6YT_NQknjnDl*rjcAM zHg08d6MZ_w3}8i^3%trtYpP7Mo|5bosVpi|4%-4CBkOb?oV&$TRM*g$y~1JuxI00k zqKidG^3l$#s0qPd??SM5`rsl-r%+e2f7IY0`@q!Qq^2oGhO*YPgITZB?N65|Nh$DX!qIWCkFdzMufxv5drQz?hmMvD+`{qWrb1I^ zHe818GmV~m8MLOjl`)mH^oKBxoNkUIe3({i`iwD}c&H|itf}fYYw5~jTdFc1j)#KH z#{BTK)HDV0iP(%##sf`KaF2)Z?1Z*-Aa_t$4AHCytrVyOOiDv+|J4}=ip)f$E83*D z3U{x>g~v%5Eye{EIEb(}^BGGj)cGm@YD#u0Wx!GgtY`=HB(B9Iu&MRI!`>$!ZCS8-co)Ds%Ug@>q5 zex8z&7YT*an3c-uCi@1P5NhYBIBUB!5+)I%lV_r$anX9cjCTy|hRS zBq>{QjlI-+Clb481(-$e!e11oPQUEK)G2g~%n2G^XbKQ82^=#q1Zj{O;|F&dN9Py1 z!2L6!eJ!~npX>_q;~= zIurJR)phrmhvk_o)yeTex4s$XfN1U&0U&mMU3{9{C97 z6!m+0$G05WYC&?6%RcNq(+71B4Z71ac7R7l8;-G=bfz%nGcgfkV~+~QSVC0APP92H zWG47hiU(aS5^wo02r&9L86F2M1ZEI!^(Uegrg)sNpvnM`k}6X?-fTgY0Ujk)F+74n z7znZr9`RKs-d9LWtdr4 zR_cqB-qhff%z>N1qGL?CpDZR67|13z+ue=&EN*HdRs1Oj^Q`zcneJX&EVopQ%5$M$ zV2+6OipHqmLj`v(qwFy%^TQT8RYZ4K`zACsr46DQY^unVacZAQ5#hPDX=F+rn-Pjk zp>CEUQ$qc#icASJKN^{$<$Y>oO8c*tG1X*3(0v>mvf8igPzOniMSF~eAp2Mq5_}~U z5e@uJN90zrOym+lXcz2VZlejw{&E#Ehy;eP(-NF$R2_w|Vf@lJgyPX%O5e; zz7rKA7&tkSOq5gdMdA1Df-lMV#fNFNX2&l#->IeqEB)h|x-43#5d1M}p-B;81ber` z=LQ>Ne*&A49l>GtM-i;$eky|7f3+s74l@nZm75FPZ!wlnZg8JX;D)t6ryIPn6gavY zGQe>s0o>(-mPm1Ddx$WHU5VfhzONaF&1N^hK^5r)mVZf{Oc{&uVOp)(u}tn@)@j9^ z)Ju1z*JJ`iDQ1P_MNqKWye$uskHThTM{=0?Q6y`5pNi!6U#-rfwG73&GIOE$(V|dn zL512gdY3H~Do7R0f3p>Csi!JPb+auMDoB;jvG{o3p~#S(rQzXz3Z}A+Pu-1^mLNR! z*|sqveE}hf(IQ&b+{J@A1N$x>=D8;x2%{<%yM=6aZcb|hIU!&RyC6)P!bC%%BlmGa zT1!=c^ z^8V}bMG4qc5fV}S=VEY{_n5#B#3j%!q{C$8ehP{Q?wGC=zRZFugJlRwmFY6XT^3Xs zEJH}DqCV`SlSQ6xJkiJ={fY_wJr)f~a$AJ$9Q}HJpIMKlU1~({L}=-s{K0ZCR6dG7 zC&f#%`wSM+gj7t-1C7^yQcG15i&bGMUi*YD)N7)A{UE6r>DR&24tniL9<(7t>&d#o zP6md56R2q=g=K}uEn~Bw zI*%+ar<)JgiRr;|a|gM%2=*UZKTlR$?uo_nMtN&bfP#S)WLU(GF~oj;%I|Xqwc4bJusvkD2D1*E5%T-sxag&?!GuK;_4{ZglIr(q z|J7-MYRfeDWIblm(UlsrOiUJMjx&~y9OabH1tFi)=kf=Tl9_K%QlWeQ+>qz3+?L@oMNL}n7XTV^6NX$K|F7UO%`nl{U6(zf8f z_a?!JB_cZMcRDGpQkAaS9ZRQJD9B=cvAoAX-a`2YMm1OAl>Rf1*XUrpy4E`md#{n? zr9Ul;d5j8A98O@9cQ>-mCn46m+y+l)W@h-rG$M@bAvWkj1J1byPCB5~JKUeESua%^7!Zb;e>G>QRIOFZL$HY# zd8-p6#R@5r;qt?*d6{sTXBcE%&>bu7EyF?M z<1WZiY{}-Y$okP24G718X<)4hQ%6z-cera3_Y@qeyJ8!`&@!&vG_1!NoNPL^YGn#8 zLt^XrfdM%L4GeUNWehN{Kw~NucUXB2qSKATnOM0a+1}aVC(o65kV&0mAhyHVQ6X7y z-}z!GBVPEIHHD`Pd^&)sz08nmf5x>UK`R){bxn-*@fmGwnhAdzn@6LC+#u`$BFmy> z9|x!th==RmpWl?HbFG(mdH z5pM+tzi3Yr9A;Yq3+}12Xz(zW?B~v`BxA?f?naI?WOjAhOAP79Bdg5}ZC!t(&Yn3o zUU2B_nWLe(ojH3(o+Z=n>=}8yR_n}6N6*N&lTMyVXlJn8S$q?xVKu(Bm88D|mt(h= z>lkb8h*q>Y?nJZ#Hr(2%pH+(Y#GYM#Er~P9gtoWtDSFx@smXfA9s#p2sR^(vbFDrl zR@iax-0UG1%NfJ{VZ1xGZ$g_*-z8E0_rm*`@f!x(3**=dbS`Bm&9iI51@Y>R5HZt! zT9F7AZyPozynS>;yo}X*;%#rwYWv+A5?JkBjU=Sbu%87_4QJR-%L4ytwh=z@j1s0s zksAEdPng2rv3YTbW?`gl#m|Cq2GEy%m2}GmPNh6L*6iKq;y-j;ode;nldy+1OqXCT z9OO{NH^^7h9dc9WEQ_f|g*Ax=^meMD8#;rvQf~_uECeo?S7tQ<^P*F?&(gf8S{lc5 z{w60N-d8%(8_r;Afp5WA7TC}qd{RhhshPFDdp-V-lb`WZ^TIfdTXyVOEK*2kTWuXcvcpDZ>Zj_*r zm!VH5HK3q?gw9huI555nUgjXdL@n9w*zWh(@$9h0@;mIvLuHZhzBqf7L6$s{hcq>%nCm)2LB$jN1GUejh&=HG-@;~u;t9wS#CU>T()?4g zl3~V2V+pi;PmLvL|J5?4m`uTRA4lClx>K=aJIPX`e9n&<3qTg3@;U#KipZYN*$uWA z>~x7&9GUy@he-Uwp&Q0t9C7ickGwcG7la=RbckDwX-8j~jt>)+lPy-L?1F-!*CJ8J zVt?>qepD`J?D07vs*F5iZC{2OVq+oXM(c&X4IB)g7fPd8uH-4xTL$}qkxXVWT!!Em8pEM% zoaZ0S7Z?}I^0|r!cNt&*xLD;@fc_)mYYL$8VSYeo8D?vmnoN)+kq5jt zU^B9TH_Z4bc(r^_1#kPW&Tpr{athMp$VmtN7=qb{ugpv-brt3U`_;xm%MJE(@bqnE zD2H=ehs$;0e)b(R7^#bAyTd>g9JZD)Qe%l6+Y9Ix^(; z8P4JJcT>1vh$6D&ZLqA-*1%8dcWrW6Ar9n4q67|Q|0ZMSuZvZ7 z1?Han{&W^7w*qD+ft$8{M))efBJSlTc~W^ zx|Pz+$U%Va0|$;euhnTy&F#XcWoL`SR=c~`T)h2qn26wvx_iSj*RSu{(6eDJ%uZ;Y z(zgzt_Tuf{^&8-S>u~adJ>!~S(jt1CnXOu=vtaZUfYiodTG#Rzu&9(MF_kAPyPBWC zt4Zr|QJ3rqR+^#0toTx?C_S{Enp%3da1$&oHJkM^1ckckC*q(#L4;b3U_VAtXY{Lx zP%DZG;cja5#TOp#L$NUDfn^QhEY0x>{J@+G<6xK#SysdQ!qsGrg$Y)J4MI&`6Rt?? z-q8=YVA}^kDjWpgsKIeBA(UVnJoRn;1FlRfPZjn`W7I> zJ#EB{2$q<=c$XKVk_N#8+*O|f>^$)Y4{ z&jkH73*wVDQwYyf^qZobo^6oOZHCgy79AGaE+xMF_xGfNDV>G6E}K~UF{@3Tl=23m zDz*>O@d*QFI@p)9xBd5ojT?E!bm1`zT`<^F=HEoyOhLc!YU6>`GXTA$$`tg+TTo>H zdP!9j^n7<$J>V0zeLc;B=zL%oMBKS_&fGcppYD`~e*yK-BKE03eY*3+SP=_FtX*L# zn;W$^H$J$GPnw^Zx_4ey!#O>gggiXQv-!4+QfHmV6*I0|ZO+BigxBX%!^^a4m;{$t zfByjxpO~e$6?IIp_5w^gA4~lz9119wx`uMaKV>X+>*%n>p0xW^$TAj<+>eC?QzNkZ zvd2|lc!^uVu+EXm>JUYZG{;I!i0bt~1tAladA@PmB9V6t5yArlzEM0d5JYOq49!ip zh4M7WNsY$Gax9mH3F1R?ie+{{Q+%4)lB9joGo-RCk#0Y>Wo}>gHpNuYAgS{3pPLF* ztuv+rm!cub&!#B$KplgWWmG&DgAgIdd2q_vjBjinpx(D%^h9F2*M`!@XoS4?I;`CH zV^FtydbZlQ4DV)b;GT$RH{Uv0LusIG#$kY^vVNA>40@bBk3rRp}EvmaC!?mGl@8|QN)}{2x^|c9kYWN@ zYy|2{Pzpa_dihOccrZF~xTNT1qAy;ySA{}<9SbIzsu2+-xs#I|G{!3+6uK|)3OU{W z07#LSJE{|qQ{&%~`!a+E#%R#i(x4#D#?XDn#a|c8#n6&4DPG!=u%^~%SEu+K-GxmJ zz`iSmG)CQKEnU2)P<>G$TnnIJ!yYTtN15)twiKHYS{zorRk%x{2;!fIL=amX)=7Za zxDbM@?r`{6fF-73PKbJuP*{V{B7;`#fn&UQ#%Q~ zDcux4OjY+(FHW?Z;?5t!R)UXbL{pcAyjAx!L@hKaf|*cHhR>5MCbFm~kC$F=mg=J} zoDn>R&B)e0g;^i%p0u1#?Vj3yb&^JDnZoIc%7x%djm07R^(cZZs8De#|6<#Zs5lig zlTzaabe5KiQ~9dsin3dYi?4DaFS{Bs5rex-g6~01?1X*zPAu5OK8*ILPl9YB9tabR zB77RT0AJ)o>ECEt6?0x3H!#5be***DDAa9E@EJ`-CY_1YUNxv{TR_lG8B}#2T7W@i zTHOatH8bF3qZAlWCkg?BMMG53R>DjzSYEd%ScMI7PheE}b-Ods@Ug`AyQDY)#$M{- zdvdQT{3Z6P!e50(4F%ljP{hzh39bUGz@cnD@`Tzt89(&lg!;N+4+vMpOF&$6EJBFx z$#mU**v$dv_nV!D+k{BKUA3wUvCaF3DwD(RW@oTgtH9lhN|grY9AZq-)C6QQ^j%(V zG_SQ@C%Mk=@n$c==-B6g8k-G7c_NIK#j88KcGOAGB2A!CqW6u>8xr4OrodC;ED3so zl(8(&j=u>K@J>Y%Qp1k+bOJ?6^S=si%(X9?J}}+weSbc9G?%mL)O*oo>m~ks{wrdh zq;4}VpKZ=%lX#5{P&|0yOv4biSx{vVh9Idj4MVunf+~YB1W8p?o-ETbb^b~=Sp3Aw zAq$cd-Sr{omp(Trp|3xvHMjtY`{-cFF4dD6>V!iR0`XdXf4B#rMGKK4JEvPbp ztE4IpS9E*I`1f{WD!j_XyZDccrv{0hwXnyw-IxkAa`FP`t$?t8PJ+!GIvCGVLjDv7pL8`z2M;0bv1pvI0@+M+EUM z6Yy&+h|Xuh2_kZh39lfvDZqrYl=OQom}V|%(=p*=7E~FG36{M2yaiR}yo!zq3(2t? zz(OqHihE4`AF`k>|EM5XS?H)>S6E7BL3@v4G_&C1!rkX>dNOp=bCc+|7NRuj$)4OL z!#!p2LF=f{l0iNr_g6rB(l~f6*WkI=@+L2RuDLyLFb+zp^pa?w^6>-Jlir8W0DF2yq~_+urk;wRS~kcL&3m9synu&GCoX; zZO9(T&(010O*}Pyrizb!J+;rIh_JWSG<^Tf*o;toERBxI8=Z$4AB~UI z@?A`>62kY}f7NTIyR0A6l^(M~GUgys(EF{%5|RU>3YmQ&6_I@>^mEu+Y#iF`ai7SlJhv0LXe1O#fd*7Fv(}z?hVLa~rQEWywT!$GS#kH32skm@G6hFCS9{kgeB#z;)=CxA=WLD7-jg(`iN5rdh8QeaP7Q^kT)Vx~=t4aH=_i4~yuw zLQA@&IrD*$T#a{VYO+vc)ooow?K3GNPq%d$HX~cN6=r<2+tTtqwcBd{)u}u5ipx|^ zS6nV6_Zo{NHei_*aN50ELGt&f*$mT!}+YjCGHM~h7eNYCI0su!++2;*jvRD z&0EJrauWH9nl2@A;q%hR1d~oys`XQWFyR-~(oYRPS;r$Ld|g=kK4jm0SEP` z5a3ueL}e**w?FrHTNJFa6#4p46ex33Tiz`u`x&wn(J3IZ6gQzRQkEk9Iuf%K8*T-> z(sFyNj%O*d+B%t~_)3vq#_Ee^DZ3c#e`X=gFXm&!Vn?|af8o6ngIaJmwTw-`9PEvPcUsievjr_ZpU$^fU5swhr# z4V;%)kem;tL~bsSZYP~70HlJUg@V+su#~9%_Gb8D7`0#Tv_^F>YQIXF6*uGCMD4%C zqR9rmk)%pL0=JLapSGYfAJk_lYClbiZKC$yX~7?3Dcbc9(&4P4_S^Ohv3f26%ck>D z`-5ooQPlpYk(>BuBx;{IIg(D12>BU=bNx~*GYDVOps`w6|QVJ8-bV zUbjBwG|J=UN~wpc!>6>-asx$(#zS-|g!t;G@!8D3Hrx|) z9ZM0WE|gfsKZIMm$rh>xY`9@D-<)HUEzCL7mW4YDPR@x1v97hC3a4C5w|G_Ft zX_pG&CRgy7^pF$R57>Nxovy+YVjDkcfhk6yQaTXR# zO;7l8tm_u}oD#gFB&MzhjMMBjH13YVr;)I*6(R52E|^GlvZI;PC~E z5lu}NU8xHH9isM`6cJwYFwI69!)Am+2G!^%wVBR0q9mL!Zk0*+?6Ny%{=tV;il{LSi8qwk0sQ!C*}^MxzG=`Kvk1 z6!dShk>E|J9Kpnmqsc@;W%Ct=?-tD`#fV`TAEwos9m9O_gKA1x$P#k7`6vbN3r=qz1S58}HWooA@Fc)xNYb=!9z&(7@2!Db) z(g`sCD{)oKI$~hPhiSED2XmGI`M)(ancz^0TtWHw)IO6U@<4g+^D{#^%=jpjwR}&7 za{I4R(g?L5M(3<63rQnfjf^TzpVeGe+ZJM7Vj7|Dvs}H_4~+FMrPs2cLPhOwx1~Y_ zALDc=H3V1ZXE+DuOJb(Go{H#D)czk8t5Iqmcr_MmG7pS6Sz8^7+8@<4xWK6Wnx-0) zZdIzairUxGPe{!kwXZA70yrvae;8VC=FybxRKP*~DFiqc4N+11+*`wE0~Q6VsC~XZ z6a|VdYM;#H3{m^|o`~9iE9xRe?bEN$MD0hp(e3r_277)b#ijRRqUKPOjzq>ai3S{r z#Jp(oJxb^tY6b_WH>OM897tG!MbLTlk5H4bZK7u!-VqxPy-B=BPU)k;WAa1TA1ZVP zNZ|hH_I}Ktv_uMyGf}EHVyE#&o-xDy-eQ6N2#_Tj_x^anp~js6jeBfKa^t-JfI{%t zlgHs-U>)N$=VxoNTW!{1fob|Wcz-?~{M;2bGjtO*q7SU*2~vD2j#GCq6}VuW*HZ->TaV^3w}_2y``ZWCd-X7 z>eM<$fLif_`>^D-PSV>qF|`>^&4%r>VD$=f!q``-tt5@YQ=g-w)TtN2Dc_{hxI#vi zID0PKv-5I??QRNArD#m)LVC%6E>vCKW2{|i&RUbSniWty z@XIuc|7jLf8ANhPs!XGUH(F3-5FIS3ib|6CzVso4^ePkYb_0{4|KA!3X|3 z0>7dAOvq&mAsa;ayP{d97~W?=l>vq&Ri+re-GV9u3`?q_80Nds=s}#YJI|G7Yu7Nd8-3+a9jbNw@w^t~2LGncgKi4dQ#pvvGx2uoCa$$~0#UPVuYSb%0e z@8ZNQ*1LVjg0}qUK?p{6c3^l)n;DSLn)nE@BL1cSvKT6oUSM4PjRjW?F#Ag=-ooyi z_b0}NzGpCINUBVGl4C8XGC+rL>DqN5GY7m`Iq>XG5E!!y3Ww zmczo`JhU(=lknRpB>dCQBs@Co1x`vK40&6{vdmAI2ORp6>?wq2@5W_JrzDr6Fu}{| zcKb&AoRNBM+_`#RxzTVga$y_nQpLHTJ~RedhHHadF41jTmcdl@`vs}0bcO|MSVzK2 zfDqz$83#a!(FMd0V8+~d3C>`u)JDrgxCv#&9fws}ci4fAk6mY9sR7I4b&$|7Nv~_H z*_>!>Ubk)(vIr*!`-W=c!J_E8>Ww$9>vJv$$-Qf+R)>|-iCT5I0aer>t8@yND&eqN z2ab#_4}ofoLg7_s5bk4AV{NiJT*5WsE?gdkJ0o}pmrEyxM*=0034a@m$>%9XzhL~~ z0Tx~~2)bUuW&f1N4oV;WLSIxb4io2-|oz28BJQgv1061EuhfI@Ztq{Fge?bES3 z)?sR52BKi}evXU_=K;o)es*%4R`#3Etdm9}*x$WiVmYajB6^`< z9}KZLYJ8X$?V3G~dI56GXMup7xW&M6k)|#a@Jn4$k<{C$g(gLWBdJYOn=Zj-gpwl2 z3`ss{Da`t4B(;|Fsgcy}zdFM}X_~tqK zBO9*6LWts8D}z*AxBsfgNPU@rbcN?a_C8|)r6Aja3Y9|eG+QcEBs5wZIU}Kur6M|% zLhvmpJPp_;_>k;ZX4W^bU=uSdI=9+&K{!{Gc3rvNooZ~xj<$sHnLS`3jlz~H-56vF zbFQ^L^vLNh4G&XLDNK+3ch&q@PJN!3Ryv>I{8+;tFPbK_Y+%5zFM7zw68`}&{#Tmf zEx4~zg0ystF22X%v}iwxHud;{fe7=XEj}jloL4q~pis4&xrU!AWSp#IQ68LxQcr%B zv*X0sW5?V+PKr(qEILz_R`Rc)>*-5nnyH)WOtDXy>CW^habAkkoSKL%{j(=*SUk5_ zajH)BnOJb1PBo|Z`pSYDmcyEA$tGm8r*;CtR!XPz_EOs-l-{D)3ej5&0IGB-?%3hp zI*Wo;Iu!SHNA**e4n>A^hIA-=PozV=7&Va6q3Bl;=}?qUfS?LOw4>bU_Pz-2{MK^v z*7VAZhFBJqcr4sD^<2qMm>-lq`RRl`81?XzHw{LX8pI8q*n5q5RjAQlS!BT6`*$B)~8g$|pND6{_bFp*|x) zM$}{jd=_$9L1@?xKb_3?VF=|i`;;l&2jjXCfY8GJ4VIX^4`9LJG$}3{P`c=JV;`)A zz^a>Se+iavaA^ltT!)II|zjWyVa^ju@o%v!F zGzOi*`)kTD+y>M3{?TgxM7;*cX-=6W8OrVrk_^w*Cu{h@k5{}50UZ*cfm@puZ_m}gMMTE0z`M+CVw#ah;`Hp zOUBkgp8UbN@#d>QvHw*>s+!Zdj)}b*-qJSql=1z9I3wB(ka-db6Zq~O zk0gkp6#Ct0BH`N1>Ktg{S0lNIYcc{ND1iJ|nZIKEJl&k1iFw(v_@=KwqeOSP3aT)h za>fe#LQq(HfyCm|!d*uC(hTu%0lmu@w6!1xg=hHeSgY7s3W5ktd|JB%Jc&qs0^a8Q zQc)pnZKV<)fWbs4HiXS@H49-|iPEDGwtoDDfBGS8$3=Rfq$su!)P*kp7qe^t{@J70 z&cE8lGA4j@XD8Cax%e72Wga)IyUY!lRmLbkBRS86mGV_0Y-D zJz>^IqX4v=Ct)RRT-^SvlXZH^8pGgs6411q|&kZ7Z zeuvG-hT$;dqZromJr%?4zv?y9U8aG$(sQBuJH`@9LA3=HDwOA-Efs1N56y|3t9VT+ zqC=rP=MJW$*#sbx{mPDdCKhaBM@4<>Vv5K5!Gx*j&SL3T6&x^`hw}ML@XoeMBd{3<4?nN=v3NHOBVg;8xwQL2q_%%iH zQE!EOor%e&+dnvH=bm1l%{{HE>7-|Rm8zz@T&=qG!9fVGlM|P%^ussKXVdrmO7ngp zkMH?q@#y6Q_&Oy9$p$wxG&j3ld3{=@ukkv7pLe zvjs_2Yzq?RP-v5!P46-RKWssC(#$qI#bD0w^SWRwRot1bS*nk^&FPkKcW9j7-jy(L zYPYTW9;r>5foHJkf#9i$9T8i&P^%#C)l!wjG*wtiu73OeBDNNiPaT#GL1$FJq|f6C8c+Ssyf~Kd!~B!^^9r#oqnO2OsVG=%N*tJyaoyeUL^ZF z@u7k`7fE)1XSP*~8#HxUh+la;tJFf1BEsX3>8eEon-TJOQg@rYY7u6A)Z?k;e5%K@ z{a0(U>MiRbb>+rJw2Yf6&+U0(PP*Z!F20yisg^;V%|qmwP3OGVj_zR(Jc#*C@&@- zpScEE6s)|M+>(pxlFp0y5@1X#gBKIu6JE@J!C&OXq+dsr7xTQpLayz%b7O8WbYs@` zchZmf?r?i5mHnHYkxH%9gm~J@RPcMUuI^9P)qPA;9dpa0W6j`IytS`9+#Cz;l*i+5 z(LMg!LYDZ7dHku5$E$1Ib(s27K~I(U z@xDl$>QNyz<3=uf!bXGT!xp^$EQWu64lZuC*weoX!bKB!?JYwRQeCQd9+r6V*_&{d zh@w}$6mU22H+1r=Xq0q{-vPXjnk@qoff3t1WP;s!y!DI9OJpvnM_k}6X;zS@E+12{^m z3Wwv{EXbaHINn2=Qve(VTRRL#W##&SOMoN1f~}oJ>WcWHh>ycKR*wq3 z?7t<@%AT+R^_Y>48oftB@fJq!DHc>2z*JIY3ez(!s4{@5q^fY3Zm}SH_F?)=(wqWd zD%jdtm>w5fr}Xg`M`@RO*MCVGy`cvTqa_Og8Vse^5vPrZ(wYTT1{jr8nPT*S1yu$Z zl~fgu(N|iKJ^L7a8EH-d7!_>oEJmM{8aUv?FpkyZ6Tb1^7NAXhgQ53B7NRvk?*~W* z#_0W&1yu&iQ?Fvgt zXlWm5wuo#FgETN?>%VPrmErc)4KtjtgxEezVEoY*T@J~OX zWqBy>C@G^Q6yPKL8;`MwE12VC&uBSgo6Kn0v17+Yl)4fQriIj&auxEQ`yqoR*t|## zHYOjkF=#_UIvLAWIs9YHgtYn?13+5Qbd3`1vl|n|wK65+IyeW?1*teBFeNOfG1X|g z;@M>CMTU0c`1}A6;PcOiEX!vwhl6`yz`J!irrg~ zqAVZ0e9oM;FPcW*=J#Q%rPhZbwB4Cn+jT}S`>*Q-PWZnU6#h4>t@n(saBU7G5N!mi zHm>dJ+UIO>_HT50o&8;7@N)`(MwUO1%q9p86H>Yk#MT%zQ6&|5<$VGqdT6YF>{Z^U z;Maj^saGF>KSW5oQi??>N-;YLZ*^2wv$*2K_|{lvNDT|7%n&uyq3bR+;q5aE7%1R? zXR+gMclM3JR;u+sr<(#1X>%~uQ%3ju3a<;Yoo>PpSU2?YV)>&KUc??vc`%K>+LO~VR2PsT_ zO*AwaQ}{5g*5WbsQ%!9qfQVu05o)7J5n)Vue+i${OwZjvQ7I3Vo70C{i*c_DIyo!4 z^@aB+HX|FR!px6iO3V9HOzFPLg{dza^DsB2oMOIKbmis(@ShvYrU(E}r2u%!7QhiFAmt1)20mV_aw`D;J25Z?!1!?H0DMe2 zy*3l{#Q?kv3N~9r%LCx!uo>9^9AaT| zJUM|!$JTKdjK~m7Pqqw}M@RJ|aE6@1i;7il1>k3sa;E?oAI=JH z1K{hh8QB0FW_}dFTHdDuxcygavg#`Xu&&%(06x!HHu*=0;0KbA5m;zvYICB90b4_g~=!=cOZ>Khz6p;tO@4{we18|u6Q2=Xsp9eFeLVh>UCho>xdns;Ix5YPg(JeLtd%$*>GaqC-d?q zzQI`iy<%aYp#E)Qbqdt+VScFdm`T!7af`L^-!yfZpfm>bUs4NAipT@>-(oYe0X@w6 zDA2W>PX&7WuU2JHWF{6}k-0GbO=Gd7VBCTVbqvuhw#!3m6Gx1OunH?W2hJE20V5SE zqC*>mZt~JWY=R2Oesv7d*;uei7)Ugb?i370b@r8;V@@?5Qs;N5qHk*RF4Nf^#kN>& z`FRl(Y&L6`OjFu|Oq*$KIqE96Ia#kdV&OM@*fo2?csNol50%yoVZnK{hHv+*--RsU z^vatGYFLgzLY^{_ahTXg8e@S}rMFGnB9z{u*b32Gi|$%&Rm)>@NsWRNaV!c}Th;Oq z?Wlh0wyLEliws-U;(KDN+V`Ud(pI(f>xkN__R^7ZrRmoD8x!tOxm4+IOpOoLDh<0` zYtPNLYi)9so!r2-C%MXQk4^y&Z6P~Yt5y7QYBsl!eGpa3EX*p;7P1e-t82ae@Y+H) zS%R9~WLKtr5gsqoUSdP%Lvii|om5IU-tF1%F`FO^?;A)$>K?Pp!g}nhZ)_{c#JA#k zkBdZMK`}Hr@ozG|{LGv$CYxqG0>wkzlIfO`k6Tb>kliS$GRC#!D%xfqhmS~T#i$ZG%dq}Zy&f}D-!=bpGMv%MP?M2g=d`pV07x*`sc1{<|3bjP=DkvCQ zPTl-Gr<*5~;zI>_E`anQqrth@%C$QJ>l&0I8d+ewwuOd$j3rr@)Q(GhctDd*!q zfUo-lIT$Y|8p^rZmgO;R>4oZNz`1!biiVt<|A4>nPv5zDba+Hea%qM}9^vO&heZU& z2XxEXU78zY*bOS0h;pjC6?bn5yp_I9>Ckb=xCaM1IbA;TSbWd=uYY8u^XWrcmyiTL zDS73tJOZpCA-sne2S9kUh1$PG=FPnR2yUjpNUWLSHgD#E*=y$9K2C~GYjgGR=RzCPLrpZM zm+IlMqbsC`w`hoROmn-652_XgE5|gqwxd0|#xX6{;4*~aeh~F+(F*9)?!{l^n5JJx zlw*23&SOOOSwCf;**T}5k=;2Rt}LsAy7i{R&uz-uK|aP^U_RcpGZe+e zo@5oxqC@>8XhOOPXW&p5d^Rzbln-1wX4+nF)1pCdRvyqwB=JE_Ze`g{}XnEp6CLw!YbWK43ZhejXaO85*G7#Jha zHD`CKpB6aPAx@zI{u=FO48p^g@f$X1R|`_dsPo)=0NXBxV!@2-Sxvx!lQtU7zsroK&2U*(8xh_|fT z^mI611m0V>wQI{(=cZLOYgn~;)s8Dx^*B9!eSHVOEzz}kqEs)9bC2j|XU~Ouc3$ob z*M=rBHl(j>bDi>uMIM}Rmc_Rfpb8v_EOB7b1Ha=o)5FP)dia(%?pO~OXc%0vY52!f z_z%%QxGkE9&t)Uwr)VbLlMQ|NmNyV3aBDOCf5#PYfd)T(%R4&XV5U$nU}DZ>fG76c z8iksltWdFTqrF#J&B!@a#f&_J=Tu)rcjg)S9mp7(kx%0}*4zC}oCSgQrP!%?a$plU?)^2AkUHnhtjT_xyE`0I_BC^k znaqLz9*Vb^1ApIwDuX$&q{?&-{BsMc41&reRUMfFe{2BxpQ88C};;NO!frJ&ec zCr!+g*z8HofxjRW{Hqby-EuZ8DKJNcyTpIbe??3k)NR(iJe+s*k9wvSGDEaYtfzdL>B9?VTpuJAxin(GRwI)1HP*c<=Tuv+$7K9==#Xc? zIdwnU0_2=}14;@1^qo`3Fyl4JGZnHC#oYNFSXwYVLm!;oGqp~7rfRTlcez@E6uR=r z2t*YHekw?#t3whyoDECCbwTe$u`|YBGX;HXK?)jmE*ufl79qKN7z;phQ8${09w+HQ z8y4O(NX4#~hMMm11&uAM`aoW*z?((xvkgyQA99{;JnI?ftb+8%O0C|Q06*N8Rokux zU)o@~+1TyY@rdSH6}$mkR&7`XZl`Fx%dMhQ#I)sJ#wvRoG^#D$2eDw9a!FTIOenJxfcx2E8?53EKaB-b z+@b18&~nQhfO{fF4}=16FK4b&+i2Tw7u#MHZTk=u49!c8w#DbF`{bG>zFICLjb%fb zUh2M%4KEtkTv#uE1CBiJABgNU+bC%yltrk(E0lDqvs34&RlB@?LNKp0p|uMq(m8L@ zaHZn&u;4r@{uJs`Y0C?FCL)HHD+8OdtqjDg>*Vslv*XpVT{2-=pubYCx|_l5g(&)RKkm~+f6xj+ zc!ROoTp)NJDp+9lbCjg?o_Q7so)fRG_2$ECfgmnd%__l)q_@NA=1DKGj(jZ6kpP&f z03<(6OMr_lRi=2{YC)9&9wk+U;&HbH$+L~e%SmSnz@uPj zp?I__EM=Lly@?%Nrdtr64YpaO6Ynyx-#FviAc)A-J@3`3Lz1$_tQ@sQ*Ouim0q=&< zVFuRdJRCS_jqYv~68`D0(VY;VIwmdDg(hcWx#ub@GMKSLFPwd$ZeOs~G;MDod@{5w zH|D}>OKBL6-Yvlayrp`Bf}3%{4xWsZ>u_4tSz>?cMi)9UgS+VBAP*<(OI-zrex+80 z^LgQgQWd@!h6TZ<+A2U8kh4Yw1I?QY0!`TEj?UsE$DJY6>5YsJpiZ;1Q1uZ;MkYU$ zZtX#N@&5t~CNI81651}Q3ss-tn^1%{02xSm5?gSc@g1ve(~1!N4rB$whPr zY<#GCA}+J^QOs@!?0?bJm1a{U4I&b*Y6t9xsf8v*guM!;J7E6^n-SUpn?{A?9k9c! zkM4l2<$UTIW&5wP?|`im4gtXTf7u{@+m`-A;jK1W4(nc@0m;pT9!^JuMTEiWJr0UMMyk7iqEa3zH>VG^7Q+cU;yB3jx?r@%19zIGI>bv&dr!w^ zWCLfI@lkMU`JM{S_FpYyYRME#_i-*Xt~3@vZZw`IoE)L!Bc2N@XA+KRtfI-Ohh;`5 zNx4|HRj_o4Ybm29KFkkG?qMgbN^qp#tf|WcRk2a?CTgKc5qW@l3pOJgFvF~m0#nQR zRA9FM>V!lEWdPL`k_(`N#)3!zs09`3FzE+u2W=H*gm!w)ppg%ValbQRMh_GVB{f0$ zBo=HkL5YTUo)SB@A~d@XI!e(SwRxB6?AMEJu?jKzs;$l{Z9%3LqAeC`uR@IY+?h{+ zEDBa3M%>2})ecRFkuZ5O>;{IWkq9yR1B#hKjOf=96=JjnNBWIkqgJ(xELxW>vM8vf zlL1BV4r=MfUx}wvz$Tm!OeKN|H#;MhS_wDkgj*D87~G-o!pK{6kG~e)5v;$|gq-d{ z*!L4RZSJ8>nX9F72w#G9B4>-U3*x*Ft8KkjHb89yHz>zmid~W?aA`%ny4GtCufU~1 ze>Cw*OG7V(!<0hz*%)wIyxEtE2)Yu^ae+4_n*4<0Ij6T6twTMpvnNWlBy_Z7m{rih*J+)`7#sn zUt5r!53rJoT#NF5Nt#mtMg?0ti_v4FhOaNKI7F8Qulnx@gof@j>HV{X^b7#{9b&Wb zsQRb{RR#c+RG9+wKP{*-0H~y@aDXmv8qXgR7o4+?s6_yDDNvoBon@?oodxKM_&n5y zUmT)Ggtm8Ri-fAX+f0%OeIx?!?fRm?Aaej zuO-bXU>p@}?JP`>i@An;{KZk)rQY>llD2N>0mJBR76LTD=nIL{#u$CI1yu$Zl~kEx z^d1YU3@|FGDjcKtT97^a7=0gUP5~GdZ0#&YPfGXI`Z$b(^@OC4{MQ9+lV4!~{)UBg z4FLRA5`!^-e_%nC0e~e{rU3qx1yu$BmQ+On%wqxcbJc_(f%%g&Z3zn^a?Mo#o7AQN z^a`GK7QH9n5tq@`6d#Cj+%EB7_g@iMRks;F*I0gr6aY?3G1n}3W-jJ;AH}xPZq41o@d|%4pdPTG z>Z4FcDbepB!+72WFNi>XOJ2Rqf+}-feLljg1-PEf*(GiDvM z_mm}(7h5G#`DJZc9ux3#@QThre%TsyzEXbKw@^salvWm6RTtgIG0cq% zBYyU@vP*-LN&Mr!@lcF%b);4wFE!z$5~nm+n`~0nSohe}#2DB@u=U_tdAtVFnS4}6 zkp;7k<$DEbwCp?70pW*xNQjX2cUbd5*3po{YX$~xTGiaY3Qj?=p&AIP z0e_55t#URy&#u)f^hXn(-L$GY;=k?S+wx_HN)dk?I1t(&GI9p4_X{K`9Fel869n)9 z`B#h$nL>zkL$NwA_)#d>tnY~`lJ_&DExe&*TbZ2(a%rw|AP@7V15SCwAy~<*Oa(eV z=B|Z9V&E&>MYG*^3_lC@0(EA=S31rG_z5+8Ns6k@PI}@Ki*2xq{yh;3rs!YQBjbFl zlj(^oRltJNO$*(W_2jEYJku#0$iP4ve!vvutYZ14QY+R$!N4Ah)}I$mNW_O}YMDJD zF|zhA5(7j$0BhqVn%dIlK1p_m#J}iB220;edP!$S3V4w=!XZrR66jX7YuENxQ z(a>Z};ls3Ai^tTfHMN-lB8I6~P#aB(2xH2-4n8-?B)kKgkquK}=0`E5<$WrqbYJDd z)TA*Fb7SfWp{RgtsAACq3|H(4L)K?dT%ADHCy8SzqZK|(tF?G!{iCKf6IjHM^$@ku zq=-Do`VKZD8?wU8k0MLU`&4A%wl7`5P4q>!RSj56 zpx{&^5FgGQte&c=&7zIU2t1YAXi`KTSgpopWP??h`B7MDd7la^-B-C_wa}P{xncE` zq&4?YE_?bUD|soNjbO>Iu%T>EvHGYmx0`sGGL+%N{FvkG*rYBA%hhE~T_$je4P+zK zLX#r$Antl>MmEHSSsz85mh-8I(|wf-aXXE9m>Y2`{MB-&TO7aI>!gEBt)VkusoM-p zZzxvP6qsH|Y)SzWKFkj$w&+ZXKzCUC?$y*}f`b^8-cRi_DIyP)K7`H42Bk3LqfpZF zJrzp2uW~`@PGcVChSDkIGw!ay+2wumv>I`Oc^m&=wp7I*7OQOvP2VR@rJxBP=10?t zI8XdeM%0`10>j$xG<28%C5E+sr`DSkkq2x4iOtA{wXj&CSkp=+6>GY$a$)Ve#ym{H zngtbV7nRd(XMt*83|vmgxi7{FD#Gi`E-IV%r=!tCg(dsdI?6d%u*o_~bjfoK#z??g zb*Vl@d!LHkPT|19{Y~26blh#0hD*&7hkfR+SYL2iv0P9)t6TyFTP-{)4MIj1qCpl= zP&=#e)lR-NYf-S;S%rr=L^VdYvkJL@GwiH_?}?pNnkZ)4S%rReW@nWsH@dy|4S=uu z1UhN6cg(F!xb8Ch?-^=UsmMy?D8?}z-^Y8O|D(}I9Y&x0%HP#SbVJ05V95rAct0}b0lf5I1{W7E zpTo-+@p9cRxD4XuwkzOr5HBRGV^Ma!c*)98eA$wrFDsg%!*0_ZY8vuWe8kyFYzVXS zS;&$L@Ur(RxZH%7#m|AuGQ3>!Jh<${3+eg$QSHBom#j?0mo1rCxK>WUcK2@GR4e6I zoQ=eYFdH91raX!l>;}A7VXuJWb7n^2%ZCf2gqdK#6KSq|NN!+=RWbzUyFY}4*$R)azFkv$NL2Q<9!M)2Ox;>s%h`D@bfz8 zpx^$glzuom!5G+GYc$G(6&E67hv4As3Sif10DLh(wpngg+`g_obr%9=p;N>I7U1m6 zI_`Drlo}31Uc$-hFg2n1$e=q^nruKp%{2|DQF9t&U{OVOdK~OTbr0WiSGYnpsrd#f zcpy@4vRh2;)THKXIDihC6wz8(are5F>C5J}7Kpu`?*}Qh79cmLFPpE-yazyi=*P}Y|hIsg*uy7Z`So6O!?-s23Wv68RXy;^(N43Xfo^V)j=uCHzx$8YhfcO3u z{@LYyLHzM|^hcm8pc+4(2Ws?PFumj~fZ1l<-VfkOYcaro+=b)V?wAg})%y01@rk|Nqk#G(vO!KFn6Nu8GD=|>@M74XK<>kF)>!I?(e-}vOIKs12!~o zI?$`^g#TYKIqGcK=xliU=Cjvt-f)()as7s8_H{k4Ht7t(5iQ`-n83h%i2lW86o~N_ z9a9B*nwm2Ovmw}S`+De+dYnOs;4e3^ubHe2J0&>k#RXcrhH5~XJSc{abRTHehMINP z9e|xA>)5^RRl9WyU+MqkhKepxpyQaSd2<>@9ak&`WjPcf3hTmOq-pdEU0tp7&Fx9F zyVsSg!|wh*IMhcD%Gjswiza-Z2*?Y^f`CQZhYQx{A=HD0iH3f?2<#$sSobX5Pt9vG<;xquP%B|y($9mnlb6)*$v zs#N5N57VB;P`y0SY#<-Yg!ZngyRR`(LSGEJ8l91PZ5+PZTdJ4c<`mA9L`qG=>2~`@ z`MM51Vx4oxn$3yE=5_1f)TXh?!9I}kI-ra?rQk$X9?*WPaFMXQ*=lqqvu&1<`CmO?w-?xE*w_07~4)=)VVT#P_7; z!+Xrf*jfa~f=t%S)855Mx3>V#mLbQ;9nd0}6olJE%T;sxxNvlO6m{8hr!1#L)~zyR zRClyAsm}r)j}9eUA6_Nx0cWO;98o11IeVcrm;mi+EiPBV;;2-nUoyR`wX9Sh8Y`n$ zVzTbiou$BXvSb?c=P^T*jb?2;aQDDX*K3Kgyb7vx6)zKbsl&zfUI7om0s2b#r?qq& zm>XaWH8xLsuYw!k>Z9vx=z0fSVX8Jwf7}Vz>!w>>_0ql}Fmhm)1#Vp^P;&Hocr4ui zilfU#^ZN0v)N%W6_)wdYJ%S~Zf@p^m@H|Epp|yUW7+QOqwO(IS!EL_3>GKfOn+=RH z1>Yb`kb{b_C&MMg2j__H3;y`-{sgllG9qR-99Hb8!Xlds!y*iZ%@Rz78v&jPL`g5O zf&UO{1vi3%-r1zflRBUV{+GlH4mI;hfU3;L!nKas44ud30a*1QJ8K|lW5jmw-hh?L zoX5znE$mq&d7eEZzAf?V;(gHDQ zv9iphu2`0Nw{4j!>FBjkL`1`tBI0628s0O_uq0wq@qfw&kL4Yrqk)Lu2VZ z+ngKIbuIiXs%znAq-%>rOS<&g)s!5Yn2Uy$hkMpL>Qs$DsHriu_TAoa~SJsGeYNt$;ogEs|caNP4A35?O6& z`R|0SmK-J%*b*kKRlaI!-T#Ip*n}hy>;7klB-{od^=`q-m3Y~Omvgb7d?xk?LHGf_ zc8Fd%NRLC22J|t_Sm@(Rumt?rLlkv(6|J3b9Va1R`utYsTQ_iEF54j{tmIJ-taBP~ z_#-y=aFG`@>h$9;>~Y%JH#)Of%{>O=1-G7U?b)TNc*wU4Ay6@=<;ju(xY7uY5@#4! z%W4z2$^i4~@GdOcHyL}H@mAzvkFv5GSTOlt7l<*A^9!2y!ONM$>>gpOAMh`Dd#%Veqexs{vHLM7e*gy~yF8JoFLuHsHldG1NVD^^l z&B;>5ajWpFRz*2=O_V2s&wA_7BB-+N;vjZ9G^5Xt4`;6Hq zfFQKlMIY}Ipvi~E`dbSIOO3HnDxFFk47v$c6myRtfOS9i2pT7ZA?;P-T!2h)cXkb6@P)SlGZ2PIn9zaI&>TC3rjoOgrx!xU@w2@g3lPxN5q!h=Q~l6x{u9Xo^M_ z##IQeaFbmD@mqV#?mp?f5SagGY;fw@LXwYEPSDX_1Y749?JHjS+&Y*ZVA_=&SWlKq z%MfoL?(Tk;lZ+p=Cu3RIuiJv%Q?7RXIG$)M$Y{a}_OW=o8F z2XoR$z8^h}NxmCV`JMSHpKNu3Ghj42{NqqA#$y~!JsJ{mX!B@%!V$%zVF5dJk9ub8 zR6Xh$v19Y7cf!8KqYeV0?GZi^VIIK*b=)IGMP2hqVNl#6&J>v{ykgkGNeFg^=ssni zq%B!T*AN54rQ7=sT0O^M6liIv*$+7mrO|q6VvI+s2Jc4$qSOP81#~ZvYGwhomVhh7 ztyfEx>4PdV2Tib6SE)J#!S58nGQu{fHrsqz&lwIlp%tyqmwhrO)k7)!rI{ICeNP%QnzjI;E_ZKZ0h zS{_0V2)Ngp<&iRE{4k^RmmOp4%GD5QEo$!#*B)c-qxo6;6I<5WD(li6jm9KIz;5T< zJoW{hQqr!~Wvmf6+3-DM3h&FI+MIarvMoZbsxPJ@QhQljRxjCI8jW-Mtd4Pd>1y22 zHkr}uZMmd+fnM7pI?m{0Wso7HJGkR9)&8T7G5omIxFiAHQ!iGW%=oKqxuzJu+qQ@( zpc-KxFp?9^r_qNlPbLQ(M}(b+{JqJm-8dH_C}24Jv&t}l&(AB7DQw|7kX$B5Ba zRT~>0ZnYJhlKl&*2n%b*w$tho;{zHNjE{fTF+Lt82MDS%#>2Pf=ixoJMJUaEgAhZS z=GroQDUA>@UVpx0yzUZ1L^7*CYs)94uAjCoBFbuuenxdw0yGvyC@R`?Rki}}+A)qc zK}_P0bxh)bF08UQS>(U5m6sCvuWXBmGTTC4O5|dSVo`*m;=vA$PF=g}H3(U2L`SC6 z4|K}pe%fjg7eS#5LE=VepN^p#;-DfoHH>!Xc+S(s~+@Z z)B|c*Tn#473CpMk$?Y4dD~R+2Z_BR%Z#HH}Rt;FA*MOi+apAwwoTiMz zzuUOvSr`6tU{pg$KiUC&CBOK;WXzDP;$PcV{C=zA3h)JUsxm6TgT^J#x&j;{mIIS* zfj{I|fya$ml2rxH)T;n%S6m4mGp8$~68y%vxYC@i zj7qS}xa3(^f)x;S4?DH>z#%i@ag9&^evD#AOBOP+N_IA(I9 zQY#H7n}^@auLj>SW=U2xXfrUdcEy$8Tjq3SRDy39mptoAaI|Drl5ud<3v=6tOQ49( z*oSB62ZeC6;wrGvoUDv0FweN;SyzGO`ydM4?RV?-T0LSGY|5_zXB#slYcJ4dh7f91 zTmd$kQEnMBK z4A6jo+h9C(^La$Fk zuTO#;lvL2_zu2-;N#kKE!lEa!VX}UG^yppw9=1C$PM2}~s9W-zy--9EW^b&EZ>>u= zd*!huk(R{-xzL=pjHb^#D%nE57J)^2@gB#S&{rO25g#TZ4UmhSub^H$kawF#= z;;}{)MOhTZGa5M+K|nxJzmjowci5d~c4nBFH=)YG*i+OA0-+=C(R`hDYj)4ho&7w+*J-E(-iYGAQ4!(kljQBMhd81$}gp{zvz3wFKuwn#2bWLAkU#_R{6jETvP`;Xq>=?{OPMjSh z1MD!@s(Ry!>H5StkZl)LhcSgq46}R!C-&!SsK|+Z*~p1=VpM<=rEE)U#lIi9s85PE zwqaCBu_u=lRsjW87ql1l{I5Rd6Fr!jP;aNa|8QLfHsqq-wHmcNMk6Tz}6vwk3;p?c#jEC6B5@y7h z=5%LD&7sp65bi|9f6dW#*<4ZQkD(8C2)Y1Pmiio4uODfLy*&V;Yr@`$<%Gc@bSjwO zroLrUrm9HBuLyZ#7(pNEtK8rK`b7QuozP|VyPJB0g!LObemj zzVUOnH$(Qa_DOnsGe?6)nxp4S*mhBEg&4gVZ~27RelcG|MNS;XMoyd)Zrn;XE5eH; zj((h~Pm7b;rctE@BW?{8QDDYNd?gi`aRM7Tab~!q=VUgD_I<&@ZThUZm2DPPRxn1- zJ;f6kv4O9kA|q~MBPY%XcdV35mTtDzoCmrxuj%vR6}E9ydBOD-P9ep<%*%Ww6}j;u z8#!@qxT9j0lP#4q0bUIGv377V5FomyGr{#2kgT*El7*xnUqMAq^kE|>&WS*55kBy- zpFS)0W}8OUTQJ5-iT2WQk12c=6?rk4jV$3sj6n-`+{5hf#()Tpd&GcnwJ;YZ#(;3O z=%SUnTjD7Ra4J;Z$*EmGeeUe~a#JZ=toWj;4SLMGaZr*%S5vi zEhQ{kxGP21#$}efQgl^Zh%T>?-itG3zu~X!GQ-2#bbOd#;Pr8>;~MyqxDY`D$9QSn z_s-l}5Cg*1Vr|@I$*2YF521YM-M2qPe%#25okSxnP_11de~erEE`gpSh!`W4amJx( zTgZs1E7RE=p2zO^LjUaP7hPq+>d5WV_Ol%q7?RidQ+>039Bo!3*W?@`XdQs~D&)e4 zEO5Pn&c<^1*7y=G9N)1c9bmMyBP!dinL3`{fR%XXDil=|KSgmSh&ertv*6D><7|-Y zjB`kyOY%IBb9xvZ^x=Gv#swhS%|j6D%`@O0O2K7HP(S2kZUk<*J=a^9w$btyADZTs z&5^`dhfdV2n_g&Z!^3%VSvZZ6jsaMfZt_ow>;eYh4UvYo&%*m%9SsSY-N#RKA1^c< zJH@R8_r2dCE`;l{yQmU|Lq6_N2xa5P_RjqeuVizutUlxS$)zqc`q=PvpY5PVh-)sywVZ4G96`ir@nU$D=-n+t zaBvB--xM>Y%9_An$@y%9TSRqJe4fj;m8z%OHZU_smDS)ajTr2n#n(o$2V7+%Tc&c~ zrga3-eKT=GGHu1mn$)sFt~nFjtN$?Q!fRlJnty<85xtuCmPV}kd->WZ*8E*;AIL^dy#A>TC@ybFL56qOs@N2qC|jvdfHK=EssxaG0B?yz z1{C=kDKemqjhr|GMxX~MWzx-XtU{_#5`(~N^eJ#9+bpUS*g@(8d_@vDu%54yA_p#E zBPY&*!P$JalFAiYT7)jZv--9F4BH^8wO=1v`<}vx_5LJZ8O3^kjE$Uly>AW&wP%;D zu@A_Q|}DnM2GmAlwm2b8;mHgsX))wHpJ%)#CSYXF{!6c{-cC`)1|8 zGYwYRWXrAfbF+=5bZJffp_xi~($t)j8V`za3c2T66VR#AT*Cb8#6# z#F)8=JsUJ^+T8l-?d3|LEi|XyLtSOR>V)uFA$#3YxuG%FAWd<1zrLyO=}uEe&k_xt zmUH%S%@&f}z0R%z%i+26M6a>UrT7+9<%??F>T3{+XDR3|M0J2$nI~p7a5S|zS8UCviBj||&3 zT}uzUm;yZ-`HCviBgIBeoF1bD?;#|>J71q0=dul>%8h+uxsLEGcxUmoR3t`~jl2;O zL&$^ous$&!U>l}ui4mR*?_R!^ip02!jhr|!#tI(8oYfc7;r&ye9`Ccwqe_qO#qt}T zk_yerJA7RgN%AHeIdPKI2_(UpfB$mPEl<-9Nyh+0*K}6o=@O$lD`p`DYSi)7RHViT zHge+B7%fmEBstz}eQwNRn?}`l>=(;-$TH+j=j*9Rjsw}qiIZbnfgIU3Tmxk-fDW!W zU#U-%GTTV1G?^KTCPH}yZ&KuItVozPHge*G5xq&ev^*6!0^%Bdnq0{?jVevzc@wLY z0y)<6^;9IsC2V8~IbuvUx${-#U~CKscfRUHb39kJFD%YHNc7mSkJJW?x=>$%`z}HWalg|@!jLEJ}ADSN?sN>+s{HE))gWX;L(KW%YJcC7e zu#43kIxydZucBg2f6L~O;C;R_$s&92JDn2VJ`hMxK0u!%y|eaXQ;Ds^s7f%X`Ww5aU$7mWsqUnT?z{F}$;= zgUY-0xp61kHmcm<&i49?DbV9KzM_isxRs5ZI6b`GVX|9ex(xH={uOO+>9gZ?wrNz^ z!R-$FN-2=zHNKvTxWvuU#2=x^_=x7(jGQPbHS`aEd9=V+dbS zMS2WmBPUJ|@5COxRZd#dz>@Q+`V2XMZ5~zs5i2ZpN-8cx-;b}UB1QIQBTFa}V>HYi zP&0>6Vn753)M7xmT9`{|V?elC{HF7V^~_Xcu)NtiLVa0#zNwNeuyOjKRg4h^F*9J_>(W>Sdfk@^$w&|@`$X(cc4ckttwKp-UgKSnC zENXPhB{WV~@-t?3GQq&JF2O6a3K^e`(tiq zocJz3!&gp`2~V<-6KBFe=*~_#@aaD2=_O4R4&;Ycs9sw0W~?ZG~LTez(HiBNnaJ zR>=0aY;@z-5n8^i)yD!Th2Z{w?|*iPIUx28LNhJB=9hv2e{ z*Y)fAHNGK3=hp?NSB;n?i>m9;l^3t@wN$L`m)XcdSnwi2NZoCx^J0w13wyJmchlRj zs&fp} zbL2((+&G_YnXcwW`1Z(i`FbjH<199E;@sF$^cyr-DX#8&RG%Rav+bkGkf|}l4^LTz z_T>S-wu(HtmyMh_PezM8DZ(O9|J33q`V{#m+cc^aiCBwKqk+L<3ha2Fuc#tB-eDsr z&W_DRc7*Jb+~%j+9hfZvqH8)Z5eqRQ=^@)EIfk#PB0uWb$cgjgJ0d@Xjf38f%v^P* zoZqWQ&mQ>C*aQZmm)b9?JNPx5Z7x-ha!}kxC%8vca4xg>IxCWAIvY80@`N~-kWG_o z)F~rl%1X9vRGAXjxyZInmid}0@}tN`PMjZOL!1ld6il@T_SIgePmycb=24}{fpMLQ zQ&ho=T*+5fktOTd$ceM0USvr*183tkLrzuf?D%&iJg-lcXW2GVrAow3{m4)yP+oyA z&+s)??!-c&L}4xB%YA&3*K_xhFt$OcO~Q)YcV=~a&SbjnuBx3P5L!`EZ>fbbH;nes_D=v4g2@$ z6Xb5TbyNuwk@8Ryzz{*)k_sHTldr2HM{Z*yC(aSygfUr;=0bcy#Jl?Zc#CZrReta% zj6J0kxbZq)PepFL#zsz@8@>feWVO(3`=ig&ZeK$F_sKAird{q@0(#l3ooFSqI$(B@{~S79%mazl^`)ah*MC3A&>G^RbOMQ}j#^*QEpz!= zD)M4B8(G4O7!!2v9G^L*69XbR#}@;_)xz9&8Uw=B;>^yUX{0UHBXKwIj7$Y`71W2P z4(edP3#vq}eKsnL`I>1*M2~h(Tzh0?o!Cx z*oIQAze$nvd}hVrHejb@g8R9ZubZO#*}z5?+|Nw}(S6f zseyd$6sgdUjhr|Yr0Jv%(5JzEY;&mkgoxa7u1|0hWr^NkZ@y-V6qv$BPMiY$S~8GK zsyQ&mv0T6YTiLcyt^WxsZon*-Sos;gT8fq5$VN`Q@&}=n%(XrN0$ij|fb-e*P$fWv zDgmrzOqQM9{4nS8RZ}FvS#0FQNf4P)?ooXnJj^zUDi4(6ff_009^h-INQHaZ$Py~V z=-<0T1ZH0}21M5n5p0D+1P5nw#Z0L_oo}vRfJ3RJ`nF7^wb1O}3;Z>khN>J=${p59 zN`~y){W5MHyMqOv5yZwYSkQOT%;}3~&!6X;65IM*?GC|MfasbIK}2Fvhy<-#Yp{zY zGy|LS6;pioN3oHGcYioRbl)gJf1Cqq&aMmu0&_qO&C!8_*~UsoMpXth2A+S%q%Wk7T&XnO#sXd(wneAN^=P+%h`&Vk`*C@V!) z3J+EaID>2Tsc<#hDyme7-UsBRg13YsBd*|UsK|)R*vN@9Vl!A@)YhJ>WXUxI=By;ubUzZK4&8*&Vpfjhr|uhMTM?ma_RuxtMMWMv|xKbK=KrtEh4!I+FCe3AcnIBYw!&P>~VGvyml? zh%qqejxCx)S1}-hV~a5$TrJF#4q`yKS`^}@`9>|^xQb7^_qd8NFmO6?=Db=bR{S6P z9SeU!uynOftoU7ALb?N?PZ30nfzUYFFlde1D9er!hgN*lRW__PC>&am%H-weR(zmu z@^`z_e8kBWBRaGL%b@_#8G)tnLR2FcReNd?SFH)9v|O`0Jvo@Kr((M? zfRJOnW1XIf1b2)FEfGvcvOj36Irh9S-;|-rNInhvz-r`noElXgnv!HMzN(6~z6Tpw zh!%Dui0+$>z%+aUJ?!Hhyqi7^P)w5}Eo}2Bwv*NAQJsrg9I;C)xQAxGzKTRivynGW zqS&WAc!_eMK2bW@=IIuq*rgSSat>c#MWUR^Moye4xOGeLD8i{0kLa`HA+~K)SrVDm zvz9vvKicAczOsrGxrdFsaZ*Ic=J~NcMLuNPrducye!|6jd}S3W@-`beaf)mm=0!Z` zK6qQ4t4YYp0t=1K=6sbE=`xCqoH$)D??6bS6Fll-jy_WkW*bM< zr9_^FQoGd&J?~;BUt2|zOk*P_P7+M8Cy*rM#EaGXENN$3N0lYhbo`0z*ozWhT}7G{ z*vN^~WLy|cA`iZ}R-Z6ev+blxn7KNH5sED|Ls#$>SESBmY~;kLgUL+<591_;@y0CA z=~L$SZ2PEEW`+)BoZ<>pd77`VB2}JXBTJ|fV|L4(ATtMjV?ekQWPK@8mk$5MnSqI( z4jArdEGC-pElVGGfp%=&4}XG0wB7k zyApHg&R1B0CPVq^D$-;y8#!^Bg!vOEv$uEnWx7634rH6BJN$`LT7fA0^Yv9E%D!ym z#EBB-PwWieUY->7dD6zVjw(-d{E1yyfhGoDT}7I-u#pp|Nti!z#y7n@S+CENOW5X7 z<%v#w;*?e(%7uJ=6^YWpMoye4f#nL+WIMUO1G{;j)aS}$YzwJ!MWKHhxXCZXRLEBc1tV$c?l(bg7_$V|n9F5*V-s#hre3a;+M6IbWj4Dk1?_!sTk$cVlIDQ_QEbDga-!xG zpv8%hVv4QQ5qvcjnX!nCoH#Qg(-*JP=fo*&lc;h+)lZnzNYR*dNY1$*^A%KN!w=cW ziL*hP!T1h+Cj6Rh4pk=XT5BPT}%81AVtUWPAXhFpmvLdVwk=dS5Si*ezyVk= zWtrIRQ|!gpOtJp=U?V49|8@ALm{FCqQ(@?{p@nT3RW{VjGGK)bVdWIL(ahIVksE0? za^l<=Xea$D1)_!v^$F3zHis%9RO3jea3TxN;VY-ef-~93iL)Rw75*doOn8WG5>+Os z_AP5vvJ7YiuN1|guxLTN*j$%N#T09;%`44IV8LK|- z-i%eFW@VZWO0TV7Jah4(bL+Eh#X_l)Dft%MZFUJa77u?nuw=EeRSkk?@q%`XjP9^` ze}a$>n@dMZW6xCBCX7f{)zDQYtevcCS*E!qy;h#AYG-|m?-*_I9Xr+J(YsSd0-d3_ z5!tE^V^@X6Fn7XY9@}J!FGN*7*x;|mhR|YKin^VQIebMG8;ygbt6W;+F=hj!D}4Jf zOIV3wAqj4_DJP`OKTe+ktNDfv-LN9K#OfUB0Hgu~0_7HC%67iqii9Y!k%cIt5EsI2 zzfD?dwcj&I3n_u)v=Y$_9uZ(LE(P(mzqf%#n$u~wvQ4Y%vE)79XueV@1&6hPucsnE zZ!)X(5y>(IX?=IP!x|e>-R656=-T-4;Nrsa1%-09k}c$YK56u&+CFI{K=49+Ri7k3nm9mCZ|MYI zG>osGA{~dYkp()MtyByyxpXw6_81UuUw3=`m%{9=X=$;kjuz14ey@9b+;!9D&8?Ln zx{>{Qg})nEv|3&6R9rT?UG8HDLfYl-+zueE4jhg~jOcKm)Kvzo);yLl*S=cb+de_x z*vECJv79AiA$JGe+cv zogCS_Ncd||OS1?2CEGZvjzFF+Jkk*~*aa0F!e@L{6&dmg8+l`7h@BPL%a9!}(`Lx_ z0MQx!3DJGTWr$r+fg#`FtE$M5ZP>_(Go()RAwu%!!}aO0kZl=N2O=K=i*_L4d7tO= z^;G1>TsHE?$PFRu^N;koaU$C?UCoW~gwH?V>#4|%HEiU>xv{0_H)shN9)p?o4Oab9 zpCP|s+eeil@{!bN@8Kz{(7xQr*H)1y*Rhck=gDZ1Cqj>r!o#j&>|v!^~&c4u2j)sY+&cO0Y*6ZRELg<=MlRDkOge9EqTofSDVk&T=< zXF_}mX2l5PO>NR=%CT(Qs4^w4PqE4=@M9@oQ$>Cp%|@2+BgPz*JDFw<)y9BuC(|yX z!5!(?UFR=68R~Uy!m5+Y^yzsqyJ}SFDL)5T*5rk+H@SeXp&~8MBjgxUVx2yAKO$Jo z!J+%#>euxD^6jWNbiX&d$-FkJzSB^#S}|k++ya3-tkCQAKvV%SKL|9a{)KqlgipFtSpAxi&$z0*J2Z7kw|D z>u^gdaAXUl5T> zY}=?3gx^Q@l~dryReVhq`EfZLIdOi79>hsh>>Y0VgFZq2k8K=Pg2eP7PC*5R{En}x zB14{HBPY%f-~1oFSx!39z)p&uS7=A6|6$ull_W8vRHv-sSnSt)T@_jKB^z17k{APJ z?pT{Sv=Rd%IMx;e!qviDh8qLI)naw$kNDBz=#jWMdPb&_X{yvOYtJ{~?hXHR=XCZP zp-SfoweqIo0G*i)MP%-Qpcbz$Xz|NkioQQVYz*UW1ECK+yIgM1%*gup(=THi-zufM zEV_wpDAoF#6xnIE(%5)QX6fc`IzE=Ko1*(!%0?F4&(Q?YeZy>#IWf-zWi*G^&SslL zl?d`AXYLzpy~xspm_Oxfr$~j<*~p1gL7EWrL46wB$2Ny54di*wyfny`CYyslf;YIE zubCnR?qnk;PJzfQMjz zFGd{FoL!k}O_x)Rh2}MZp^Gx;q8TC<*%nbHM0BHI6T(|MkqK>l?G%|{u#ppI!XO&@ z%ix&`0Ulhb&x7@Bd#Lgty3c6yz%84|f=l?iDYD=~Hge)DfSl?1=2RI*Z(!U8xDcAe z>KT1PJjphVDk0=&Y+$FsCPZj4MQ%LCS5uK2kFb#w=f=oVrme6to z!M2MkG4_faU9yQGmQiHIr+gh1S@AI&IeAvt&H@f;wERV&{TP3hb{sJdAb7EO=dwa9 zqsWSF`8q1HVrw?CgcULRhi(_s?99Y~2zD`JK)70%>0V+$xLO<)cPBe)0Xcg<=-!+? zTO5?hXG+Dadp8oT+c{GrH5@(Ybr-4uuj=g#t$;@VSj$AC}W| zha+xf8%^ffxA-D^~v>*~mip@Ek#O->}ry5}w$3Z@kN}241ZlUiJfs&Im6> zvdortI+V#`msxNaefU}{GG-Gt@8INn1(?R1`7?y|2%mci4tf<&HezhjQp^mRO+8n|z%W zY4Z;@vLbD|jxAeB*bmg znx)U2>1ML?+e>QUB9FqB*kW52medZL|22y2CC-@v$(xEoK z(u#aB*vN_VWebVVS>2ZNZP~w4pDgRy)=?$POnovQ!5cKxVw^*|l2Qc(q(KF?QHk*3cQ za*UbaPVaeb9$M|e*{NPX(+;D107Tb>(bIKnK6G;G+b*m8uh__fkNU#o&#|)O^=>|H zlO8*r)Ny>rj>fCdV|Tk^EBf{b|03Esi2ZYXV;VwoSnaCMl!;x&6s`Wbx5R?0n84Rr z(N&CRBPY(5ZNhy@F*K*uQhmZ4%{Gy0qjHG0YjF!LaApZ#ZAH#3W+Nxg8F6Zg-Zyib zqj&K5bbY>@$~KQGU$myCJOviGax!0KMXsF0Moye7;!+7RU1-j$JN4;u8{0yvbkUml z_Lo^;%&mN_6&bUEjhr}R#L*kFURVyS*YyeW8rwjsgwYzk@fBI%%PV}P75VZq8#!^l zNIZ;_D$3g+4ZBvmZ!`oTx~6ZW?_r!W3yc}a*IJP={n*HfGe(>Or`OHN8x`0fa)3T* z_G24K)ywEdy-u;kvA@0fYAdp43L80b)=1i=V!Bk$1Q@eipE0d$1F14bKklU>i)_j8 zRaRt6BO5t!wuno~$;Skz6fV+d%K2>Ls4_)sDY;Wzp)ER>udpId&SE1=coJhez@1Sr z2aIDt1ZNatK)70%`)Oi8xLTan`STjI(`gWnIn|>V$>v*pvlcJ0Uk6n#H>mG&Y6I;= z@Ajv-+;aDn{ed7hhM9!HEtyKHxsA5c2imT0*;Qb@L+-|=uh^zit;O9H#`v)M8a(9_ ztMUuJeu@t0Q#P{TfIcRO?%T^Uf?D$wS}m1sYlf}F0YXf;PP@-E9w54=&m=#-+$<38 zS|maUr4(r~j<2O6Ew*JNCr*nxkHK)TuBB9HF9w*gM4uUp*_Kgt74kDs6}e^5yinwd#forxL`* z5H5^r&Xz0LTrMI#+b!&>5WH7t>9_p^(3VKAKtDUSeBCl^61~eF0ts3n`M~Pkbd6N%03Za^j>I zY}%__S(6L$VZim;eCP`hT@y{JbT7zHc*-YIp*LSYMJn`UBPULUp{A)SnbnnCHXmfe zUixgrj;^_4HL@Ls%g#HQhu@_w&_MY%T7ITi>0&lD#g( z62W!sAL`fmdwf%dW?2#(U{!v=UCkN~UBUh~UsJ_Oe}j!Igb05pi0XNyEq56tN2{IEPVubrpFsoQ=G3^2E+A`4CU?Aa8T?dwrff&9;y#PvrZ= zYkLw;d4(3|3BJaPZ22u4IdQg(31dr9NRaf6K2iS7HjXM$6U8j5z>&}S$|`c? zUu@*WIkH60bgT9wj9bvPMj^FBPZ!nQ!5N5`xe^%Sf46CWSd8o zDmp!4yRZUJj_0eZ$dk2f^Cy@3LgDfkVd?l61W!m7czZLqcK4V^D+ewu%b2J8Jg;ER5`4eAn zMdtj0jhr}hwvA#=wvuV{(`LZWwZq)L0MRvJu6!?dZAW9%#w@bHncjS*6*<$Bjhr}V zLffUL9L&#W0+rV@^-UfN&S^ z{3d#)GR{Jg=yeO`5*%mU0&aTrg-=C&nzpekN0p}X%`;J6jmjvH)8K2VNX`~QjKJAl`_tx2S?!c!;m8B1i6LBPY(0Ky*WfE7C|_u-E;mK21Jm z+eeiq@uHhRX$7Wy$k$hqDetk76K6_b#RAz*Os}`?P1??6Yk=sQE>pY}3!ah+3>nMU zRgoc^vyl^LNMNLw?B)z62s*+dQhSB;H7`zpw&N4(6+?$dj3DR!F?RyC(e~HpHjs5B$&1627RVn z%QlZHQ#5>vQ&@o~SM$|X!{Ks-pU1ESp}B- zp0BMUOP*#UC(e?<$_3MM&CMx_V*{B)Hu;5iB>P{sg;bfM(cg*4t@sUJUq!C`n~j_} zSHe6BZ5$5pWRgBlb^(ZuuE#@V6*ms=#Mf1kBRjB>B^-${Til^J+MsIgldGSRsbrce^~>7xO_gjR@1Hnb!G0%H9p5f$TU47#JCVKl zxEyo01TQCujbZG6F!iHmm&@&$8Cm}}>kf7WSnra%HT4{}sZ{Gwp3=hXH<^`Z?o4$` zXX)qeNj;OVpQ0l=gN-aWqSFYX`-b|5G-peb$$IdgKm+v<=%P8ke?QwIs$7^{WATf{ z1#j`hxB4EwdWux|4I4ReDvTl;q)fw^k!?A^h!6D{@gCbSs*I>L18gxOq?95t-sWqm zNQ^hw$QuhW%CJJNsS-|%u{UeGip>F{YhqazF?^*Ii7|?=r6Mtgvymmlh!Om`@uL}} z#DM7f_|c47D0gCT0o7q_`l&h<#mpwu=YdsSR+YHH#=N+->c)+82x4Q18%LVH(>lQ! z&k6{J3>9`oSQc}`#}#agsn%sJKVVg2gIG$jUh{k{75%_+HnQLcS_z^XgGke2ru_mT z!&RV*W)OKf+ajursMVqHGs65Na^X_Geu`YUh>e^$7lxZ$!0FpiKKxFf4^OeJqRNN8 zYIG|6d~nMsa^i8mj*6Ukl#QG?Cx(#SQj|h7*roVdpATQMEuzW?RsY~EpU8#J`1&bw z;S)BpgbOhOJ~w7G1C$sLT^}=!oKIN=56U(FF}p6T61&mkVQizR zR%fjR$5uTygcekE3iJ4iDmsNZY-GVH983`17eEe%eMKuXrAn%?usRSwo&>6B#*Zhk z4Wi14T7!@-C!FGmOgN6Oo+1-gvyl^L!dROLW+$_$P$)HL^XW<^=qonp^WrA9c~p5( zYw>xI7eZl0mfXNsR*@ywvXK*K$rd(CXpxhgBrofe9Oqk5KwBjDO-D(U+*i3LsC-PttUpqw}?7~J)+$W4`%amXVaIu_e zZ!V-Nh2@!iFs^9SCq#;E7**F$D>t`Ih>&86%s7UxrXn+rVk0Ncj3K7ULMd^UJ|(Jb zi>OjUwYBh;PvpWnzJ7{aIE9UzI2T6e3#GPnF1t39YF$%o%|Q197ODk8h*KFj(*)h^JS}vV$X@@-}0cyOXPmMR(c2T89tw~V32@y*v z^5P$SEfsn3DjPX*UJUhEOgtQ_?iTGH#|VJvnjXg##W>SnLXi(c`5G$nVK5sxaXxHT z%D_-?u~5#W78j-mT*P#JIvmJ0iK>g(Ly-=?0*Y+dpRb@I8}?-*C(eeUuCZ_&CqRdy zJ{{WFHc_R6s(0|0P~?Nb*HDoUEo@{7A7ad5y3?TMWLpe~;529q2v-a9;ISAGt`=+J zp0a~lz{zp%cJIk?qZiIxIKTc-IP-ndR6JZc^!T`Y*l$?)>w#sfb$r}!;?mKbxV@bq zVocn|$qGihb4HLABTkQdsjDnlt+_R)lqnVDhsXU%-`s!bPIK2t!nP0!>*QdgT`a!D z2H&!)!SZ$P9QjvlgDJiXRrwjj-YRSeDWavMyKwdkzLJWq#iwyAyYq9fiG~Q~U>kp{ zcE4d9Ky*gGL2!Ll`BCIGsyj3V+qQf?6>EHJHnI>Oj3tQfTM@BE2sP}3)Vyg47K2`z z%OehBTSwI$$fv<;@xv~v;1=fbl~p9k95(VsNfP@MH7`j{(kICYZ0mGAN$jEuBsq?+ ztRhKPvyl@g$>m93HC3d?wQS^#k{-fgaWCuB z<3+YfGs?g zY`~qIrB9YB+f1rtnH}4m2xS&L%R0W+inKX}jhr}b!aPg5v^p`WnpMwOxR-O=*4k4h*o z^PhYj6`A=yA;*|F>-3r)Db#duY=4_yYj>Eo1c}(Eb>{nu_&Z$3_-B z(FlU*zM*}8KM7xuhLFQm=7Lt51N*btwo&y2ar#N#f(i~{7GG6Gf=p*4Cr*&DqUR_A zOT-bEHToP`$u^HFN8~xFYd8?MtO8BSd~FqJQe-11P80vYKD}NvR^#9hRFWAV5 zQzXQXWb#b~$jZ>-YiTCxb7TTQbWLw2wjXhdDv)G6Us*+xjAJ7wPLeGpA?vDabEVZs zlcV)%vV?6PRaYWEZbv0%H47^cWiek}MWP(WMoye4{`q8b!_K)PflU#o>NDkJwt-Zc z61#WflvdmzaS~r!MWUR*MwSpI#$c;EUTcoc#eh&AukG|fyis-~ zX)53C>2qiyr4;FTH(yIddfv%K7U*dX&v?*gopo=u-cI_@w9YAzV z_$$v-=czl|mf;poh+{_Z)l+=`hq94{_kS=!bl)KUW>Lpv%mQUJ$L^=IO``fbt8u|s zJn^kQkguL17xrf(C(ea|c*qP5y#^xKvOW)rY;&mcpdos3ib;S5r)VMv+W4v|a=>6C zC(eP*eCLJ)cyOgY57x6yqRIm`f8Z;g$c0Pz>M3&JLN>C53o!yaH#RgwkQflb*f0i! ztA)8MI0l5P#jn`gDlK!O7O=Pb?e5*%J#5jEsrB+*-S4qqtnjx2OI2%E_uFxa=mxTH z5JZeXHqJUE>NpHX`$cT(9&iUYQc*NuwPvrOyeci<)7=;J;)TK%OBB`M1v>=S zd@a$h>&1LShHifloL*HvS`=B=p-a9F<7=r{-SgPU78l)Ca1KFqUvF`Y$P0Uivv;5F z$)J{Izwsotaa0|Fe9c!hIqZT84&em8s)`Ibj*YxAGQ{5A>}ANU`V85?HcnSF#4f17 zkem3bDl+5-Hge(&sS|yOu#Ni_eR{miwu~x0=^9Z2{l?icxbDstnGY~+oR8^Rv$ zfp=;*F8u(47nOH4H^O&t_u=cQ$c;_d$cb}fOVMw5wlsSim%a5FGKFm)RffnX+@rmR zr>sKzGMTTfB2Ol@sw5I z$(wv_6?yUxHge)T*+y(>V0l_4ldq(*U_^g5nK8f7?$p!)MAvj`X2cshhZ97-#TD2x zg0HY5TZXccyNNC5AOB?!`WLoI-=LYUknlBMfMX+li_d52(`7o_Myj4AZun{zQ=rL# zd_@&$vOgPHLX#LX+3sYxIqMSx!kq@#mPUG{(*SX1U85%ghkAV*E`Im1^F)1i{(xOI zs_c}X0w9Z?seA%0*YGt|q~%INjxpie>C=ECg4Ge9NxMsv<|eVk0Nc5#JOb zy;?N7?F%Ay)#t~=F5?G(3eZzZfg2O}dMa{bJR3Q2Zukb^$ZDb49+v9U<7l>VRNEK+ z0Gz*|0z;PYRaIojVm5N(4Dk)&li|WrJ)EvjkW<;VQ6&g}2;WyufgdOHHC5!tNo?fA z`5}4`C;5SQmiSJ6g51V7jw(T7dJw0e0z+=)tE$M54Q%AZ88S+UQM1|XGGF-dx;{Hz zV;e@59rB!pk>M#7Qeeg_d^HuB@iH4Zac1}?cgW{Dc@6@5p@!Y9-G3PZ5F1th#VM<} zF=`-RS4EceV`#f>Of-lU(8Yp9%OI6o{HR^>MRwfFMwYN6#-y%0FKkW&#()UU3&((PwJ>*|#(;3O=-K(R)3j0o zH!0T7h{`3@_a1JHu1e(zwQ>nzN6bt;Au_i&sKtx>Eq=M1f_oCg#xO@baM7yt>~gt1 z6HFvDfo*)Nl*59PaB~4b&5O2vW-P~=!IF(6zm%={xUAY3i}9ybdLY61CUKJ4E7 zF?9!J@|jY$sebXy#fQ$V&$bl{rAo%1-2C_Kmn;0uz@pVkBJ*@yHo76q69f?>go(49 z(F_H}NP+X&$bx@5mms8`q0_mtMF>~I z2IZduy)+%f<814wItckv=bAi`49q{u*Hw`t53`Xa9Es5Yxow!~tYSd8y-%|Zivi(k zVMg;YAY3ipFgpY?-x{YSrG8c&y%*6#^Mmi>z7wj1-bJmaB^#U{0E+Rt4Hn1To^4-( zkOtm6cAtkDOf?ZYJijZu0<3bn!}Al_rc$lL-KC?lrc<&8=>4U$^mB*kC-C)CbVTFX z$buspM-bgdg&~*$ova7{2?R1rK^M&i>S(q_RJkC}>+R!$w|HXQvV^amA{7?1krSuF zC^B5iH>(j~#OeBsIF)S}RYuh6(^%diq?95tPUdT=NQ{%%$QuhWd~+NDV%(`ujN90T z=@Mf2N+}ZKR=$>s#Mr<_mJlOG@aM*lW{?sCqU+;FGis@)?nTMJX7U3z{ZzT5I6!YJ zWmSnAY`hz{R^7PqErO87jolb4n6A?-Yrp{vykpYsHOL?KODIC%*av z`1&cn`hD5R!mHmqE`^bnU;UAbX^QUPOs<$I)wg9Tt%YX42bcxr=f$e3r_N(y8+ zk8KZCGRPNr`N`mxPUOMaeC-r@@KZK&;yf7aSgNo(K!pF*C&GhlgQyar);NOg6+Fcg znQ$LpJw+zm%|=d~37f;tCRl`&Dio;dUenfC$OY%}Khh_~2W;D@l0v=_-|rp5 zODgi?UB0G@{CJCvoH##*;07Ov;nI-ZJ{@osTRouNW^4fv$#f%ZSK%$6$c52-{S>({ zl8u}=7lxy)in(-Cra5$f+adaVIEZZ(RWGqujp+z4AG~E0IWdE;qar7!vXK+##Ae8e z3T${Rx2B81oouW02~lC2M3oSGFbLrkP-MdjzJiKu$g`0XXT!)+rme6tLwU4Q`S!NP zOmLd&XZn=5iftEFO4OQ^uziGBN|6_r^R-mu#ieZI#Cb7rRiV^8*(ewCfe_;V^y%XxiY$1XjVxh7j4?!a7}6ZeH2MA-}C}#p{+2a)bBq*9Hq-Yp2N=cor|px47z#s@4&NbX3*IHTCFWC1iiC zSC1Y`JB(N1PwOJ1H`I@@9KJQagbT-a>}Y&te*ca6=RfA39`G}`X%q9$r*P4%(v?kk z`|66CAL-Jgb=YgEl!|0$7=Yup>;tyA$z+2jl zFM7c*1{hC&HsAT939k=1%_|8rS$PO%I6+;d&y|_%Z6w@|wl%f3`C4oaH&&($2>@o0EqFM}`)j)3d!g1Rr0{G?{@rKHa>uEZgF_G!%YS zDP=OLY`!Vi-fU@!dMyQQSEH|6i{b|;PC)TP6hA_7GKy1BoQC3b5E;?}Z_S|>%%dox zD1pedSBGNirE;oLNR={;?b%#283iA67Tkl6J)55Ae!>U4pHKt$6RP5VLY>@CsGR!= zwRAtBy6z{`+x>)k<0oth&!z&npRgqECoGclsl7V5C0A&Kl94&f9cOh^hnaSU@fGjY z0nLTBG&nFAfy2UAdp6OZfu%yBVhz}K7z292YqIGg8ouo~ufu8!u~C^<9aPRX=Cb*g zsg2M%>u^6$%j62H;B%jBGcXSRGq5?+luMV)mw8^bFT@Y|bQ|14&DFf>X4&$>jQ2O| zs{K=NvR1CSl*xB2?Whh;rSpaSboWYoW3_Lg(a1DG;;iF4T9?e}(O&J})SAx2K}(qm zm7}lq(fPh#Q`U@F@N2xqxMvPF-NrBJPkXgjQ?ZEO84to6XAFii8V|w${G~P?g-i2{ z-=eJcrxMOD%`ZYJhv4}~;IT3bpr~+&4;aM0GPTqKpN+@q)+b0}Lp{%U68^u=cnTy$ z3D3|Ui25*iuu9e1U@MCu11g6v?o#->X@Ah~LKIsL2l4!95Wn39#Dn8O9R}iGD1N*U#E3;8o=0)&Vi2Pb z2k{Mxn~nf+|B)b$JqpA}C~i3##1a1k;*MiLRF>j%?3eg2Py*wZ28gFo{H6%v-zz}m zSAsZhHHg#Jg4pc@5bI9@@$|_c4mu6Qb!Q+F`X|(QpL5_+Gm4*`3u4xJAYMSRtOLa7 zC{8&a#HH!e{L~-JSAhv%9#O)|%J`7^r|AN^0 z5fG~$1<`@xxW_@f{v?QjzXS2hXF(kO9Ed)D05R@G5K~dSj^dENfw=qcAj+?U*yT+S zv)%(yL}7dgV)&<6W^53!%s2lVF1?82pszu^gJR7$Ao_m`;$jp#{RhN5DAxZM#Kayw zdl(;}IK3x`7kYzO-3LU!O+h@4;>f-r-bS&yABe5{gV=!LfB_(G7zpA~6my4wcz!bw z7YzsThdK~nZV94g8xX(R7DTV@L41FFPf!^fFI4%-UEtDNC?075vGXJl5AOzI(PaD? zHf~g#Q{YmcJweRe3&dL}R_zU-Z*^v{F17{yTq5QB>#)}vUu0>lR>jwyq< zeHDl$t3kYl;`%ip_F4<#F%(OV1Mw}2rN@K#FN*z71aUNquTBDS)Tto;whqKMD4wi> zXgdc#o`>3EsDRooxDYNKhvJk=KrkAnE-2@wB!8pO0`K)mrRh<`p0V#k+2{NLX|oca$C z2fhK~?YBYn{s6=;J_51i6A<@(4&vnhpdBz)LOb-{6k3-_C<=W+4C@EtDipi*2k{V! z!v=sDHW0+4C=MS4V$@&|ze4f*p&(Wa1JQFhh=))dHUh+JC{~OFF?tk;>rs4J2ja%D zAbyLY=hh(py)B5<;~)^C$PJYmw-a2Nk7D-DAl^f9+%6ynP6Tl&ik%xkyoch7NgyWe z3gQ727w-n5JQ>7<-9g+q1;m^^LA;FO(!D_Jyf=t@P|Vo}#K$O3+84y5`+<1xyC8ZU z0OCUwcYF`T^C*s<24db!R1`N`qoP;OhD#5iICc(*$54EK9*C3YgJ@U);=3qTEdtlwo;cnyf|Y4 z!gv^>98;kkB5`obhk0;_7vedLk#66{hS;$dMA^jf>+vYuZ~PKsEK`pOkszj>!p!mj zMrr@Es|^#a3l(LuXp`k|zw!OaHb}HCuwD;FNy^q~q+Yw^0l`>1#fF&}StM(g0{c(7 zLep};aZU#i+j8UT#5A!as}iLR?vB{;(~l_`}9B_DsUg!_&Cp{rqnz(Qlm3zbWk z%b9XRAO(t5Ul_9+f5m|Pk|S)W-FQ%VjF}g0*9Cy~FbGqzJ(*~k<}3~a`yih~$c2Z+ zg)|E7rF=T)gKPve!5J2il+dWvFYJ9;$BQdDmb`npE2tI z&Unwk zv=5*S?lp|nHb7vO-7GsTHA9Yv+`krXHCDj@l*u4z35v^GV9>Y>ZB&y(Ycl?+ICxcm zf(@{}%mk%Ox*3K|Q?Pg;V8mVFW}|wNjj&A^LN=eRq;iFpmcYH2!EMGpFkoh?ABY9b z;;)2=YuOr`S;2^j3h@*Ux$OxdrsIzBj;dQw8&0bk8t!3?uLu zNBXMqTDafHoNa*^AD#mrO@@DhFTmdT*=4xY7d1K=^BSh*!ec4IYeKviF0yr&HZ>7= z9-mH-*PIKGQ_iy?_w(1H+jWY93`A9gI};#{NA++6p%vl&5pfUiu=U+Lj=tXbia;}f zXS|^Y>J738^fLr)Ki}5$fH<0pkrqM!1W=7B7ue9!_${i^u=RBMAA~v+0F8GX)P3V9 z?2ow!_TLD*28OiFmvlxPSUO6=-o%Bog5I}m9bg;#IB!KPz0;-Tc=C-Gb~YdyPdKoU zbRxVD6mDc}S$%MCBHY%ptJ`ZmAf6AxG%S+^)b#sfGODg?5q0~^ZPf4b ziw)TU1A!Rzs|}#l04B_q)ngH%_Gqkt$W%9KbYwY_rz*F-|Ob8o0aQ z8&3l#9YO(8TiGn8ak-$*CptXtrtX~&N?f&cy)C+6HhLGB##%k12` z4b$?=!b1f@?2S+Oany#QhBZnI$zjAvmnY#{I*0M~fI z0hfE98m90B4utzA;+}rHt+d?xMB@5G2LiqXFpVwluz_dAv_>1YM^-P=6Fkr(CIGte zlJkg|y8FWi0)7{PjXQ1KzZX+?be5@hH#8yawE$~;-F)b{)1D$?}?fHeN&;L5uL5rr2cD}o+!KS19PBBn1Qm3^@l z!Tt_m=RIJ>}&2Q9>lpx2oIc*cRYlQ72*gpWr`05!OOdhGb zx(QJ+0S&-4T9-d-14(BoJzC*FW}pSz00749=WG<|3`YPZ#IGI&AjYT9+ej)_3Jue# zaJ~P4R{E0#W-RY*97GOksquipI(-+U4KhO7O9*8*4gD{Drgo7_) zb9SW<=O2i(&);mENGsaOYtL1(Vmwj;AY&}7^)n3?`I6db*=&KU^+X&wZOGx$M107a^ zUk%8{mkxYX$Dx+?-d-zV_xl#GPXl49u7mCEGZ6Mgh`srLZ0wmDRu}uP^n@J&+<4J> zLR9Ctwq-n>SOR|=!K?qZ)t{=NzP~3+=$8Sou_df2HVr>V16?*2PS4o|mo5LrdCqi= z=LBO)!hav}bIwZ})iIP;0fuR|10vLQ0BD>53#Lu$L0kE;&LD#T+=nHx? z!cQDvE3ZLHq31{H?+?(%3j0+x&K&|DJ+Z>|@Qo){hzg(ZO?Ep}GxNbN6+P||#M=)Z zV!i{l{EdYN<67lPslCZMh#m~(L{Nk}2ep0}wq=;8idu$tt_bfr+~INRa2roPQ;=Wg zDYekQBCHDm#`s^@C}QdsHU8wmT9#d%N*A-KHJNlN)s`*8qV6VV4I$w^k9$wv+S102 z>L+<{X%!6My4^Pd`vP!`Bet=@qI#17umI39z%Z`b)&{B-TUzx?9ZFh2o62S(@s3cz zw*sQ^$v7KUu|n)P-dQX}W!+?Zz&doijipGI&^{*NEd?lJ&dwH|v0@^8G~XcH|6@F{ z3w(B?ZE5{4%QUy7*E-SmEczH8ZWHdCh`R-BB{F$+h%7Qtz_mI&_MvxnQ%;Zh-v)Th zVCON?3XOgYx_%$*1VA*VO}4d{HsrNoP33*y5A6ovQyg&VxJ>|jabfv_LfN;Vm`tz` zkc^{Ya~4_JI16`ZnHBsVR`8#3+m>6w(z(7|`)a3l=eDm7uUbOvjZYX2JG#uOl@6Ol zB6>n60-b@|$zA|qf=at*(V&6;6CuBe$jkQDL=JSF2ze!d8lOAJQu0OX?CmuX?54Pt z?i3KFDPqEUJ4^(70m9bpYr{%89j&d{S0eBo0N1$A0hhXwXz(JuhfdQ;xOX7#(fip- zOGEW&Tz?OVfDOPj-gSVbbA8dkvd$4b;wyZ_+V5)Wj$xPEiV^VXfN2bHUO?%Hj7)d0 zyCcByfMq-b+b&JUNhh|V0EHKNDqy|5b~Rd{DYac%JHgC=qnI(6;O=_Z5A8%&4!PjhVXOv#?uf=2X&D+ zR^T_ceRZ&#Ku;QR5I`LO_@>>Z!L0`>G@2){d8p^Ixi*+Ixb=bsx9}0rO29B$=Gj0h zT7`y<1hxurjO;=S%-C!ZeDoOeT=>Rg%t()6kB~z*2^LvbQcVR|-JQ*QCk5z{S0d)f zBbD^GF>w?eH;^_Kn%CGt7J-ce9OLGrZLrANw!w(784T}zQ1=0fapW;Jl(bWb6=Xv# zgXAHY*af$LImb2oP+I}eIQCc@RXS4dMYZ-9+PI#PdMa}f(2P@CSdcarj2&13wol6o z0nIoqYe5>%8}QMj!3P*zZiS!S;8Ge>KssZ<&s7RehyHoGL!v2f** zcw^YoJY*oAQU|UUkhJ|O;33fpK<}DQ88(&5atsAXB=~mjj2$Jq^gNouB>r)YZ6=zkc{`vv*Dy8uc0cU`^8LQ)uua|PBKT-VGm$wSlD*pS6Jlfj6O#2Vmp+&Oi@D+=PB7qVIK?t-Ev>qqgqe zP@A9+0#xI%&VcsD*aZD2gkG}V)>;~3)z(@JuIT|SfNhM1{A6bHE)B72KcMK1stJ2M zragPg!It(lYh(MvX##&4!T;w9TX7xB(Do0QY<2ns^nm4nZM^3^K!fb*5~Tlxq@vz8 z|7%1)?n+yIY4};&HkgpsJ1|Php8?3mz^iO{>11AQco+k!K;qL>CYQl1>3$oHg(nzq zIFFG=RqBs{bVHDB)N85F@EH$WZ5KtuQjm(b4Ci?H;eQXv#>I}al(yVaMZf^Wr}G_v zXZ&b`t+TY{t_2!&z$>l?++H`>xUt?=ikAG{38KshfHlr@uw&Vou+b5SjJyWn7u{&9 z9?Sm{ysra6nl}Ne@oxt@)|*Pup|4W^p9A_j5T=Ke2CB8|lKf0IzpUWn%EgF3<|Z3I zzV>8yPZ&(L*$IG+8y)mmKA3D1=EUwo@O^$^s~(>+LMMcDpAE>y@i*J>@hRi$eGv3d z0o7=?Ur?iTD}40CWjDb$p14dJSDJNh%{|P=uxxf<^D;f{afJKVEp|bqYu$ZtxcY}K zz<+#%>4nOE7czaCgskDQEaeC(6*{LuD$d2sq1C(NfQC*<5a?xqXAF420yQ3h|9P#v zJAQVf-x>v>N`%GqW=X8UFI-sfx6T3t9rv(ZkQ!Cz2Q@$WjJ*dS8CyPT!$}AI&8qXm zp*snI+y&5#_nxpJ6*VNF6!lsB4cOO!WIXY-ZRA^?fsY<4e*)ilj9jBBvS&je2WBJm zW$YOS|GovP+o|}RKRj!fq6Vj3TysXLv9Q`_=GOtpc;$H;twzHZL^H_{GD}68eh9F} z=$CBl8g3+rOq&IA^8huL}tSwW*z0l=MdvgI2>@Uyc{cQUl02+UK+eTF^v56W2_ZA=- z|9ansQ)FkpP--)CZMLo{wnB0aZ_Y+4e6J4x_tKAS+#0UMF1(AIgXH_|!Km&gz%yoj zVuMOoarmpp1L_$aAT5soEaMlS+kn!QgMPpgY|Dn!wXmaTabddeExH$=jPieMJVgav zJmk6$@W+5%F|QhI zJ+dkteX4=!iJS>G-xpweKw|0|{oGoxfq@Ic9*X&{&(_Bd3|kQPm55#6-_~0?mQzb_ z@1O+%?+Td4E&9OTAqxV25P=UIU~4Q53TtUB4p*Qrp)xCg2AV_`nf1ur#i!p>=Rdjj$I0tnsXa9fNPA zbYLcppkGDkV@K+$9h^WT>>|J#o5E=Vrmdy@?HVSbid>a#b~7;(dK~~87dYr~X(Dup z3H&+)f5~}gq*0Ae^@v4W1o{Qw87IPV3S`_GfXe-S$Roa>JS+rzoc+f5YxI9nlfsPJm@p;iMar7}DNg z6kyN?-7p0(_l1*pOiXs+DZ=nabENAb0BJnw;7VhcNL?ue@J_uE^h*eRI-C;}(^rtn zzDSN>FTsnQfzwW0+=5G8SD(2z!Q-+13PQ&wG%W6e z{QzN4wB3;LIG(!Y3BGQ}qqyAQOS;Vfsbht+i&mwB!7Lf*IfR-G_+|m5!?hl$&{&qh zjzT^6htt8-!9oL20y`9NjM1|#Fk|*1@X@rvhZulf1>bmVAbou*nM#|Rd44f|3J*dF z^?pRXV~$+{=^#f1-}hRIQBFUbdo9WJ`yna3owm8k3&i*2+TRcR?lNa6FC1lsp-0NwkjIG~}g6M-HK zc*fOm-X4|PSh5s8daV2ue4}n;C4y^U}u!#8OUJXA-+qGaBp%P=eoMLSJ>TASwf9@f*lvFN5#Ea#+ueI9si@~$Oe*SrOKBuM z-(ng`_gl(9==l~i5W3$|GCuQL${vEr_}p(P{f_5bOuyrPOQ}W7Z$CzzF|~;ME#;*! zzkQEN1bHdkZ)vlx=Ud#Y>wZhSPd(q_?o;<$+LUO1`vD^3rbPE!TB`5)7MJR~-_mMz z&$qZ*-TjspSexIzkI1;d+WnT6`FOs?Wj^k=v^>T0EiO-Szom5*o^Nqoh5IedjGEul z2q4aky5G{6s^?oAQ+2Uc)rDO z!2On*G0(TyjJe-Zv*Y;|n;qv{^H|nuuTq(gr?-w~HBYYZRjxGSak15b(AcyTia0lq zr^H%6s&1M~H)e8lZfvz591jNjZZohLq!C`() zDUf zr27?UFu>Fj@3DS40)IKcRFf`aSx8UMkIu#)^(RH`-wdE8x8C!k7fmEwrfFWr=K+cF zj5k*^JWIGb0H|nRhF{g;L16Hc`gDChv#Q=c6{~}eL^e)M!5wu=!hZsH_o?KXOZ!&w9WRM@hU%J7Qp7;2Y@YR%YOzaThbwC_36A*EC9*Ymzzr2V#UI_330Z>5@viqoIZE9{X;oz(GMxnvk^>rAmw~Tz z!UXADKc8#;{80QkZp(Ln4yeN#bM;NI*uBu^R=5+KMS8ICGw){G%+4pb4Bt4|ibT`I&)zQ}7VYV1$<}0hG;N0a8oz!l$LbO*MH7nD6P7_42o1p|CrN3niKIk}5 zw9P2GPc#kPiT#S|D7rJuGQ(gXx<1gfP>*AK=rvS_!4iXdp(PoP_XO2+x+&G+c$28* zNW4o_lJ3I}Om!sQ7p7yK{oVwf5_NBW1fv7hI{Tdwz0yeYuAsh7*Qh#jF^;;0Xk2IB zDe6r3V#ldkXWkp8^by>0H2`2wXVFxq!V6xX|HT zz#ax%xX`(Poe8*b310Xo+6em!aACc30s8}R;Y#NM2K{j18s`EA@o?cf=K@CKaN$Ph z0tVY~;TL$piLK$nFP#e*JHv%vITtV@h6}el7ckI;3%_wLU@!|8?!gNbC1QXI7w&g1 z;P4q-c*wbcaU@)L1TRpah0!5gc+9zgqh@g7N#_Dab8z7q=K{uNaN$|J-~?H4;d$o* zj)1|17n}~Wg$r*u7cgjm3vW9Y zFdBdh@8JcffrkqpIu~%53od-@T)>7IE_~`-!1fm|e1R9J5yA!*E_~%&zyU3|@U3$J zn?tzJhJr=K>B= z!G%%I1$66hVRO7dz6^aeTo~(Iz_BT~u(fjmy((PT*13Ql6E2K%E}%bz3*+&Eq{EH%5a@CV-x z_=9|gKgdz|gEWIbNFDft{D41L#qbAf3I3p6;Sbsh{$Tmx4{8j5P_3mM?d|Q=O=-xX z+)*89jdEcJv7*CC zQ8s@D;TbHZg6w=ba}pudUdlp2tNm%c5)E&_r-7x+GDyt<{od6$WLF&tx$bamDMjOC z=Jp{pNOiRT3w%1B-9z7v@_*NsE*3F%guBN2zoFeoDbM$t`@e^V!Sl$#&89}shLy1A X4B&PCn+uIbWld4`m91=hW5fRglJPS2 literal 2449997 zcmeFa37i~9bvSIDyIS3+Z@X=by^{BkZES2=J|!QrWXV`EhmDw>o$cM3(d^84dPXbx ziXq{U=mZ06z+C@?gphm?LgGNakdqriAmI$~k;E|x;XWNmLK43BUR8H>^-NqUyWXC6*WB2t)TV9< zJbLZ?<`>UDI6v$zDmI(*d)=X_MycJh?Wsy@-rd0a=(H=fu|l)hF4roP^X_0_f4g-D z{5Aht_;A7ayu0Ks$8I_E?m)9rYSh~mc&9SkY}t;}n0HV2evY>7Bkj>*>E2GoskAGN z`n2$l%(v(7gy&AHgpib%4VQU3UNuXZ|2eWW9&6Bki$zvDv8E2;ru{ z)0s-U+?i|^XP_s6V0gXQX*U4!dH2k~+xXD2OE}S5ani2MyQ>4O_`$pl7;81E@Y1}y zaTmv!bpQZ^KN_vJ4MbR0YBZ0wDl=tx=S7!pz4WpX%YSp!x*aH0v~F+IEA2+BQlGIJ z(^k7|TXz(lwl!+qbjQtmMt9!2=Y|_cth?^Mb#&K`J@8}iu03~+?znX4t?c*ctrZ8p zJ~{>n2cXLh7}dJH*lybm>(0h(v2NFD4eLTNY}+o*&bvdiFoxYK(8QNi>QnZSd3P<& zx>PGVP60WAxNh-e19-(ZMv|$}Zp^&ipIfRc(8=Su3_}hZqbf;SLr2XY)mO zjeUfD0sIHUF=Cb1xXTWdJF}C{*c5Q&vay}}fhij;8&En_ZqL>VfP`5aIG&-r&}vV4 zk5<{u&Saqk5KdQWKuH*J6@4N-T1k(pftOa+JF~!Wh3Q6Xw%8`28Q}BXEom1gYw%?70RERA4V4;G_UL4-Q91%OmA|@WWlpnF>hYv(70X zY}hNkxw*Whd;s4Z0LefJm@n^VpANL_nq72k|G^RhiK0O2JB53s+T`66pSjbW(ZeM_XM7m;F{?r$|3?FYy1_u3x3 zALQXd-mx`f0~Z`=7mC1>bA{>7efJ$Lv|Gix13W~ce9Rr-@8DG3vjU9;z{?D3r2=s% z;P4oMQywjk@yX!W#L?c9l}@A6D%l06Gd*27f(m5uSj$0O3LUjuol+YpJ%u36JFJh@ z{0qAd1O}1qgRePl^HgR^5x|#*t=^yaR@NCyKu`E4^Jzm?CD~s)-JR_9eEf( ztvEFWGq00s0jX($l$C7nCbup5UG9xh+wJEm_&tBfR9=g(R6OKso<0W z#jJni>M76#6_3Ja!`HJQ3ypTY(Y6aMd)fv#5K+xy$>xZ4S5OyBsd(6SS2d5$pgnR5 zPWvcLi+FKe8I)WN6`ju(z@QufocwFWJB=ozH|LDQPp|g0M`03_KJevU_I%|b z+in)-imf^j#Rq1s(`k~W5-kV$V4DDiq}D)=W@j69U>_TK0NM%QA>f08-K^9aGahN^ z5pY@H(Kogq#6A z&Ks99mFH1&J=cJs_25@w%5H*Q-@SmwE8kGQkqvzIZ1G5C7TST|>qQig0w^)~$F9%7 z`y9O0phL?A=w=d32ne$`##(xA-wUYrG%f#Px%UO=4iAGv?V3{n9afn>>OESHk4nIU zt+wM0Hq-(CqyRb!oFK0^2Qc5hw*$=U(_>xWS)oYc1xokcouCbgC>w(9+$@nEc9(g| zinQIrY7q?AAo(=t7om5t2xtVlgA|3`;h17PTmTDLE;<#*E`TL0+2uxU3O{4Y$z4hI zY7&$(Q;-`x^Dp@c<^9A7^scAWZ@GSuoWWDv#rT}`;SjyStYi6!@)P*gI`OO!5G!iq z?^oI&sO4ey{&ISsy9!`*=tTymhlly{1bfV^EsS2PIA!o>yw|xE_6-zRB94H=ofer4I$iA0<#2yaPSDnh#JUk&{PmOBa5P`+!!7~SJ zHDLIW;P{|y3ZNj+-Q>Kmf#wMU#-11EDpM$er?9WsY)IvTB0!a0siWv|%&}$=1#rU9 zS$hSW$r|btyyH@LZhU z8XK^DRe2j%$>h%v(|KMVYgiX(2yh-#udIzY&%MB7ddWMsEYPbRv?XNxxn5pF9|9U$ z?Lu4sbLKxvrfC`V2ui&H>Vtq=K~JWOhsiTz56jn-ucg-Vb>-{XYaA2=uO!cWHFW^G zyj7GNHnOq6w=Q&=Q{WhbttW4b#$|4)11vmQ1Qh|sj~=s+!0gkys#t?Ak!!r>_0%V` zGD!SNiIg6F$^a3*Ca?uQFYhkzVSO{_ILJ2i9iLjWR%vs7Iu@KWx}F8*3Gp7eS4-^4 z8ql}(8aEM$;<@K=fxFcP-->n3=7FGqu+$y9*eaFLH4OlSlIVTQX~iJL!9oqJI|4{} z%53`XX$&6pfY>g8uIBe zX#i&fTqtmYU`2qYi6+Mg%nUDDOzw`l3&$+}F&luxTJwBzjsiYYLRe76Q{QY@Z=gvEC+Q0wWc{cJ!@hMv_!Xh7|2pjg?u=kd`Zny=$ z+q38TT4k)!n(@9_fpLK&AT2X!#N5H=6u8gu1Q-pczM3_ACV>2`@|b&Cg$GjL%{Kg| zILa7)mKbA65$#wC{#yquTN{>UH2M%5of`#uw1z!`NE$FR=~zGEJs@Ku3e$&YqvnzGyd^HT$q#19e26DXuK{vNtxcHzN8y*loZoSXggx zu-3B=sSPa#INj)K(08j?8-xRBt%iz>o^N8$1GdI@eyDvdYo~YzU*Mta3{Ey0hu|T2 zB@of7HCnC4oC7jK-z?|fkWW|rZPX9+oWsW)hk%4qBgeMvRZt5D9TX7J(3V z;fv1mc-%^Q50_@N*xbxIJWIUT0tp3;KMQVw2kI@?xBiM2BJAa5AWGap0nPx4-4G|S z5DAL+`4FWe#oY^hNnDBj3I68%dj>w8Egk}IfC&r+qu>_=Iz1L&?|=~1Y-Z^_m#VtW zH&1IpX;kXd4Gaj8m+7gPcz|JfG*bOJeC?jQuWlpmY%AbaV`$co_O86W;*=n+3c=Mz z#}Qg(oi}8R5?SN5MP<@!iB|#sJOq;5K`(T}`E#S!MIEMVw~Lh;h<(Sy)M;L8snvk+ znIG-(ISPkpdzdbj(KmGmwp=s%(D@fnaO1I(Ua8x2h!|*I+^It_g&CIh)P`Xz-^KA) z

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

    KPjs0Ou3L@-)ciSQb@zDjzj)k%bmnBwv>;z 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%0A4zNLv7PYI3{Y7l+exTwb1)yv0F^}! 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�itV*bFZ{5p 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
    flOl0U<} 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

    fBMT(rCc_W^A|!;9j__)^4s-TCgf; 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

    R=~mb?)d8;ud%fy$SUZoJi@?r3|mu#Y!)h&&v3 zK==GDI*G;XkMsdAzd7OR&DwZ=uA`P~d4B{2gP9|tam2uD=nk=}Ygy(+wE!q$+@eUz_EYuc;qTNo zq**~{lDXnPfR-@p-iUuN>wXjeBog1q>?2wXnYF_}HfCy~k)()(p!P(mzoLKmzR^sf zYL7Z_YxZ0HRD9|{P{{)LJN#69fEQE@UCuNC+Z^`QMX&Kv)2zSqle?@-^vcw?<%ZrFazPsImYK_v@pKjWw3gRP)q zz;;_Y%N>jAT3K3CiQn*cC@Y$uiB^o2b(2u)3B^G)``@G09;M^9LH^A<46kO-L1onj zpBa*YibE)trL)ybBC9-xXX!Y(<6d2Nd%K>2JJvjP+fz?nG?gs#^njm=Z=Q0F^_TRs zy(Avhk`BFH&md`Y@yVdV%?dhmGTTvG9j+tS>a@`;CzmJtv09&A>gR^fthxhLdo-MT zeR_?bif?@qRI=dyH~mz6xEEB6^(lv0^&T%}UE(Lkta>*SO%Ah)kAEPbW~mgPI!|{x z;A7~R8x=5m_V{kN zj@S=8Ot4W8KaA~R$rAIV26raC##kbH!VWbgfgq9dps7I_>{6l2YcDF%tBGl^ikxY@ zd<}1UI#Q^1*ee?fIFo$@DPDZUEc%4-<~&~4gH%cOzGGQJS`$f7Y3S`LOQh;WiH>t} z^CE=#h{ly5nu_Q!LJ1@B=ximdmeOytU&VfR$SkSKP@#nZMz53S7?Q}p{zx(Io;-|; z?xNB=qLQ8De-fWJ#XhKm=E2Wx=mrugz(F!&?MY&{i6|#xupHx94KhgPcuo!9U>5 z_V}b=OmjTYV?EoVXC+-0pDa6#m!6aht%_l`aQi^ybJ#Y?hpfg;GtQa_(Fb@(-^V(d zUR1X`c{GLsEy$3rR*IE20xfGOw6M?!yFHPH=)-8+v}NusKgjf2`E5zeg>x$UAj&4* z-;|ik-%^*e9%+*U6q2w!oF+YGv2TZFH-a$V8ieLQqLS~(n$Fbdvnb5Cl#Qw1LGkGG z_{hl1llCTILHbkSdfBF>;wIT40;4NTn`HAKG&iEsQgL%D+RnpGL{`Mej2O$FLB^Hn zh~;}UvGk~j?eVGrFTB!4=P2{UjJi?D-)=-1I*!ASST}b>Z|(zYT3;#4{Ee(!IXT+Y zqv$7@FPs)Gyt}pCWV-zjeG}&e6P*rC51lSFdh|B5o9GBF*drvOJyD~)DVJn~_P(AW zu19;*V_pB)+B>sSIcznQu71fn6`H!tI+b2@F-m0~ZKq9xgc$}*dwAi1siVx}y<%2k zv#a(8=e2#(r1b(fjiW$G1 z3oZh~4={f8V3;0f$FK^Xh(4+Zs(XKIHJJ`k4b6YZx|d#bF=&22+D=0=XNxgYqc1|Q zNUg5tc>MJs1It6;%YzdbSoeHy5SrgcrKMVee}K00Nb4LL0Kb6ASc6xdY$7`yTN(@< z(uz|prp<#>7+74p2BCR6DlL_`o`AN8A+CLc42+A5GF)_%iJlrd&9N3a$~-Zn0#};O zZlwRJ^c#I9PUcnVnQ}qmIl#(px`N)VOj}}e+2Tp%*c0=Ou5%f(qdQ_&*^kxuXLHF+ zEJQO5j6DkxJvM|k=nm~RtD#Ku{ut|2deOx!Kewapv>B2x!vJs(FB|}Nlo?56uk9b~ za^}Upo2CtzB=6)(wLN|_7k9<`x$Hhb_?zjP4%kN;A{DOe^8M%4R3G`7^UB{|16!6~ z@4vN)UPopE_ki`gD&{yZdOK@1x)cA%eT#6pMt5t^;x*Tc2$h(D$#=z~&*qZ@r~lVf zNq5#ozowDx*_Lg3PJ<4#mkgZ&+4VeE-TFqJZm~_|*ZNCNa+TrYl<-#4R#JcFDR+ElB{XR;i%MD$u= zo31SBvZRNyi0wg2>6xNY%h&9?OA-L z-XLN^O@v3=WpxDZ5MG7Ovo@l(UeA4sO=x`gk4lOChIbl|MaMxYx)UEp(AP}LYT;)x zERv>9WY|$>nmEoD-J`Q-y;*{9r_G0o9lx85!Fh%)dM&Y|>u1N^Hanu{>M+4T%CZw}oJy4_iGw-Op>3JVS)r=WzpY~JnWv~eT(4kKJM#T;|aAPWc4Tljg(8n9*C~R9)&Zv2Ey(&-r9B zRHWWGxuS#;-4BNI)SKvLtn|?@941wpX}@1-6*8W z8+Dy%Ce}z@1%=*417zT0{+Cfp3Qg?mw0mpDFQN4m$uv+#I3HeL5ZN;o+&aQ;sjSQf zl{PLL!x>$9R}n5<%U4U`V1H&D(BAk2r@`o)W$O_bdA{f{82O?eM)uz`>p3lAS=S#a z#@vvHF>)Vw9aY*XXDBIq@6P!=x!@?j+;uQh$Sp@9A%Or~@U(Z4d#?7qU*J7uEGu-qe#j&~~yz zqM5E`oFvRJd~iLyaQNUHWlrxWXC*niwo-3t0rx^54rzNZ0<68$(Um_H6<(=pGPEl8 zU~-P;+Ji|AspgMk4|YE~LHUoaJy`T2Qqoi+x}E;|82$C)vTWdX`l`?aiVu#0iUG%!2}@lYTK1#j zXVJ`V4R1hl0_|yd(x@&xye3&EBqO;7+E)trb6&fiP<6iKAW;j}jTU&x-u2 zx9eGCVBSyXrt*fxQZcQT49mV*K^K9?0?$&ZCxqhO!5SeH2A6CbpbvRDrH|vpY=GX6 z{@5EJy%#1oMf}9c62;u;A?$aC5rcPVBR)S4=)@c7)O~6rc9Nb?6OK9rUhKq z_u&UkWIEwE!piwhI5J-m4za`Ozz&ODXK5vQ)KLIb?%TCMXxzu&G>ty!$Vr!?hQeRw z9k4qfz1?lQ&nj@6XETLOno)~)oSIu4@?Q8>qe>7yu~hQov%1`d0; z;lQDz%<27Pte9li?vKpp_u)`xWL{@@gY;__ESCm@?L|d;^NGId9w{^{c+t|R0ItYf zKmwKyUCX&7RH^k;vEyABoDRQqAZSp2C*ug#b@Pe^daSqA56a}85&g6G!5^`j@^?!> z`A=E5(u*zz${$19X;9|uFqVtdgNIBxrEI8Za!wF9}8T* z-9%iv>#bUs@4Q&qtoM#U*uq^CUd%~VG2>S-1`Lrcu z<^}nfnGVkD2N~HSxmg^9=JXPBGt9@$DD8;xZG#MqGtdGSmEcK=PY1hrk|O)-(UPRd zX8lwPugaY-EGkJ+Z)g}viuaJWSZ{QUcTyZcW2uV4g~4vT@|9l3T_5K^wpM0#Oth=<3X?VY;0vKX&P znmW;0jJjabvlz`%S{9@6gHp27$#@o>#i&>lO;M|kEXK=+1t{hpFwdY3w{7eA+6JTJ z(mP>2Ra`yd<*GMw*F@Efd5|yjQ}LxY2r6098}9d0@r8~B6(eCM$BO%)m$L3ZH&)yw zJIMAHCK_|yPnYHEHZ5DejQ)c~oP*te>K$~{pWV1zcysbHJi0Tu(5^T2i&mFkDDjKG z6lOo|B^)oW_r8?wlT;MFK<-#a3D+2(8k z7v@i~tLID>##>vRMx)+rPqpT8!St=2dK<3>W@jY6t8+Hp~l%)9`(d}D53iCW~dz>?%YTe<(TutvHj#PLdc$0u}X z=Za1*c`m@^dAK7t*OySGozl7^GsTe$pj;p?GK?m64s&l%Z?r3Q9DL#-SnWh6FN`~> zs->_w5gg=tvxW1Sqq}nNICeP`2`qa}xF4*i3PHEz;*m%ycs?pQk_zat;rp}v4Tdw) z7qn?w4VmVMZWNKu5uuGb>r{GC-G0iORM1A-iNk_4jh0lvnPHAC^)AO>9b{yJ-Cl04 zm6(%CMmUH<*-K0RxTz0^HE$(pr;M(wi&@d3_>w<|ru2!OKFO%7!k7G6s+3)k zo_8ob3eN~^`G74NV$lp{3N5-Ct`-yvwR#O1c2&C4Z@$n@66^-3siwp?^9+qS9>2l( z>SuxQEfWidEG@&2?Ds9NICTK2Eu%`=rM9IJIXznrlh{>M$>Fh?JG%&d6TAA5rhm4f zM?SsBEvg;S=Kkemtbv%D3lCo1Asx}up33EjmNu`FwZjq3w7J(2O(nP^`YMc#JEGZN zj{-;ZdF1MC6z0Mle&{pP4$Eup48sk5m)$qRjLhtXre00(s*egMbO|6xUnp`pp{J9k z;)N4?l->|Eckte$-xRjVlAW};dY&uo)=CDmXb-gFOVp%R9Ukb*h6E-yFw+4&)TS5b zf4-5&q=IJKMy4cbC?myd9m+#%U6d@FzQYtVA5$4Dk)cpWH!1+@HJ;{fy|V z^e1n&mVMKmEZ1VJF1Mi4FQz;BS(yRFH$OWcY10hczEJde$-T+X$-^A!1i79n?Ua@p z86944@@K0q5gJM@@apa zuTd9;b2yX#3yS;sYCDsAFy?S3JIegZ@SMr^G_-ulHgUD&rTLPJeHcs_7v4$IO&PsW z*RrBR@g+Zuru2E8KF6r5!k7H_R4Ka}Jy-dXiNV=j$@wcPOZH##42*e_f5^D%XMm6u zF<{8D%Hc^qdB*@U+d!4F%gp7PW&UkTQ(+FfiYhsrmASKvP&Up!q$!^>&?BGHOBvT3QNq!#2#y!dGuSbC=ncchE zI6OCnlwcg>FNTXsY1SJWhA;UD+nDIRo2^vCsrf<;y03`~SJ~S=jjik`~SduVqVAPzP9xzekwkk3MyG}y7KWp#qIU03n~Uq)n2~W z^kcs%E^hZy9vGVgM=!RZKenZpL6$>+;%WXh+=b6IqKbrQ@?G(FpO?Shk^K^5MW#zx zFZWaNp;S=Gg3_D(RD9Z0P#F}ZWiRCukJ1toO%8>bgUc`Rj@7FR1eGi?wpaV9_+o51 z#`1ulN(RIjF}6*+O}kpoyPoH2{=LiJg8`yreE0(?oGg`kqGN7z(}<33c`+@Ny?YCn zfueRFib?;Umq`Opug$%^8YH0o3G~ih67)Wq#)gP@Ik~3zAC(eLrFbyyWHqA(%*588Dvx=O+%z*WlviL~<(~6xzWwv3feF)aWm^6`kH2 zbXs(bq7Vo>F%bJ%k@OC0^^}y>v|QYOwxD6Ym3lR&E9aqYqW#ROY${A$hRjoW5{#f7 z5m98(jFmK;%v~8t1}=N!SdG{1^x0YcI`lXXOVwnuIi^78_BfyL6D2w#<%yCX2uT5e z`&Vii8V}qEpjr3{ruFUQbWjV!5*@$@WFU3%2Nq!N! zJBEq3&Bay?>SGp8%~`k66oABg+J^pItFcV8kb1yo;fo$o`KoDUv!w5|iq52QpLW$M z=#(e0(f9{`A#jhPawfnT%Nsa^Inn!`yJ6nAdSgBT;l4if^Vw8sr}RKUR<5{sjvH5> zrcdf%%;j5Php`USYh;93i-~!Qpov70V{blGg01Hoy$E?9W&*`bGVc(i$GQX5Hh|2q zu-kzmy6gT?b^B!&DL=SGQ2b8T?ewC$|0i!y{BE?J#81c;(SqU$yNsKrdbs5X-a5*h z+yeo{6}x!9S-K-C_hAWzcIf8Tkd^b?+rs!VCkGJ-l#W(otsOIugEoYiECPE5zE3^+)V``!FmsVlR<44qbBHbLN#< z@%*}L{g`X8%h&_EvyKweS17aOyBhb}(@fB%13&}tN>p-W8qwpS0C=a>kiSy`z>i~{ zN-w$?0H2Pw(*VqwVJ<1DnMO|?WMqQfmJ`>cs7|R0S*r(1*q07M{lF6P_42-a#oBHt zh8~PL6oaD-6hqoX!bw$QDp{FBc4Pe&!}2~1GZllsJ-Q)rroW<**$iY2X3Sye5}LyM zF*QHYznlaEaW)qiyoy048mVn6&u8#gn&W#$>1I@db|e}x6VIDyL?w8l(el&r7kj}o z`|DAVX!K-Cu{ytjXqb~xYK$F9S< zSzYD-;`TxVW0<(8wjkcztbVK1+Wh5F!%QA}9Av2*apok*l{~aPX)3<$#GX90Agio*S($sc=1kVO=xc6U=*5%{c%3{*_RHp8N0 zR?sD3CJKnG#HWD`*|H0IGBex@xsjQ%(Pgw{(15$WY|{soZ-p3G1D=Tv^(^S_72@*Ba)GvibAy!ZH5jSN;1+pXox2xy#qsx=&MXd+SBVD82<|?*S0o!xa1#)ddQJ7J_zRrW%eSp+*#H1xG7wq~)}O z*5OKnd=TuqKqf9~mmRp3d&Wn+A4<9(%q!jK161R*t5z6HUgBJjzsb`}>74x(RZ6BP zt;60s5H8^}Z_KqvWjP#Ii<_5uImombs%cA8^TzrCXGpw{!{8rt4Ne5bzm7_dm?Aya z-BGqtU`E0C)>lNp?CtjdvYIkITh_dn_ZR#d>sESE-R{bps_`FaJMlo1rq)t5zRu_x zqf31q{Wx%yd0I5ika8sV`lmb42y$|EE6|rCoZ{Z?#kPP(7bgCxp^cX zH&fp)7!NWq&P-F4q`e{W4TBS~BlSeq(AHI}}RzQsB-q7SoIJ zODAS5M6~Z%mfzwTN`LLTf{8a>7@Bq^(C3<`Ug|O@Lhh7vUA`in9cm6baw3>P>dlFu z5E@?@+e^gWZ zZtq0Rl^*euq^Y=b;?g5DhC5#jxy~rSQzDFKiWjd-2C(R42*r!2ORYMRA1QjVLD(l6+T;_%B{Yc!O2%K-Hdriu(?V_xq{%G9(0*EEy7?@Kf=H z3x>vLi_m30m!mV)1`^S?nFn&v?ho(61Y|>&`R{JBw#N*a|9HV0)^ciVwDe ziUHddWGOM{g2N0oO}BHrlm`YX>%kz%+>QR&`=kuA90D>o5bTmA*#*qCvV_E?dZ1u= zt(T$RpKizbP)>?xj39EQ!h>uv}oG zu_pg}V3+mW;r}2WA{N8PKM*XlRO(6NaGM}T8pl?LVVWim_p0`erIE_8R<*Y}grq~o zT%<%;+sQa3g6MXv6+;PY|2i7tVeL<#pAmhPVeJi?)oNa^AJC)M6wuy&1$Ii6 z0s?3J5&~!~?3ohhSK7~N@_j0^m}Q9h5!?ZQGk+!e3rloq&$vIFhcVJO_V-k2r`#Bj zx$KC0<~ZZNUs&|P7zx(gXY#-(lef6Vf-a({<8O%wXGApF6o`OC@?Bx#KXVOTL`8pw zN{*-~J@z@v3}=7r(hj1ZuAfwww>-flV`^_*F=&CsO{iqsA9_*UT+bUZ-G;Uk{}m|# zEn=Fm$~Z6B!zo7;!cpe5tNjpAJhCe*#g`U-_z9D#mzV6Q`@eA6ACZoW@@ghDmHqKd zvZk0zBp8ED%Ak7?$K!C?1-Tyr+kq$$PRvcnXA@T(y7byTP9SR=1pyK5i5m5lxsV~i zd?`Z)c6{b-4fHrWn3ZLYbMK}lK4mrJWwDHd|1#@TdQly&(QXuG#!q;xQLPj!?e2Z? z8p_*UXoTJ9yD(f>agoLw6;M14t_d>?xc2bE0ar(v)BIxB%)*|^cT0H1Cr?jHeMtD2 zo|Z=Xdi}B1)=Ts7yL=bcnI7uE0&u8sWf$l_n-27mA1$zSaYWzQ^m>|2w0r5jJOXD< z@hb8H1dqZ zR@Le#(zT8@EA2LJqQFu^S0|MUO}wj>x6H(VBRe>}m&i)(o5Uv^QYrg9HeoNvMlhxIU6PZ8CzwbRoy2~?+6(pPOz z|1yjYotKHb&$V3I`RL*{uY8vP$1hlV+ifbtmse@6rOmNsSVO+ zCQV|Ipl~Kt>Sx9)>*ll@k%6epZf?-foo_QySX3{q?pI97^=WxJ%~B4Yf*L+kmIfyh zMQCtCmP3w|gB$Y6T}71~Dw#XG2(|9q^rWfzkO|KVVupvA z{^cYdh`G7&;MEE;v@Q4PxkB60?V)7t2yHXV)*ITU5Rpj_uhL;qbhL@WcJpp5LPc6CAbo`>Y z_$4nFy>X$>qiV(|`?vg5d~qQ`B}-iBCw?lvxR9V?#D%nUK*e-NsGTR~DVDB%qHh+b zdF`TpL3U1NkSNhgbf`yA?(?OO_x9a;dXH0a@W;w9JljiRVD>O7L*tJ7eCd8~*E4V@ z)A`b?{ZxD~PM3k1%l`r|iL5dh`U!g{P~FE&Yu*uL5QS+PW>9oy1)WD`08gBK|18&z zhGsvrkCma+Ok`-Ht&Vz9yE}hV$|&~p$rmfW7gc-IhC3bQjeaUV{Vu3v(eLl{Q}Im< zLB-(ep!)q!yxq`&VQjUQPQU+=OHgk8{%LRDz0-zM>960v>?JWUdj{6;PVTt%`wzWc z&%m8b`u*pADn1ye%fQs{>vs>s1R1)1cZ$NT-vjRmGKj)d*Ixb3m4jJ9$EioZXQ|Ya z6yx4*7)deQbDQD`vfa7|y}Z&#;!{XH(&l6aF{z$~4*6VsCPPKWi<2vgztOv}pbjPD z&v+@JpAmhP8844@Z%WPN7r8}*Px-&1E^XMcO!{wgIPWIrOTmQ!c%clP0+&t| zc=uZ!T-7jJX$v0&i-@(7yqE!Ms?|Trj12Oo?=SCslsF5(+Bwm6<7$S>Zai8Kn2ah# z&ZnTz41*HF9QznsYn}NS{t6T0T86#yVhr4pfrEGnbBETRV);T|mPf~^2EQsn4cd`1 z)IZ2DdR?Jem=D`wvn9<#{gLYAEAn(wy0%|Vm3B%G37LkA%NTk_kH;byqcd*YJx8`M zOTIXO!;-a@+;+?rTA#yGrL?uT85k1!3cebr$CJ^!c zyy&CtL->%@l<5i7RXT-B#O(ur!n&1SRM-A_lY~Buwi8c3=}j$3h_k~OUFx+8pC4pk zg5Oq5#2^?m`%rMx@;H2R5SrgWCEpsRGc)=u3g<{+`4)=jU}pL~06!mOWSO{0_N3&d zXAczXcjTw$rLlc{~rWa=sV9I6M$eZGDM|qiE{dnG)x8TL{28e^YF@A(xzl%D$EX zuqOkD9vg!4>KL!eE?Z4yninDKR(jFJXqgCYr_Bq_4nu>cs_f%~3{3Fb%TABVe)S+U zU$KPDygwf^(^d962N_u=Zl|AIQheRF(aiK?Ww6X{l^_YG?w7%6635 z=2Un}S=m+k>+Gxh@ZHqe{`M&E={NeX0W!aWEc}f5758Ll^)~)y1i7inm0uxu)@sAy z$gf~#mp8wHO7Q%OQ}*C5_BsXj*P|f6Vw}=b!>O>@tPg&5fY!z^GAVA>+8b+^ag1ZC zQeu|@;2m1c3eI&*$#;y3r>y(?f=iAS!v^K@?hR%O$oDPLaS(Njz>=g(oC}faQ`THY zbEQk{O`1B9*9K_JcIHT|GYW`n1N3J~&pb03z@oDu6fdIv)T$#J;?m(jgY7WJ%#+v~ zjHX2iBW|-+mXI41^+56CIbMEv-NDaB)r@hkqMwQ{?j@*XiF@7Zr{W732r35ul-Xrj zXRwZ~o%2h*lyx_RVW{>CbulV~IF|2VqA}-r23e*rlBOqGgl?~4R7_ZIb7?;pIyYGb zB}jXoE3Cf5%VqEQ-pae0<;sBf`lc{+4d{`A!2F2>iC)vNd84=h)422hJHr$RYvkR zXxk(+d@uJjaELN4c}kYu#2d(u;*$qB&C1t zjwxxoLlZk6$!#`<@)u(KMs~qlFDexp6l$Jr*5`w1q-Zq5R;xZvhgrr8#ala-7VfL4 z*CvhAEt4XZquH7))|=r(V_}+5P^ebxM@Ks?ddEXSL_=`Encnb#7j^K21aXj=Ax64Y za5^tITCNoF#&zI`XJml|KTr$VC z(kY`3`X4CN>NVu)RFQH+s*>=`&zVjp=lyM@*NeqY6BlhbV}{DlBLao$ffCs{xws*s zgS%15k=e_dHiTIEJ3;cudCzt=a2=fUtu-m@07sy%UP$=i|W{p zo`b?UvTI(2;%V4Um|%%1FWSM0Le=?~ry_2E@! z@IGB_sFeDZcfSCk{Bj_=LWma>`!SK~YvqdSJ|qDT{gE2*$$L%Er3+mH@Oo5o7!&k3 zJAggL#93BD{!R(d?qZ!vFS;1e?nc{bpiP)zfVPJh4$wNvJRy_7mH6x?`r~z^51UN9 z`rD)Ai_`k!UnXCWMV>MFVl+dmw{bgPZbEV;Ur4`*+8{WRFW9Q=O}?NKJo(}XA%rDg zu)iJ!$rr!G{X>*aUai!^7Q4uo6RyUFk&bc9)$kbYo*IRAS!7_y zcT2Lb!HWX+YC>Fbam!x$+Q})pN>7wldQ_ID+>GEtaevzQe)flO#t-im53d3ObnRzq zqFlKeznU}^zxc#mZQ+uHGihU+yCl!vaQ~<@)&t4d7M;eS*c82)T6LsxykO~&BmiU_ zf8Jn_6D5pT#adZHx|-Aj#iKv;^2i(O`F&K)nA-7CKNVlBM^MQU>-if$6<@4JP%-$Y ztcKp8B>PQq@nJ7z-D_l+21}OO@l_@oQ!QkWW%?3qh9*R6$GaF6GqvN?R0ZmUs4Sg8 zGOO-aH7n@M z$-F*n77AWj(XMr2y%;%>Q}Jm8K_!cJd)QCKrx64d zV|~bB!u+e3vM%crW5WE9i6(~$!^b}ai0<_6>RfQu{as!c*kdOKHf6StkWG6izG%MoejLQ2OVCdj?0 zNe)^p*#4`Gp8i}b7gG8^SV~rK3{Tg_8?u(2P!0X1(nV6f@# zWL-QIv7w8N9u2Nyuz6H;+(=U!gr*&3&Xl2PpRCoi zvRhC~gF9b(M|7|cmt)rU~YXJ##iqhH9! z(bSu8elWF=@%B4fl*p)Xs&3OsE#|cOP%+`+Tv8J%d_O8V%no{Nh|Q~VJago^RzsPf zbR+9jdeOxwoadwMG!>pO!!SE~c;SGsqs)^AfPfOF-C%!2pWlaRnGs!99&3hYtXt$7 z!7VfF7MVho86(oDDo_$K86$_ouo1MIh2mktJ#S;Q-UyrG*4*G=I9upcvE@WzYS4-J z%+Bvu<^q;L`^y=RdJ>rF@leqIR;!_O2y2S-Ev!@NMHhqix1sGcv~y;d#sf1_gr2bs zmJbXvHoOUTY z`j3{7um9GUub8%mUhbK-4!!Isb6^;nOEJ=>&|gvC(}#(CTd)p*NM|Ozs`MU5@5>CF#^uejZ06Guxhd6Pc+5Ph`IIJp9EHnb}{%NMu$6 z8H+AoM=!gkoML$R;MfnPH$Z<_DDOsg#hzQbB*W42j`DiGtd#e{?H1~skJnSOvOJ&M zWe#U@!t^}18eE|@c#?Nhwz^gCG>c)kyf@y4Wx3X>vW0jGC!6Q!@O1a~?pVC4U2hkv z-K&@Yxl}LmoNFA?uED%5 zb>r1H6{@&Hzx&#*c$M>}L-EL5SR<>T-bDS85^fO38=&VK__B_ZnL^u#@pV*u?KG?S zwxL}wPR+o)pkG056JlMwq9@75Ny$YylQ#8p@o+Q;?DW0V>o#--^G)S@TpW9 zQ^orHJgT>cUr$# zO+ML8_-?$RTByx+aHbv{FX8)1VXYf)ZH73B zUz#d~4d4SqrKxl%UX5+iEQSe=R7yZ-I9oY}B>i|}q0_G8nnU?0KB)pwZ75-LIF$eo zjAaihz+AIds8WNQ*b@e662E%XXoa0ped^deLF!~&om_8)Q+iE?l45)62;C^aAi8O) zRB07vsx%PM%QbRBU~;X;V3OCRqtOkb7(2Gm>S9W7r~Ms>w8AFcQ2_2zYbA($U9)gB z`3i_`rdb8#)aC%hYGuCC?p|~9?s#J(Y)pYEh&$AZjA85P$xH!#r^yjNw%l$vTKgs^ z@wkO+4%+2f%hGnhrCcDZ}i+IS~0IX{(5&tw2otp*|FzWC(U zLakjmHdU_7m8LcBKtLaD{LmU{o(wYO?@?)DT-pXS6yqR=LVNp}1Nv z@M#I38q^cOgZEjKFqaq5g>o3SLF6$-lsY^W&f}iVsY0nldKUe=H{MJwlk8hzb(ZV5 z`{EU0?MU}fXC_|D^$I33Fj=XUDwrkhsdk|`7eY^AbeqvBV5|-GiZ)8&ENSm}i}AFE z*GsU+Xl*w6vYO^niJnKZE0-<1r5ly;PoWjBp(6a3vFN$9cHT%Id+=-YM0}LLQvOnO z|8jhL6@7eiePw8U}3ubf{eOyZoUyYAW zJVI3I$O~J37~T6A&HKt@29`mDE1ORh;vb!KG=AE zPQ#<|MCZ`($TBEDB-Xc8<<)HabUJ!gJ_GEgUz62cng{W!N^8Cj4M5B6XAuIeoUqd} z>|hLxHtIUr5(uu%(kSZmasOs~{5pL+v;`kuppVOuybxVOA2*+hk7@cS@4&}AeQZ7* zAKU5U_FedR34PG(fFgQl5W7w&qC1CJR6U~5dnewYYXpYa4aExd3PBramiPq*6PBf7y+eym zH(pn)SF3!A!!%iQKsW+`@Ny!qC<2;V`+{A~uvy>5eJj{8ozt)U!^hu5H6A|x z<_T?c`Y-6S?h#4%P7s3k15ZCh4WB*ACS@UBC2XhV@k&wy-FW2{hc3OA44A}PCH)%0 zxA)kG=EOP6%ER+^~2ACAeQ<9T&r3 zY^FB5M{({@zb3)CjE!2%-Sniq%w3NfUgnZc>t${RQC{9suTYQG>9;T delta 15399 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 XYO}4fqN=O~&l6*n2U#7qv!VY7^?+69 diff --git a/docs/_build/doctrees/importers/bl.doctree b/docs/_build/doctrees/importers/bl.doctree index e6126f02c9f4567d51d33d4e05109c894e252011..2372ee2532dd3adacb63b86a080279a1a58e1e16 100644 GIT binary patch delta 425 zcmX>#mv#MIR+a|Vscairx;`_8PwxM0z*sc-z-MXp(!9*H{G!~=4?jO*ly>mTFHtBh zPEAn&iYb(2WELxA<`(1^m87C7{{K~wi*f2!LB^kUg5o)u#U;^BIiAJErKv8NMX}TG zurex7?^R|Lm>z1+m^L~7CEMilEcjU0dq1pZ0D zB{`XyVil%;v}crN%Mj~fssXc&rVDT|@-xnxUTDLpIo-gK(T0u1FF!AJ%Jc~)jEW#L z*(QgI%TAt9#=8BRBO^N_&qX-~hO9YEK;oj@S1Ms6N? z28JHK)S{yNqUnKdjKbR&xiKDRc7FuYcOOKE2>}UV5OE8{y$&QwdpPqFb5lzy3sQ@x z)K1aJ0;%oMYGr1c~3DzQKzzh$RSU$K+0yzUfoE8P5xYOvsW18E6I~fHq)$ delta 423 zcmZ2Km-WsIp|m(PMIkM}NTDPnvsfWBw;;c$Bo(ZM4WeZ;<2OMr#!Xv=8GqVIN%;io5gkmXU2JqTs>_0r6mQWB~$#ipL1cH z&B!RYJL?CZ@p^U8tKPNtJ~=rlLD6q+|2WNl ztjGTC_1gQKub%Jy!XNr1uj{#O+zsO-g$t$|BAm-L3ZG$cQi1tnrm(qLH5XKjV_&g%z5 zv;CsXiNU(dgO%ZMi+7U0ng7-f`TX_4FqDs+0Cjx&5Xj;8RDd}-Nm--tBNL!heYI-< zdAp2;&qK`-B20d{pzxrvvofS#RaE1);@KWggHBk(4}$5E3Cn?~{6zd(OJ>oT}B ztFq3&s;autU$xp>-CWh+4>wdtYJ7Zr7%qm8T>iyx!q8pZlN4UN9-Ue?l;=a0i`C_M1UmcEA`Oi0w z%cf;I6xTQm`AM*`T(er*a4|K<+py^}t6Lk&sQGs@jAi4^yyH69 z%0`>j*~=Z&y>&el@Wa*6b5M%8M-PPPBORg?+Kn_p3MJ;LH~1C4&H*`Wlhg+1>)O^gbx{#-_mTh`=sqpTs35B1IT2N#`AQ7{(jdzyNj< z7iEEY6b^cdaKVn7d6yNp;2X&mo0bB|8Gv-{F)e!L64~4VY2AEDt^RTpaX7LLISkDe zdwSfgrk4*?-}GlOR8fa&$vIFBiE~3zRCAnj(CQ^;Mv;m(WBOf^tM6`EuC%brr8d|U zC|#9H7M4?%E37Am(tsFBA`l9cykfaVl&W(jN~Jc?4aWxnF52yj*0yG zfTkJJmbH-1zL#s|i&nunc2+|tT@e<5O;T4{T&~jH;-a(5jI+C2T!kPz*W&nz{ctI0 zSkBcs94|odYV@Xnwn-5|B~F{x^J=5�hq`<3hA~O6MTNAJoN*ojnpidR!mXclNXp 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 ACjbBd delta 6605 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 Mh;jY&!>r6!06Rf8KmY&$ delta 218 zcmcc9&GWaLr=f*$3R5r#3j-qq2uxpS%oMzJ9aG3j#**pGrq;sMGV{{)T((=C zV$xt@w4N?*3;uJF$qmKsACe_o_UdJu{u`|TYhOtL21bpzwJCfnPxLGYHwfh zi)k7w`_!qdAlFT`VOH3h%6yQ8b*mQxL-FK`1gpb_58s3IG6T9gL5YVF5C;@PPCI Dq*D>+ delta 45 zcmccB%Jig_iKT&c>dlQT)2g_7*z!wD3Q9|+_-&q5HJgz!Z?joV84Kf<&1)NUnE$T8y}M?(wa6ea`4gsp~5)}mZJZ26@n1*IiZ{I=(aGRj62~tS28ImTp7!a53XJX3ugEfaL8NUNe{Bcq HGGqb(Jpmz* diff --git a/docs/_build/doctrees/importers/mets-alto.doctree b/docs/_build/doctrees/importers/mets-alto.doctree index 966319031a88f22d589a6ed2d95cd41caded52db..c7e90d1bb2973d23c253786cc326d90191df5c9d 100644 GIT binary patch delta 203 zcmX?poBil*_6_#m8NDYveAi-(-<vp7^xPoRZX{$$=GO zLhMk^lnnORDH&ot?D3ly{YhcwoVxKX0~ky`*eJJEm+=4xlY9L1cN~mSShQ@9;AGUi zxxM=l<7`I8<=fStF!r)=ywzo3$dK)sI{jicW5{-amy8aKjPIrgzGAdxd^^4W72`Ds aPkMUeYeprAQ1tYG$&A+9t==&9G6Dc&sZBxv delta 236 zcmX?loBjB0_6_#m8NDVueAn8X{at{GQFHoR5k|Sqvwz-UV&_iF%qdANnjBamCcp+^ zPsw19osuEe!?t<(pA=@k+9?_tic>egWdMW86B8w;%U@vR*lNYNgM-m+`WFtyC=8w3 z6F3<)Z*ujp<(HNel$K2K+ur$zaW*6O3S9<<9=_C~qWq%iA0IOcZ+CjaSir*Z4k(-< z+cR~#V*+EyWQ7jL>HaSn|1-Xy?*EF>mIWj`nepqi>1wYTZ6Siv)9YU|D#2tTw*P(2 Hc!CiCO(a=! diff --git a/docs/_build/doctrees/importers/olive.doctree b/docs/_build/doctrees/importers/olive.doctree index 902ddc8150fdfe8f16d1a4a31ab8e9570426d8f0..418b1b703a272f68eef879b0471dc464c26f8e13 100644 GIT binary patch delta 118 zcmZo#Blu&rU_%RI3sVd86c%m9qg!=Y-g|6U^<^>Q2QWKqf8)<0#mE>u-9DH_db&~|ixNjrJ_AFBY)|)e!vx0e>DL2UykOF{ bK`crTz4stOwosF%Ur1xJ-7Xly@|+O>uKz4p diff --git a/docs/_build/doctrees/importers/rero.doctree b/docs/_build/doctrees/importers/rero.doctree index 9a81ee3130f017d53918a40df323c1ab4d14e070..3dd9d37339fda234d12e531042be2cf246f935e5 100644 GIT binary patch delta 77 zcmbR6i?!_+Yr_;q9>z~wc^Ur{Z5J+PoXyCXw|#vHV=W8gmhHNgjO~mpTa_6YCL6Gb hPd{J9sLZhi$jgxJ>70Bq$A5aiG^5q_=4!@XMgT#&8n6HW delta 108 zcmZqs#X8{^Yr_;q9>zagc^Ur{arLm}mzETimQ3;6&RNVjn~^(TnSr5)FSV#Bzi4`4 z38V1#LnVv}EF4>b!WptX-IFiobWh(}$@rCJ8&G7j0gL$bqg9N`F!@fPy#IFlYR2P? E0B|HGzyJUM diff --git a/docs/_build/doctrees/importers/swa.doctree b/docs/_build/doctrees/importers/swa.doctree index 7e998510b55bcd4509306ade0d8e262369e82e80..3a8587d906d470bce4f31ef482cde7d82646da2d 100644 GIT binary patch delta 31 ncmccih4suA)`lsJ6^v)LRx<9+-)>jHIGd4?bNj7A#u^R)=;I9A delta 42 ycmX@}h4tDO)`lsJ6^z%mRx<9+=jvh0FD)r3Et%rC-K2nVHX|d~_6voKH5>qyToC~P diff --git a/docs/_build/doctrees/importers/tetml.doctree b/docs/_build/doctrees/importers/tetml.doctree index c65c287fd3838716d35868b32c20724835424510..a94cb8e7873a01514d9f622e74377f097e4ad830 100644 GIT binary patch delta 258 zcmZ4Zk$w6{_J%2pLcFy<7{DMytcOu=a>0M;$@g6qCg1tXxiy*5_~rHmFBxYuGG5+p z^O|uk3!~)pTkjd~vq%{*FigHEDL(zm2S#NMNgyvnwx@gY$B^#nT^|{}5c2OoBIJ94 z@&S_@t~pF!^@&jvq2}EuMr9U|8I$M!nX-N5XU1|S#`Nim-xv*Ps>$wV$Dd){vnv|7nFkLa6QF{9QZ;aO&n;|ANz)cWj Ster0XgHet#X}Z-9#&iJES6iq6 delta 222 zcmbRKk$vGu_J%2pLcHxi7{DMytcOu=a>0M;$@g6qCg1tXxiy*5_$5~lTYhOtL21bp zzwOgrGR|gXyt3WsHRD_sMycr+-ZS23l%Brf1EVdF_wEDZ9SBc)`m>LWN)QF%(-S^1 zX2QguenN;F>}0gvKIb!I6%$j2!DPkjdMwgesRolLCd*I%|D91_dgV8u+T`hTzA?%$ lr5H?C3@Z5l - + - + Overview — Impresso TextImporter documentation - - - - + + @@ -190,12 +188,14 @@

    Image data
    -text_importer.importers.core.compress_issues(key: Tuple[str, int], issues: list[NewspaperIssue], output_dir: str | None = None, failed_log: str | None = None) Tuple[str, str]
    +text_importer.importers.core.compress_issues(key: Tuple[str, int], issues: list[NewspaperIssue], output_dir: str | None = None, failed_log: str | None = None) Tuple[str, str, list[dict[str, int]]]

    Compress issues of the same Journal-year and save them in a json file.

    First check if the file exists, load it and then over-write/add the newly generated issues. The compressed .bz2 output file is a JSON-line file, where each line -corresponds to an individual and issue document in the canonical format.

    +corresponds to an individual and issue document in the canonical format. +Finally, yearly statistics are computed on the issues and included in the +returned values.

    Parameters:

Built with Sphinx using a diff --git a/docs/_build/html/custom_importer.html b/docs/_build/html/custom_importer.html index 91733a4a..9c9ea84b 100644 --- a/docs/_build/html/custom_importer.html +++ b/docs/_build/html/custom_importer.html @@ -1,14 +1,12 @@ - + - + Writing a new importer — Impresso TextImporter documentation - - - - + + @@ -404,7 +402,7 @@

Test<
-

© Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

+

© Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

Built with Sphinx using a diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html index 6749dfb2..c749653a 100644 --- a/docs/_build/html/genindex.html +++ b/docs/_build/html/genindex.html @@ -1,13 +1,11 @@ - + Index — Impresso TextImporter documentation - - - - + + @@ -131,6 +129,8 @@

A

@@ -417,10 +421,10 @@

G

  • get_iiif_image() (text_importer.importers.swa.classes.SWANewspaperPage method)
  • - - + @@ -149,7 +152,7 @@

    Command-line interface

    Functions and CLI script to convert any OCR data into Impresso’s format.

    -
    Usage:

    <importer-name>importer.py –input-dir=<id> (–clear | –incremental) [–output-dir=<od> –image-dirs=<imd> –temp-dir=<td> –chunk-size=<cs> –s3-bucket=<b> –config-file=<cf> –log-file=<f> –verbose –scheduler=<sch> –access-rights=<ar>] +

    Usage:

    <importer-name>importer.py –input-dir=<id> (–clear | –incremental) [–output-dir=<od> –image-dirs=<imd> –temp-dir=<td> –chunk-size=<cs> –s3-bucket=<b> –config-file=<cf> –log-file=<f> –verbose –scheduler=<sch> –access-rights=<ar> –git-repo=<gr> –num-workers=<nw>] <importer-name>importer.py –version

    Options:
    @@ -183,6 +186,12 @@

    Command-line interface--chunk-size=<cs>

    Chunk size in years used to group issues when importing

    +
    --git-repo=<gr>
    +

    Local path to the “impresso-text-acquisition” git directory (including it).

    +
    +
    --num-workers=<nw>
    +

    Number of workers to use for local dask cluster

    +
    --verbose

    Verbose log messages (good for debugging)

    @@ -239,15 +248,49 @@

    Configuration file

    Utilities

    +

    This module contains generic helper functions for the text-importer module.

    +
    +
    +text_importer.utils.add_property(object_dict: dict[str, Any], prop_name: str, prop_function: Callable[[str], str], function_input: str) dict[str, Any]
    +

    Add a property and value to a given object dict computed with a given function.

    +
    +
    Parameters:
    +
      +
    • object_dict (dict[str, Any]) – Object to which the property is added.

    • +
    • prop_name (str) – Name of the property to add.

    • +
    • prop_function (Callable[[str], str]) – Function computing the property value.

    • +
    • function_input (str) – Input to prop_function for this object.

    • +
    +
    +
    Returns:
    +

    Updated object.

    +
    +
    Return type:
    +

    dict[str, Any]

    +
    +
    +
    + +
    +
    +text_importer.utils.empty_folder(dir_path: str) None
    +

    Empty a directoy given its path if it exists.

    +
    +
    Parameters:
    +

    dir_path (str) – Path to the directory to empty.

    +
    +
    +
    +
    -text_importer.utils.get_access_right(journal: str, date: date, access_rights: dict[str, dict[str, str]]) str
    +text_importer.utils.get_access_right(journal: str, _date: date, access_rights: dict[str, dict[str, str]]) str

    Fetch the access rights for a specific journal and publication date.

    Parameters:
    • journal (str) – Journal name.

    • -
    • date (date) – Publication date of the journal

    • +
    • _date (date) – Publication date of the journal

    • access_rights (dict[str, dict[str, str]]) – Access rights for various journals.

    @@ -324,6 +367,25 @@

    Configuration file +
    +text_importer.utils.get_reading_order(items: list[dict[str, Any]]) dict[str, int]
    +

    Generate a reading order for items based on their id and the pages they span.

    +

    This reading order can be used to display the content items properly in a table +of contents without skipping form page to page.

    +
    +
    Parameters:
    +

    items (list[dict[str, Any]]) – List of items to reorder for the ToC.

    +
    +
    Returns:
    +

    A dictionary mapping item IDs to their reading order.

    +
    +
    Return type:
    +

    dict[str, int]

    +
    +
    +

    +
    text_importer.utils.init_logger(_logger: RootLogger, log_level: int, log_file: str) RootLogger
    @@ -362,6 +424,42 @@

    Configuration file +
    +text_importer.utils.write_error(thing_id: str, origin_function: str, error: Exception, failed_log: str) None
    +

    Write the given error of a failed import to the failed_log file.

    +

    Adapted from impresso-text-acquisition/text_importer/importers/core.py to allow +using a issue or page id, and provide the function in which the error took place.

    +
    +
    Parameters:
    +
      +
    • thing_id (str) – Canonical ID of the object/file for which the error occurred.

    • +
    • origin_function (str) – Function in which the exception occured.

    • +
    • error (Exception) – Error that occurred and should be logged.

    • +
    • failed_log (str) – Path to log file for failed imports.

    • +
    +
    +
    +

    + +
    +
    +text_importer.utils.write_jsonlines_file(filepath: str, contents: str | list[str], content_type: str, failed_log: str | None = None) None
    +

    Write the given contents to a JSONL file given its path.

    +

    Filelocks are used here to prevent concurrent writing to the files.

    +
    +
    Parameters:
    +
      +
    • filepath (str) – Path to the JSONL file to write to.

    • +
    • contents (str | list[str]) – Dump contents to write to the file.

    • +
    • content_type (str) – Type of content that is being written to the file.

    • +
    • failed_log (str | None, optional) – Path to a log to keep track of failed +operations. Defaults to None.

    • +
    +
    +
    +
    + @@ -376,7 +474,7 @@

    Configuration file -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with Sphinx using a diff --git a/docs/_build/html/importers/bl.html b/docs/_build/html/importers/bl.html index 71e7e5a6..adaecaa0 100644 --- a/docs/_build/html/importers/bl.html +++ b/docs/_build/html/importers/bl.html @@ -1,14 +1,12 @@ - + - + British Library Mets/Alto importer — Impresso TextImporter documentation - - - - + + @@ -389,8 +387,8 @@

    British Library Mets/Alto importer
    • base_dir (str) – Path to the base directory of newspaper data, this directory should contain zip files.

    • -
    • access_rights (str) – Not used for this imported, but argument is -kept for normality.

    • +
    • access_rights (str) – Not used for this importer, but argument is +kept for uniformity.

    • tmp_dir (str) – Temporary directory to unzip archives.

    @@ -442,7 +440,7 @@

    British Library Mets/Alto importer

    List of BlIssueDir instances to import.

    Return type:
    -

    Optional[list[BlIssueDir]]

    +

    list[BlIssueDir] | None

    @@ -461,7 +459,7 @@

    British Library Mets/Alto importer -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/importers/bnf-en.html b/docs/_build/html/importers/bnf-en.html index 371a7e25..582a542c 100644 --- a/docs/_build/html/importers/bnf-en.html +++ b/docs/_build/html/importers/bnf-en.html @@ -1,14 +1,12 @@ - + - + BNF-EN Mets/Alto importer — Impresso TextImporter documentation - - - - + + @@ -414,7 +412,7 @@

    BNF-EN Mets/Alto importer
    -text_importer.importers.bnf_en.detect.detect_issues(base_dir: str, access_rights: str) List[IssueDirectory]
    +text_importer.importers.bnf_en.detect.detect_issues(base_dir: str, access_rights: str) list[IssueDirectory]

    Detect newspaper issues to import within the filesystem.

    This function expects the directory structure that BNF-EN used to organize the dump of Mets/Alto OCR data.

    @@ -429,7 +427,7 @@

    BNF-EN Mets/Alto importer

    List of BnfEnIssueDir instances to import.

    Return type:
    -

    List[BnfEnIssueDir]

    +

    list[BnfEnIssueDir]

    @@ -458,7 +456,37 @@

    BNF-EN Mets/Alto importerReturn type: -

    Optional[BnfEnIssueDir]

    +

    BnfEnIssueDir | None

    +
    + + + +
    +
    +text_importer.importers.bnf_en.detect.fix_api_year_mismatch(journal: str, year: int, api_issues: list[Tag], last_i: list[Tag] | None) tuple[list[Tag], list[Tag] | None]
    +

    Modify proivded list of issues fetched from the API to fix some issues present.

    +

    Indeed, the API currently wronly stores the issues for december 31st of some years, +with some issues being shifted from one year. +This is not the case for all years, and the correct issue can be present or not. +This function aims to rectify this issue and fetch the correct IIIF ark IDs.

    +
    +
    Parameters:
    +
      +
    • journal (str) – Alias of the journal currently under processing.

    • +
    • year (int) – Year for which the API was queried.

    • +
    • api_issues (list[Tag]) – List of issues as returned from the API.

    • +
    • last_i (Tag) – Last december 31st issue entry, returned for the wrong year.

    • +
    +
    +
    Returns:
    +

    +
    Corrected issue list and next december 31st

    issue(s) if the error was present again, None otherwise.

    +
    +
    +

    +
    +
    Return type:
    +

    tuple[list[Tag], list[Tag] | None]

    @@ -551,7 +579,7 @@

    BNF-EN Mets/Alto importer
    -text_importer.importers.bnf_en.detect.select_issues(base_dir: str, config: dict, access_rights: str) List[IssueDirectory] | None
    +text_importer.importers.bnf_en.detect.select_issues(base_dir: str, config: dict, access_rights: str) list[IssueDirectory] | None

    Detect selectively newspaper issues to import.

    The behavior is very similar to detect_issues() with the only difference that config specifies some rules to filter the data to @@ -569,7 +597,7 @@

    BNF-EN Mets/Alto importer

    BnfEnIssueDir instances to import.

    Return type:
    -

    Optional[List[BnfEnIssueDir]]

    +

    list[BnfEnIssueDir] | None

    @@ -588,7 +616,7 @@

    BNF-EN Mets/Alto importer -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/importers/bnf.html b/docs/_build/html/importers/bnf.html index 434123dd..0e825130 100644 --- a/docs/_build/html/importers/bnf.html +++ b/docs/_build/html/importers/bnf.html @@ -1,14 +1,12 @@ - + - + BNF Mets/Alto importer — Impresso TextImporter documentation - - - - + + @@ -780,7 +778,7 @@

    BNF Mets/Alto importer
    -text_importer.importers.bnf.parsers.parse_printspace(element: Tag, mappings: Dict[str, str]) tuple[list[dict], list[str] | None]
    +text_importer.importers.bnf.parsers.parse_printspace(element: Tag, mappings: dict[str, str]) tuple[list[dict], list[str] | None]

    Parse the <PrintSpace> element of an ALTO XML document for BNF.

    Parameters:
    @@ -816,7 +814,7 @@

    BNF Mets/Alto importer
    -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/importers/fedgaz.html b/docs/_build/html/importers/fedgaz.html index 28e4f2e1..680b1f15 100644 --- a/docs/_build/html/importers/fedgaz.html +++ b/docs/_build/html/importers/fedgaz.html @@ -1,14 +1,12 @@ - + - + FedGaz TETML importer — Impresso TextImporter documentation - - - - + + @@ -195,7 +193,7 @@

    FedGaz TETML importer
    -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/importers/lux.html b/docs/_build/html/importers/lux.html index 881dc177..76b77dec 100644 --- a/docs/_build/html/importers/lux.html +++ b/docs/_build/html/importers/lux.html @@ -1,14 +1,12 @@ - + - + BNL Mets/Alto importer — Impresso TextImporter documentation - - - - + + @@ -595,7 +593,7 @@

    BNL Mets/Alto importer
    -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/importers/mets-alto.html b/docs/_build/html/importers/mets-alto.html index 1c460819..409334f0 100644 --- a/docs/_build/html/importers/mets-alto.html +++ b/docs/_build/html/importers/mets-alto.html @@ -1,14 +1,12 @@ - + - + Generic Mets/Alto importer — Impresso TextImporter documentation - - - - + + @@ -406,7 +404,7 @@

    Generic Mets/Alto importer
    -text_importer.importers.mets_alto.mets.get_dmd_sec(mets_doc: BeautifulSoup, filter: str) Tag
    +text_importer.importers.mets_alto.mets.get_dmd_sec(mets_doc: BeautifulSoup, _filter: str) Tag

    Extract the contents of a specific <dmdsec> from the Mets document.

    The <dmdsec> section contains descriptive metadata. It’s composed of several different subsections each identified with string IDs.

    @@ -414,7 +412,7 @@

    Generic Mets/Alto importerParameters:
    • mets_doc (BeautifulSoup) – BeautifulSoup object of Mets XML document.

    • -
    • filter (str) – ID of the subsection of interest to filter the search.

    • +
    • _filter (str) – ID of the subsection of interest to filter the search.

    Returns:
    @@ -586,7 +584,7 @@

    Generic Mets/Alto importer -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/importers/olive.html b/docs/_build/html/importers/olive.html index 55f9e212..77277c1b 100644 --- a/docs/_build/html/importers/olive.html +++ b/docs/_build/html/importers/olive.html @@ -1,14 +1,12 @@ - + - + Olive XML importer — Impresso TextImporter documentation - - - - + + @@ -928,7 +926,7 @@

    Olive XML importer -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/importers/rero.html b/docs/_build/html/importers/rero.html index a280109e..1ce2d689 100644 --- a/docs/_build/html/importers/rero.html +++ b/docs/_build/html/importers/rero.html @@ -1,14 +1,12 @@ - + - + RERO Mets/Alto importer — Impresso TextImporter documentation - - - - + + @@ -495,7 +493,7 @@

    RERO Mets/Alto importer
    -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/importers/swa.html b/docs/_build/html/importers/swa.html index 1d4580eb..b6b093e6 100644 --- a/docs/_build/html/importers/swa.html +++ b/docs/_build/html/importers/swa.html @@ -1,14 +1,12 @@ - + - + SWA Alto importer — Impresso TextImporter documentation - - - - + + @@ -550,7 +548,7 @@

    SWA Alto importer -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/importers/tetml.html b/docs/_build/html/importers/tetml.html index e832b82d..ba3702bb 100644 --- a/docs/_build/html/importers/tetml.html +++ b/docs/_build/html/importers/tetml.html @@ -1,14 +1,12 @@ - + - + Generic TETML importer — Impresso TextImporter documentation - - - - + + @@ -493,7 +491,7 @@

    Generic TETML importer
    -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html index e9a9cfb1..a9398a35 100644 --- a/docs/_build/html/index.html +++ b/docs/_build/html/index.html @@ -1,14 +1,12 @@ - + - + Welcome to Impresso TextImporter’s documentation! — Impresso TextImporter documentation - - - - + + @@ -126,7 +124,7 @@

    Indices and tables -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/install.html b/docs/_build/html/install.html index 89c592bc..3bf05968 100644 --- a/docs/_build/html/install.html +++ b/docs/_build/html/install.html @@ -1,14 +1,12 @@ - + - + Installation — Impresso TextImporter documentation - - - - + + @@ -108,7 +106,7 @@

    Installation -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with
    Sphinx using a diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv index 2bb72e161445b6ba0f733410f659656b6369dc00..a9df14b133d5ef758e71c7586a3672ce152fdbdf 100644 GIT binary patch delta 3848 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 Cd`30^ diff --git a/docs/_build/html/py-modindex.html b/docs/_build/html/py-modindex.html index d7abe6f1..a0014794 100644 --- a/docs/_build/html/py-modindex.html +++ b/docs/_build/html/py-modindex.html @@ -1,13 +1,11 @@ - + Python Module Index — Impresso TextImporter documentation - - - - + + @@ -252,7 +250,7 @@

    Python Module Index


    -

    © Copyright 2023, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    +

    © Copyright 2024, Impresso - Media Monitoring of the Past - EPFL-DHLAB, UZH-ICL, UNILU-C2DH, UNIL-History..

    Built with Sphinx using a diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html index 5cbebbd6..ed28079d 100644 --- a/docs/_build/html/search.html +++ b/docs/_build/html/search.html @@ -1,13 +1,11 @@ - + Search — Impresso TextImporter documentation - - - - + +