From 6fbca62e5b0a12853d321343008ddbe4e845eb73 Mon Sep 17 00:00:00 2001 From: dengwenjie <775747758@qq.com> Date: Fri, 18 Jul 2025 12:28:06 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81OCR=EF=BC=9A=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E8=AF=86=E5=88=AB=E6=A8=A1=E5=9E=8B=202?= =?UTF-8?q?=E3=80=81OCR=EF=BC=9A=E6=96=B0=E5=A2=9E9=E4=B8=AA=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E6=A8=A1=E5=9E=8B=203=E3=80=81OCR=EF=BC=9A=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=89=B9=E9=87=8F=E6=A3=80=E6=B5=8B=E8=AF=86=E5=88=AB?= =?UTF-8?q?=204=E3=80=81OCR=EF=BC=9A=E6=96=B0=E5=A2=9E=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=8C=E4=BD=BF=E7=94=A8=E6=9B=B4=E5=8A=A0?= =?UTF-8?q?=E7=81=B5=E6=B4=BB=205=E3=80=81=E4=BA=BA=E8=84=B8=E8=AF=86?= =?UTF-8?q?=E5=88=AB=EF=BC=9A=E6=94=AF=E6=8C=81ID=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=8F=8A=E5=88=86=E9=A1=B5=E8=8E=B7=E5=8F=96=E4=BA=BA=E8=84=B8?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=206=E3=80=81=E6=B4=BB=E4=BD=93=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=EF=BC=9A=E8=A7=86=E9=A2=91=E6=A3=80=E6=B5=8B=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=AE=BE=E7=BD=AE=E6=9C=80=E5=A4=A7=E5=B8=A7=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 65 ++- examples/face-example/pom.xml | 2 +- .../examples/face/facerec/FaceRecDemo.java | 48 ++ .../face/liveness/LivenessDetDemo.java | 4 + examples/objectdetection-example/pom.xml | 2 +- examples/ocr-examples/pom.xml | 4 +- .../examples/ocr/OcrDetectionDemo.java | 76 --- .../examples/ocr/OcrRecognizeDemo.java | 145 ------ .../examples/ocr/common/OcrDetectionDemo.java | 113 +++++ .../ocr/{ => common}/OcrDirectionDetDemo.java | 35 +- .../examples/ocr/common/OcrRecognizeDemo.java | 206 ++++++++ .../examples/ocr/table/TableRecDemo.java | 144 ++++++ .../ocr-examples/src/main/resources/ocr_2.jpg | Bin 48680 -> 247054 bytes examples/translation-example/pom.xml | 2 +- pom.xml | 2 +- smartjavaai-all/pom.xml | 4 +- smartjavaai-bom/pom.xml | 4 +- smartjavaai-common/pom.xml | 2 +- .../common/config/ModelConfig.java | 56 +++ .../common/entity/DetectionRectangle.java | 1 + .../common/entity/ocr/TableStructure.java | 8 + .../smartjavaai/common/utils/ImageUtils.java | 98 ++++ .../smartjavaai/common/utils/VideoUtils.java | 77 +++ smartjavaai-face/pom.xml | 4 +- .../face/config/FaceRecConfig.java | 20 - .../face/config/LivenessConfig.java | 5 + .../face/constant/LivenessConstant.java | 5 + .../java/cn/smartjavaai/face/dao/FaceDao.java | 6 +- .../cn/smartjavaai/face/entity/FaceData.java | 19 - .../face/factory/ExpressionModelFactory.java | 3 + .../model/facerec/CommonFaceRecModel.java | 20 +- .../face/model/facerec/FaceRecModel.java | 19 + .../model/facerec/SeetaFace6FaceRecModel.java | 30 +- .../FaceRecCriteriaFactory.java | 15 +- .../model/liveness/CommonLivenessModel.java | 3 + .../liveness/Seetaface6LivenessModel.java | 3 + .../face/vector/core/MilvusClient.java | 144 ++++-- .../face/vector/core/SQLiteClient.java | 49 +- .../face/vector/core/VectorDBClient.java | 16 +- smartjavaai-objectdetection/pom.xml | 4 +- smartjavaai-ocr/pom.xml | 26 +- .../ocr/config/DirectionModelConfig.java | 19 +- .../ocr/config/OcrDetModelConfig.java | 8 +- .../ocr/config/OcrRecModelConfig.java | 28 +- .../smartjavaai/ocr/config/OcrRecOptions.java | 30 ++ .../ocr/config/TableStructureConfig.java | 27 ++ .../cn/smartjavaai/ocr/entity/OcrBox.java | 20 + .../cn/smartjavaai/ocr/entity/OcrInfo.java | 10 + .../cn/smartjavaai/ocr/entity/OcrItem.java | 1 - .../ocr/entity/TableStructureResult.java | 33 ++ .../ocr/enums/CommonDetModelEnum.java | 9 +- .../ocr/enums/CommonRecModelEnum.java | 9 +- .../ocr/enums/DirectionModelEnum.java | 7 +- .../ocr/enums/TableStructureModelEnum.java | 30 ++ .../ocr/factory/OcrModelFactory.java | 68 +-- .../ocr/factory/TableRecModelFactory.java | 119 +++++ .../common/detect/OcrCommonDetModel.java | 21 +- ...tModel.java => OcrCommonDetModelImpl.java} | 110 +++-- .../OcrCommonDetCriterialFactory.java | 53 ++ ...ranslator.java => PPOCRDetTranslator.java} | 23 +- .../common/direction/OcrDirectionModel.java | 15 +- ...2Model.java => PPOCRMobileV2ClsModel.java} | 332 +++++++------ .../criteria/DirectionCriteriaFactory.java | 61 +++ .../translator/PpWordRotateTranslator.java | 29 +- .../common/recognize/OcrCommonRecModel.java | 47 +- ...cModel.java => OcrCommonRecModelImpl.java} | 386 +++++++++------ .../OcrCommonRecCriterialFactory.java | 51 ++ ...ranslator.java => PPOCRRecTranslator.java} | 16 +- .../table/CommonTableStructureModel.java | 160 ++++++ .../ocr/model/table/TableRecognizer.java | 454 ++++++++++++++++++ .../ocr/model/table/TableStructureModel.java | 63 +++ .../criteria/StructureCriteriaFactory.java | 60 +++ .../translator/TableStructTranslator.java | 198 ++++++++ .../ocr/utils/ConvertHtml2Excel.java | 233 +++++++++ .../ocr/utils/CrossRangeCellMeta.java | 42 ++ .../cn/smartjavaai/ocr/utils/OcrUtils.java | 123 +++-- smartjavaai-translate/pom.xml | 2 +- 77 files changed, 3544 insertions(+), 842 deletions(-) delete mode 100644 examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrDetectionDemo.java delete mode 100644 examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrRecognizeDemo.java create mode 100644 examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java rename examples/ocr-examples/src/main/java/smartai/examples/ocr/{ => common}/OcrDirectionDetDemo.java (66%) create mode 100644 examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java create mode 100644 examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java create mode 100644 smartjavaai-common/src/main/java/cn/smartjavaai/common/config/ModelConfig.java create mode 100644 smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/ocr/TableStructure.java create mode 100644 smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/VideoUtils.java delete mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceData.java rename smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/{criterial => criteria}/FaceRecCriteriaFactory.java (86%) create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrRecOptions.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/TableStructureConfig.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/TableStructureResult.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/TableStructureModelEnum.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/factory/TableRecModelFactory.java rename smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/{PpOCRV5DetModel.java => OcrCommonDetModelImpl.java} (81%) create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/criteria/OcrCommonDetCriterialFactory.java rename smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/translator/{PPOCRV5DetTranslator.java => PPOCRDetTranslator.java} (95%) rename smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/{PPOCRMobileV2Model.java => PPOCRMobileV2ClsModel.java} (51%) create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/criteria/DirectionCriteriaFactory.java rename smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/{PpOCRV5RecModel.java => OcrCommonRecModelImpl.java} (50%) create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/criteria/OcrCommonRecCriterialFactory.java rename smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/translator/{PPOCRV5RecTranslator.java => PPOCRRecTranslator.java} (87%) create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/CommonTableStructureModel.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/TableRecognizer.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/TableStructureModel.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/criteria/StructureCriteriaFactory.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/translator/TableStructTranslator.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/ConvertHtml2Excel.java create mode 100644 smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/CrossRangeCellMeta.java diff --git a/README.md b/README.md index bd0d242..cf5949b 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,19 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 + + +
+

OCR文字识别

+ - 表格识别
+
+ + +
+ +
+ +
@@ -222,23 +235,25 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 - 支持多种主流模型:兼容 YOLOv3、YOLOv5、YOLOv8、YOLOv11、YOLOv12、SSD 等目标检测算法 - 支持自定义模型加载:可无缝加载并部署用户自行训练的目标检测模型 - **OCR文字识别** - - 支持PaddleOCR 3.0模型:集成最新PP-OCRv5模型 + - 支持PaddleOCR 3.0模型:集成最新PP-OCRv5、PP-OCRv4、表格结构识别模型(SLANet_plus)、文本行方向分类模型 - 支持任意角度识别,方向校准 - 支持通用文字识别,通用手写字识别 + - 支持表格识别 - **机器翻译** - 集成NLLB-200模型:支持200+语言互相翻译 ## 🌟 AI集成方式对比 -| 方案 | 技术特点 | 优点 | 缺点 | -|---------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------| -| **OpenCV** | 传统图像处理方案 | ✅ 提供java接口
✅ 轻量级部署
✅ 社区资源丰富 | ❌ 基于传统算法精度低(60%-75%)
❌ 需本地安装环境 | -| ‌**虹软SDK**‌ | 商业级闭源解决方案 | ✅ 开箱即用
✅ 提供完整文档和SDK
✅ 支持离线活体检测 | ❌ 免费版需年度授权更新
❌ 商业授权费用高
❌ 代码不可控 | +| 方案 | 技术特点 | 优点 | 缺点 | +|---------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------|---------------------------------------------------------------------| +| **OpenCV** | 传统图像处理方案 | ✅ 提供java接口
✅ 轻量级部署
✅ 社区资源丰富 | ❌ 基于传统算法精度低(60%-75%)
❌ 需本地安装环境 | +| ‌**商业闭源SDK(如虹软等)**‌ | 商业级闭源解决方案 | ✅ 开箱即用
✅ 提供完整文档和SDK
✅ 支持离线活体检测 | ❌ 免费版需年度授权更新
❌ 商业授权费用高
❌ 代码不可控 | | ‌**云API(阿里云)**‌ | SaaS化云端服务 | ✅ 零部署成本
✅ 支持高并发
✅ 自带模型迭代 | ❌ 网络延迟风险(200-800ms)
❌ 按调用量计费
❌ 有数据安全风险 | | ‌**Python混合调用**‌ | 跨语言调用方案 | ✅ 可集成PyTorch/TF等框架
✅ 支持自定义算法
✅ 识别精度高 | ❌ 需维护双语言环境
❌ 进程通信性能损耗(30%+)
❌ 异常处理复杂度翻倍 | -| ‌**DJL框架**‌ | 深度学习框架 | ✅ 纯Java实现
✅ 支持主流深度学习框架
✅ 可加载预训练模型(99%+) | ❌ 需掌握DL知识
❌ 需处理模型加载、预处理、后处理等复杂技术细节 | -| ‌**SmartJavaAI**‌ | java深度学习工具包 | ✅ 支持主流深度学习框架
✅ 提供丰富、开箱即用API
✅ 上手简单,单一Jar包集成 | ❌要求JDK版本11及以上 | +| ‌**JNI/JNA**‌ | 跨语言底层调用方案 | ✅ 直接调用 C/C++ 高性能算法库✅ 支持调用各种原生成熟库✅ 可封装成通用工具Jar |❌ 开发成本高,JNI更复杂❌ 跨平台兼容性差 | +| ‌**DJL框架**‌ | 深度学习框架 | ✅ 纯Java实现
✅ 支持主流深度学习框架
✅ 可加载预训练模型(99%+) | ❌ 需掌握DL知识
❌ 需处理模型加载、预处理、后处理等复杂技术细节 | +| ‌**SmartJavaAI**‌ | java深度学习工具包 | ✅ 支持主流深度学习框架
✅ 提供丰富、开箱即用API
✅ 上手简单,单一Jar包集成 | ❌要求JDK版本11及以上 | @@ -273,7 +288,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 cn.smartjavaai smartjavaai-all - 1.0.19 + 1.0.20 ``` ### 3、完整示例代码 @@ -427,19 +442,37 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 | 模型名称 | 模型简介 | 模型开源网站 | | ------------| ------------------- |--------------------------------------------------------------------------------------------------------------| -| PP-OCRv5_server_det | 飞桨PaddleOCR 3.0 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | +| PP-OCRv5_server_det | 服务端文本检测模型,精度更高,适合在性能较好的服务器上部署 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | +| PP-OCRv5_mobile_det | 轻量文本检测模型,效率更高,适合在端侧设备部署 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | +| PP-OCRv4_server_det | 服务端文本检测模型,精度更高,适合在性能较好的服务器上部署 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | +| PP-OCRv4_mobile_det | 轻量文本检测模型,效率更高,适合在端侧设备部署 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | **文本识别模型** -| 模型名称 | 模型简介 | 模型开源网站 | -| ------------| ------------------- |--------------------------------------------------------------------------------------------------------------| -| PP-OCRv5_server_rec | 飞桨PaddleOCR 3.0 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | +| 模型名称 | 模型简介 | 模型开源网站 | +| ------------|----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| +| PP-OCRv5_server_rec | (服务端)致力于以单一模型高效、精准地支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字等复杂文本场景的识别。在保持识别效果的同时,兼顾推理速度和模型鲁棒性,为各种场景下的文档理解提供高效、精准的技术支撑。 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | +| PP-OCRv5_mobile_rec | (轻量)致力于以单一模型高效、精准地支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字等复杂文本场景的识别。在保持识别效果的同时,兼顾推理速度和模型鲁棒性,为各种场景下的文档理解提供高效、精准的技术支撑。 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | +| PP-OCRv4_server_rec | (服务端)推理精度高,可以部署在多种不同的服务器上 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | +| PP-OCRv4_mobile_rec | (轻量) 效率更高,适合在端侧设备部署 | [Github](https://github.com/PaddlePaddle/PaddleOCR/blob/main/docs/version3.x/algorithm/PP-OCRv5/PP-OCRv5.md) | + **文本方向分类模型(cls)** -| 模型名称 | 模型简介 | 模型开源网站 | -| ------------| ------------------- |------------| -| ch_ppocr_mobile_v2.0_cls | 原始分类器模型,对检测到的文本行文字角度分类 | [Github](https://paddlepaddle.github.io/PaddleOCR/latest/en/version2.x/ppocr/model_list.html?h=models+list#13-multilingual-detection-model) | +| 模型名称 | 模型简介 | 模型开源网站 | +| ------------|------------------------------|------------| +| ch_ppocr_mobile_v2.0_cls | 原始分类器模型,对检测到的文本行文字角度分类 | [Github](https://paddlepaddle.github.io/PaddleOCR/latest/en/version2.x/ppocr/model_list.html?h=models+list#13-multilingual-detection-model) | +| PP_LCNET_X0_25 | (轻量)基于PP-LCNet_x0_25的文本行分类模型 | [Github](https://paddlepaddle.github.io/PaddleOCR/v3.1.0/version3.x/module_usage/textline_orientation_classification.html) | +| PP_LCNET_X1_0 | 基于PP-LCNet_x1_0的文本行分类模型 | [Github](https://paddlepaddle.github.io/PaddleOCR/v3.1.0/version3.x/module_usage/textline_orientation_classification.html) | + + +**表格结构识别(Table Structure Recognition)** + +| 模型名称 | 模型简介 | 模型开源网站 | +| ------------|------------------------------|------------| +| SLANet | 该模型通过轻量级骨干 PP-LCNet、CSP-PAN 融合与 SLA Head 解码,有效提升表格结构识别的精度与速度。 | [Github](https://paddlepaddle.github.io/PaddleOCR/v3.1.0/version3.x/module_usage/table_structure_recognition.html#_3) | +| SLANet_plus | (增强版)该模型通过轻量级骨干 PP-LCNet、CSP-PAN 融合与 SLA Head 解码,有效提升表格结构识别的精度与速度。 | [Github](https://paddlepaddle.github.io/PaddleOCR/v3.1.0/version3.x/module_usage/table_structure_recognition.html#_3) | + --- @@ -474,7 +507,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 ## 近期更新日志 -## [v1.0.19] - 2025-07-06 +## [v1.0.20] - 2025-07-06 - 人脸模块:新增小视科技(MiniVision)活体检测模型 - 人脸模块:新增阿里通义工作室活体检测模型 - 人脸模块:新增 2 个表情识别模型 diff --git a/examples/face-example/pom.xml b/examples/face-example/pom.xml index 3c21eef..ff2d93e 100644 --- a/examples/face-example/pom.xml +++ b/examples/face-example/pom.xml @@ -12,7 +12,7 @@ 11 11 UTF-8 - 1.0.19 + 1.0.20 smartai.examples.face.facedet.FaceDetDemo diff --git a/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java b/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java index b6670d4..031c1e5 100644 --- a/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java +++ b/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java @@ -20,6 +20,7 @@ import cn.smartjavaai.face.model.facerec.FaceRecModel; import cn.smartjavaai.face.utils.SimilarityUtil; import cn.smartjavaai.face.vector.config.MilvusConfig; import cn.smartjavaai.face.vector.config.SQLiteConfig; +import cn.smartjavaai.face.vector.entity.FaceVector; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; @@ -360,6 +361,53 @@ public class FaceRecDemo { } + /** + * 获取人脸信息 + */ + @Test + public void getFaceInfo(){ + //使用ID获取人脸信息 + try (FaceRecModel faceRecModel = getFaceRecModelWithSQLiteConfig()){ + //等待加载人脸库结束 + while (!faceRecModel.isLoadFaceCompleted()){ + Thread.sleep(100); + } + //ID需改为你需要查询的ID + R faceInfoResult = faceRecModel.getFaceInfoById("9c4c316d53a74b1184195c1714c250c4"); + if(faceInfoResult.isSuccess()){ + log.info("人脸信息:{}", JSONObject.toJSONString(faceInfoResult.getData())); + }else{ + log.info("获取人脸信息失败:{}", faceInfoResult.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 获取人脸信息 + */ + @Test + public void listFaces(){ + //使用ID获取人脸信息 + try (FaceRecModel faceRecModel = getFaceRecModelWithDbConfig()){ + //等待加载人脸库结束 + while (!faceRecModel.isLoadFaceCompleted()){ + Thread.sleep(100); + } + //ID需改为你需要查询的ID + R> faceInfoResult = faceRecModel.listFaces(1, 10); + if(faceInfoResult.isSuccess()){ + log.info("人脸信息:{}", JSONObject.toJSONString(faceInfoResult.getData())); + }else{ + log.info("获取人脸信息失败:{}", faceInfoResult.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } diff --git a/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java b/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java index 1c807b4..bf1f966 100644 --- a/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java +++ b/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java @@ -79,6 +79,8 @@ public class LivenessDetDemo { 这个数量相当于多帧识别结果融合的融合的帧数。当输入的帧数超过设定帧数的时候,会采用滑动窗口的方式,返回融合的最近输入的帧融合的识别结果。 一般来说,在10以内,帧数越多,结果越稳定,相对性能越好,但是得到结果的延时越高。*/ config.setFrameCount(LivenessConstant.DEFAULT_FRAME_COUNT); + //视频最大检测帧数 + config.setMaxVideoDetectFrames(LivenessConstant.DEFAULT_MAX_VIDEO_DETECT_FRAMES); //指定人脸检测模型 config.setDetectModel(getFaceDetModel()); return LivenessModelFactory.getInstance().getModel(config); @@ -103,6 +105,8 @@ public class LivenessDetDemo { 这个数量相当于多帧识别结果融合的融合的帧数。当输入的帧数超过设定帧数的时候,会采用滑动窗口的方式,返回融合的最近输入的帧融合的识别结果。 一般来说,在10以内,帧数越多,结果越稳定,相对性能越好,但是得到结果的延时越高。*/ config.setFrameCount(LivenessConstant.DEFAULT_FRAME_COUNT); + //视频最大检测帧数 + config.setMaxVideoDetectFrames(LivenessConstant.DEFAULT_MAX_VIDEO_DETECT_FRAMES); //指定人脸检测模型 config.setDetectModel(getFaceDetModel()); return LivenessModelFactory.getInstance().getModel(config); diff --git a/examples/objectdetection-example/pom.xml b/examples/objectdetection-example/pom.xml index 7b5bdf9..7d3cdce 100644 --- a/examples/objectdetection-example/pom.xml +++ b/examples/objectdetection-example/pom.xml @@ -12,7 +12,7 @@ 11 11 UTF-8 - 1.0.19 + 1.0.20 smartai.examples.objectdetection.ObjectDetection diff --git a/examples/ocr-examples/pom.xml b/examples/ocr-examples/pom.xml index 5e8700b..3506826 100644 --- a/examples/ocr-examples/pom.xml +++ b/examples/ocr-examples/pom.xml @@ -12,9 +12,9 @@ 11 11 UTF-8 - 1.0.19 + 1.0.20 - smartai.examples.ocr.OcrRecognizeDemo + smartai.examples.ocr.common.OcrRecognizeDemo 1.5.10 diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrDetectionDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrDetectionDemo.java deleted file mode 100644 index ead2d71..0000000 --- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrDetectionDemo.java +++ /dev/null @@ -1,76 +0,0 @@ -package smartai.examples.ocr; - -import cn.smartjavaai.common.entity.DetectionResponse; -import cn.smartjavaai.common.enums.DeviceEnum; -import cn.smartjavaai.ocr.config.OcrDetModelConfig; -import cn.smartjavaai.ocr.entity.OcrBox; -import cn.smartjavaai.ocr.enums.CommonDetModelEnum; -import cn.smartjavaai.ocr.factory.OcrModelFactory; -import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; -import com.alibaba.fastjson.JSONObject; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.File; -import java.nio.file.Paths; -import java.util.List; - -/** - * OCR 文本检测 示例 - * 模型下载地址:https://pan.baidu.com/s/15Noz2xHQzqMQSl1B19BobQ?pwd=1234 提取码: 1234 - * @author dwj - */ -@Slf4j -public class OcrDetectionDemo { - - - //设备类型 - public static DeviceEnum device = DeviceEnum.CPU; - - - /** - * 文本检测 - * 检测图像中的文本区域,仅返回文本框位置,不识别文字内容 - * 模型需要放在单独文件夹 - */ - @Test - public void detect(){ - OcrDetModelConfig config = new OcrDetModelConfig(); - //指定检测模型 - config.setModelEnum(CommonDetModelEnum.PADDLEOCR_V5_DET_MODEL); - //指定模型位置,需要更改为自己的模型路径(下载地址请查看文档) - config.setDetModelPath("/Users/xxx/Documents/develop/ocr模型/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx"); - config.setDevice(device); - try (OcrCommonDetModel model = OcrModelFactory.getInstance().getDetModel(config)){ - List boxes = model.detect("src/main/resources/ocr_1.jpg"); - log.info("OCR检测结果:{}", JSONObject.toJSONString(boxes)); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * 文本检测并绘制结果 - * 检测图像中的文本区域,仅检测文本框位置,不识别文字内容 - * 模型需要放在单独文件夹 - */ - @Test - public void detectAndDraw(){ - OcrDetModelConfig config = new OcrDetModelConfig(); - //指定检测模型 - config.setModelEnum(CommonDetModelEnum.PADDLEOCR_V5_DET_MODEL); - //指定模型位置,需要更改为自己的模型路径(下载地址请查看文档) - config.setDetModelPath("/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx"); - try (OcrCommonDetModel model = OcrModelFactory.getInstance().getDetModel(config)){ - model.detectAndDraw("src/main/resources/ocr_1.jpg", "output/ocr_1_detected.jpg"); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - - -} diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrRecognizeDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrRecognizeDemo.java deleted file mode 100644 index 57b3310..0000000 --- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrRecognizeDemo.java +++ /dev/null @@ -1,145 +0,0 @@ -package smartai.examples.ocr; - -import cn.smartjavaai.common.enums.DeviceEnum; -import cn.smartjavaai.ocr.config.OcrDetModelConfig; -import cn.smartjavaai.ocr.config.OcrRecModelConfig; -import cn.smartjavaai.ocr.entity.OcrBox; -import cn.smartjavaai.ocr.entity.OcrInfo; -import cn.smartjavaai.ocr.enums.CommonDetModelEnum; -import cn.smartjavaai.ocr.enums.CommonRecModelEnum; -import cn.smartjavaai.ocr.enums.DirectionModelEnum; -import cn.smartjavaai.ocr.factory.OcrModelFactory; -import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; -import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel; -import com.alibaba.fastjson.JSONObject; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; - -import java.io.File; -import java.util.List; - -/** - * OCR 文本识别 示例 - * 模型下载地址:https://pan.baidu.com/s/1MLfd73Vjdpnuls9-oqc9uw?pwd=1234 提取码: 1234 - * @author dwj - * @date 2025/5/25 - */ -@Slf4j -public class OcrRecognizeDemo { - - //设备类型 - public static DeviceEnum device = DeviceEnum.CPU; - - /** - * 获取通用识别模型(不带方向矫正) - * @return - */ - public OcrCommonRecModel getRecModel(){ - OcrRecModelConfig recModelConfig = new OcrRecModelConfig(); - //指定检测模型 - recModelConfig.setDetModelEnum(CommonDetModelEnum.PADDLEOCR_V5_DET_MODEL); - //指定检测模型位置,需要更改为自己的模型路径(下载地址请查看文档) - recModelConfig.setDetModelPath("/Users/xxx/Documents/develop/ocr模型/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx"); - //指定识别模型 - recModelConfig.setRecModelEnum(CommonRecModelEnum.PADDLEOCR_V5_REC_MODEL); - //指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档) - recModelConfig.setRecModelPath("/Users/xxx/Documents/develop/ocr模型/PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx"); - recModelConfig.setDevice(device); - return OcrModelFactory.getInstance().getRecModel(recModelConfig); - } - - /** - * 获取通用识别模型(带方向矫正) - * @return - */ - public OcrCommonRecModel getRecModelWithDirection() { - OcrRecModelConfig recModelConfig = new OcrRecModelConfig(); - //指定检测模型 - recModelConfig.setDetModelEnum(CommonDetModelEnum.PADDLEOCR_V5_DET_MODEL); - //指定检测模型位置,需要更改为自己的模型路径(下载地址请查看文档) - recModelConfig.setDetModelPath("/Users/xxx/Documents/develop/ocr模型/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx"); - //指定识别模型 - recModelConfig.setRecModelEnum(CommonRecModelEnum.PADDLEOCR_V5_REC_MODEL); - //指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档) - recModelConfig.setRecModelPath("/Users/xxx/Documents/develop/ocr模型/PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx"); - //指定方向检测模型 - recModelConfig.setDirectionModelEnum(DirectionModelEnum.CH_PPOCR_MOBILE_V2_CLS); - //指定方向模型位置,需要更改为自己的模型路径(下载地址请查看文档) - recModelConfig.setDirectionModelPath("/Users/xxx/Documents/develop/ocr模型/ch_ppocr_mobile_v2.0_cls.onnx"); - recModelConfig.setDevice(device); - return OcrModelFactory.getInstance().getRecModel(recModelConfig); - } - - - /** - * 文本识别 - * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字 - * 流程:文本检测 -> 文本识别 - * 模型需要放在单独文件夹 - */ - @Test - public void recognize(){ - try (OcrCommonRecModel recModel = getRecModel()){ - OcrInfo ocrInfo = recModel.recognize("src/main/resources/ocr_2.jpg"); - log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo)); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - /** - * 文本识别(手写字) - * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字 - * 流程:文本检测 -> 文本识别 - * 模型需要放在单独文件夹 - */ - @Test - public void recognizeHandWriting(){ - try (OcrCommonRecModel recModel = getRecModel()){ - OcrInfo ocrInfo = recModel.recognize("src/main/resources/handwriting_1.jpg"); - log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo)); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * 文本识别(带方向矫正) - * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字 - * 本方法支持多角度文字识别 - * 流程:文本检测 -> 方向检测 -> 方向矫正 -> 文本识别 - * 模型需要放在单独文件夹 - */ - @Test - public void recognize2(){ - try (OcrCommonRecModel recModel = getRecModelWithDirection()){ - OcrInfo ocrInfo = recModel.recognize("src/main/resources/ocr_4.jpg"); - log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo)); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - - /** - * 文本识别并绘制结果 - * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字 - * 流程:文本检测 -> 文本识别 - * 模型需要放在单独文件夹 - */ - @Test - public void recognizeAndDraw(){ - try (OcrCommonRecModel recModel = getRecModelWithDirection()){ - int fontSize = 25; - recModel.recognizeAndDraw("src/main/resources/ocr_4.jpg", "output/ocr_4_recognized.jpg", fontSize); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - - -} diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java new file mode 100644 index 0000000..e77a31e --- /dev/null +++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java @@ -0,0 +1,113 @@ +package smartai.examples.ocr.common; + +import ai.djl.modality.cv.Image; +import cn.smartjavaai.common.entity.DetectionResponse; +import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.common.utils.ImageUtils; +import cn.smartjavaai.ocr.config.OcrDetModelConfig; +import cn.smartjavaai.ocr.config.OcrRecOptions; +import cn.smartjavaai.ocr.entity.OcrBox; +import cn.smartjavaai.ocr.entity.OcrInfo; +import cn.smartjavaai.ocr.enums.CommonDetModelEnum; +import cn.smartjavaai.ocr.factory.OcrModelFactory; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; +import cn.smartjavaai.ocr.opencv.OcrOpenCVUtils; +import cn.smartjavaai.ocr.utils.OcrUtils; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.opencv.core.Mat; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.nio.file.Paths; +import java.util.List; + +/** + * OCR 文本检测 示例 + * 模型下载地址:https://pan.baidu.com/s/15Noz2xHQzqMQSl1B19BobQ?pwd=1234 提取码: 1234 + * @author dwj + */ +@Slf4j +public class OcrDetectionDemo { + + + //设备类型 + public static DeviceEnum device = DeviceEnum.CPU; + + /** + * 获取文本检测模型 + * @return + */ + public OcrCommonDetModel getDetectionModel() { + OcrDetModelConfig config = new OcrDetModelConfig(); + //指定检测模型 + config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL); + //指定模型位置,需要更改为自己的模型路径(下载地址请查看文档) + config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx"); + config.setDevice(device); + return OcrModelFactory.getInstance().getDetModel(config); + } + + + /** + * 文本检测 + * 检测图像中的文本区域,仅返回文本框位置,不识别文字内容 + * 注意事项: + * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。 + * 2、模型文件需要放在单独文件夹 + */ + @Test + public void detect(){ + try (OcrCommonDetModel model = getDetectionModel()){ + List boxes = model.detect("src/main/resources/ocr_1.jpg"); + log.info("OCR检测结果:{}", JSONObject.toJSONString(boxes)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 文本检测并绘制结果 + * 检测图像中的文本区域,仅检测文本框位置,不识别文字内容 + * 注意事项: + * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。 + * 2、模型文件需要放在单独文件夹 + */ + @Test + public void detectAndDraw(){ + try (OcrCommonDetModel model = getDetectionModel()){ + model.detectAndDraw("src/main/resources/ocr_1.jpg", "output/ocr_1_detected.jpg"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * 批量文本检测:批量检测要求图片宽高一致 + * 检测图像中的文本区域,仅返回文本框位置,不识别文字内容 + * 注意事项: + * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。 + * 2、模型文件需要放在单独文件夹 + */ + @Test + public void batchDetect(){ + try (OcrCommonDetModel model = getDetectionModel()){ + //批量检测要求图片宽高一致 + String folderPath = "/Users/xxx/Downloads/testing33"; + //读取文件夹中所有图片 + List images = ImageUtils.readImagesFromFolder(folderPath); + List> ocrResult = model.batchDetectDJLImage(images); + for(int i = 0; i < ocrResult.size(); i++){ + log.info("图片" + i + "文本检测结果:{}", JSONObject.toJSONString(ocrResult.get(i))); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + + +} diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrDirectionDetDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java similarity index 66% rename from examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrDirectionDetDemo.java rename to examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java index c2165fd..d9264fa 100644 --- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/OcrDirectionDetDemo.java +++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java @@ -1,4 +1,4 @@ -package smartai.examples.ocr; +package smartai.examples.ocr.common; import cn.smartjavaai.common.enums.DeviceEnum; import cn.smartjavaai.ocr.config.DirectionModelConfig; @@ -17,7 +17,7 @@ import org.junit.Test; import java.util.List; /** - * OCR 文本方向检测 示例 + * OCR 行文本方向检测 示例 * 模型下载地址:https://pan.baidu.com/s/1MLfd73Vjdpnuls9-oqc9uw?pwd=1234 提取码: 1234 * @author dwj * @date 2025/5/25 @@ -34,18 +34,29 @@ public class OcrDirectionDetDemo { */ public OcrDirectionModel getDirectionModel(){ DirectionModelConfig directionModelConfig = new DirectionModelConfig(); - //指定检测模型 - directionModelConfig.setDetModelEnum(CommonDetModelEnum.PADDLEOCR_V5_DET_MODEL); - //指定检测模型位置,需要更改为自己的模型路径(下载地址请查看文档) - directionModelConfig.setDetModelPath("/Users/xxx/Documents/develop/ocr模型/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx"); - //指定文本方向检测模型 - directionModelConfig.setModelEnum(DirectionModelEnum.CH_PPOCR_MOBILE_V2_CLS); - //指定文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档) - directionModelConfig.setModelPath("/Users/xxx/Documents/develop/ocr模型/ch_ppocr_mobile_v2.0_cls.onnx"); + //指定行文本方向检测模型 + directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25); + //指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档) + directionModelConfig.setModelPath("/Users/xxx/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx"); directionModelConfig.setDevice(device); + directionModelConfig.setTextDetModel(getDetectionModel()); return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig); } + /** + * 获取文本检测模型 + * @return + */ + public OcrCommonDetModel getDetectionModel() { + OcrDetModelConfig config = new OcrDetModelConfig(); + //指定检测模型 + config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL); + //指定模型位置,需要更改为自己的模型路径(下载地址请查看文档) + config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx"); + config.setDevice(device); + return OcrModelFactory.getInstance().getDetModel(config); + } + /** * 文本方向检测 @@ -57,8 +68,8 @@ public class OcrDirectionDetDemo { @Test public void detect(){ try (OcrDirectionModel directionModel = getDirectionModel()){ - List itemList = directionModel.detect("src/main/resources/ocr_3.jpg"); - log.info("OCR方向检测结果:{}", JSONObject.toJSONString(itemList)); + List itemList = directionModel.detect("src/main/resources/ocr_1.jpg"); + log.info("OCR方向检测结果1:{}", JSONObject.toJSONString(itemList)); } catch (Exception e) { e.printStackTrace(); } diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java new file mode 100644 index 0000000..037a278 --- /dev/null +++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java @@ -0,0 +1,206 @@ +package smartai.examples.ocr.common; + +import ai.djl.modality.cv.Image; +import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.common.utils.ImageUtils; +import cn.smartjavaai.ocr.config.DirectionModelConfig; +import cn.smartjavaai.ocr.config.OcrDetModelConfig; +import cn.smartjavaai.ocr.config.OcrRecModelConfig; +import cn.smartjavaai.ocr.config.OcrRecOptions; +import cn.smartjavaai.ocr.entity.OcrBox; +import cn.smartjavaai.ocr.entity.OcrInfo; +import cn.smartjavaai.ocr.enums.CommonDetModelEnum; +import cn.smartjavaai.ocr.enums.CommonRecModelEnum; +import cn.smartjavaai.ocr.enums.DirectionModelEnum; +import cn.smartjavaai.ocr.factory.OcrModelFactory; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; +import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel; +import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +import java.io.File; +import java.util.List; + +/** + * OCR 文本识别 示例 + * 模型下载地址:https://pan.baidu.com/s/1MLfd73Vjdpnuls9-oqc9uw?pwd=1234 提取码: 1234 + * @author dwj + * @date 2025/5/25 + */ +@Slf4j +public class OcrRecognizeDemo { + + //设备类型 + public static DeviceEnum device = DeviceEnum.CPU; + + /** + * 获取通用识别模型(不带方向矫正) + * @return + */ + public OcrCommonRecModel getRecModel(){ + OcrRecModelConfig recModelConfig = new OcrRecModelConfig(); + //指定文本识别模型 + recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL); + //指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档) + recModelConfig.setRecModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx"); + recModelConfig.setDevice(device); + recModelConfig.setTextDetModel(getDetectionModel()); + return OcrModelFactory.getInstance().getRecModel(recModelConfig); + } + + /** + * 获取文本检测模型 + * @return + */ + public OcrCommonDetModel getDetectionModel() { + OcrDetModelConfig config = new OcrDetModelConfig(); + //指定检测模型 + config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL); + //指定模型位置,需要更改为自己的模型路径(下载地址请查看文档) + config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx"); + config.setDevice(device); + return OcrModelFactory.getInstance().getDetModel(config); + } + + /** + * 获取方向检测模型 + * @return + */ + public OcrDirectionModel getDirectionModel(){ + DirectionModelConfig directionModelConfig = new DirectionModelConfig(); + //指定行文本方向检测模型 + directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25); + //指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档) + directionModelConfig.setModelPath("/Users/xxx/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx"); + directionModelConfig.setDevice(device); + return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig); + } + + + /** + * 获取通用识别模型(带方向矫正) + * @return + */ + public OcrCommonRecModel getRecModelWithDirection() { + OcrRecModelConfig recModelConfig = new OcrRecModelConfig(); + //指定文本识别模型 + recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL); + //指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档) + recModelConfig.setRecModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx"); + recModelConfig.setDevice(device); + recModelConfig.setTextDetModel(getDetectionModel()); + recModelConfig.setDirectionModel(getDirectionModel()); + return OcrModelFactory.getInstance().getRecModel(recModelConfig); + } + + + /** + * 文本识别 + * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字 + * 流程:文本检测 -> 文本识别 + * 注意事项: + * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。 + * 2、模型文件需要放在单独文件夹 + */ + @Test + public void recognize(){ + try (OcrCommonRecModel recModel = getRecModel()){ + //不带方向矫正,分行返回文本 + OcrRecOptions options = new OcrRecOptions(false, true); + OcrInfo ocrInfo = recModel.recognize("src/main/resources/ocr_2.jpg",options); + log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * 文本识别(手写字) + * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字 + * 流程:文本检测 -> 文本识别 + * 注意事项: + * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。 + * 2、模型文件需要放在单独文件夹 + */ + @Test + public void recognizeHandWriting(){ + try (OcrCommonRecModel recModel = getRecModel()){ + OcrInfo ocrInfo = recModel.recognize("src/main/resources/handwriting_1.jpg",new OcrRecOptions()); + log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 文本识别(带方向矫正) + * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字 + * 本方法支持多角度文字识别 + * 流程:文本检测 -> 方向检测 -> 方向矫正 -> 文本识别 + * 注意事项: + * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。 + * 2、模型文件需要放在单独文件夹 + */ + @Test + public void recognize2(){ + try (OcrCommonRecModel recModel = getRecModelWithDirection()){ + //带方向矫正,分行返回文本 + OcrRecOptions options = new OcrRecOptions(true, true); + OcrInfo ocrInfo = recModel.recognize("src/main/resources/ocr_3.jpg",options); + log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + + /** + * 文本识别并绘制结果 + * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字 + * 流程:文本检测 -> 文本识别 + * 注意事项: + * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。 + * 2、模型文件需要放在单独文件夹 + */ + @Test + public void recognizeAndDraw(){ + try (OcrCommonRecModel recModel = getRecModelWithDirection()){ + int fontSize = 18; + recModel.recognizeAndDraw("src/main/resources/general_ocr_002.png", "output/ocr_4_recognized.jpg", fontSize, new OcrRecOptions()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 批量识别 + * 注意事项: + * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。 + * 2、模型文件需要放在单独文件夹 + */ + @Test + public void batchRecognize(){ + try (OcrCommonRecModel recModel = getRecModelWithDirection()){ + //批量检测要求图片宽高一致 + String folderPath = "/Users/xxx/Downloads/testing33"; + //读取文件夹中所有图片 + List images = ImageUtils.readImagesFromFolder(folderPath); + //带方向矫正,分行返回文本 + OcrRecOptions options = new OcrRecOptions(true, true); + List ocrResult = recModel.batchRecognizeDJLImage(images, options); + for(int i = 0; i < ocrResult.size(); i++){ + log.info("图片" + i + "文本识别结果:{}", JSONObject.toJSONString(ocrResult.get(i))); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + + + +} diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java new file mode 100644 index 0000000..8b41f9b --- /dev/null +++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java @@ -0,0 +1,144 @@ +package smartai.examples.ocr.table; + +import ai.djl.modality.cv.Image; +import cn.hutool.core.io.FileUtil; +import cn.smartjavaai.common.entity.R; +import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.common.utils.ImageUtils; +import cn.smartjavaai.ocr.config.*; +import cn.smartjavaai.ocr.entity.OcrInfo; +import cn.smartjavaai.ocr.entity.TableStructureResult; +import cn.smartjavaai.ocr.enums.CommonDetModelEnum; +import cn.smartjavaai.ocr.enums.CommonRecModelEnum; +import cn.smartjavaai.ocr.enums.DirectionModelEnum; +import cn.smartjavaai.ocr.enums.TableStructureModelEnum; +import cn.smartjavaai.ocr.factory.OcrModelFactory; +import cn.smartjavaai.ocr.factory.TableRecModelFactory; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; +import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel; +import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel; +import cn.smartjavaai.ocr.model.table.TableRecognizer; +import cn.smartjavaai.ocr.model.table.TableStructureModel; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +/** + * OCR 文本识别 示例 + * 模型下载地址:https://pan.baidu.com/s/1MLfd73Vjdpnuls9-oqc9uw?pwd=1234 提取码: 1234 + * @author dwj + * @date 2025/5/25 + */ +@Slf4j +public class TableRecDemo { + + //设备类型 + public static DeviceEnum device = DeviceEnum.CPU; + + /** + * 获取通用识别模型(不带方向矫正) + * @return + */ + public OcrCommonRecModel getRecModel(){ + OcrRecModelConfig recModelConfig = new OcrRecModelConfig(); + //指定文本识别模型 + recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL); + //指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档) + recModelConfig.setRecModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx"); + recModelConfig.setDevice(device); + recModelConfig.setTextDetModel(getDetectionModel()); + return OcrModelFactory.getInstance().getRecModel(recModelConfig); + } + + /** + * 获取文本检测模型 + * @return + */ + public OcrCommonDetModel getDetectionModel() { + OcrDetModelConfig config = new OcrDetModelConfig(); + //指定检测模型 + config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL); + //指定模型位置,需要更改为自己的模型路径(下载地址请查看文档) + config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx"); +// config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx"); + config.setDevice(device); + return OcrModelFactory.getInstance().getDetModel(config); + } + + /** + * 获取方向检测模型 + * @return + */ + public OcrDirectionModel getDirectionModel(){ + DirectionModelConfig directionModelConfig = new DirectionModelConfig(); + //指定行文本方向检测模型 + directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25); + //指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档) + directionModelConfig.setModelPath("/Users/xxx/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx"); + directionModelConfig.setDevice(device); + return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig); + } + + /** + * 创建表格结构识别模型 + * @return + */ + public TableStructureModel getTableStructureModel(){ + TableStructureConfig config = new TableStructureConfig(); + //指定行文本方向检测模型 + config.setModelEnum(TableStructureModelEnum.SLANET_PLUS); + //指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档) + config.setModelPath("/Users/xxx/Documents/develop/model/ocr/slanet-plus/slanet-plus.onnx"); +// config.setModelPath("/Users/xxx/Documents/develop/model/ocr/SLANet_infer/SLANet.onnx"); + config.setDevice(device); + return TableRecModelFactory.getInstance().getTableStructureModel(config); + } + + + + /** + * 表格识别 + * 仅支持简单表格 + * 流程:表格结构识别 -> 文本检测 -> 文本识别 -> 合成html table + * 注意事项: + * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。 + * 2、模型文件需要放在单独文件夹 + */ + @Test + public void recognize(){ + try (TableStructureModel tableStructureModel = getTableStructureModel(); + OcrCommonDetModel detModel = getDetectionModel(); + OcrCommonRecModel recModel = getRecModel(); + OcrDirectionModel directionModel = getDirectionModel()){ + //创建表格识别器 + TableRecognizer tableRecognizer = TableRecognizer.builder() + .withStructureModel(tableStructureModel) + .withTextDetModel(detModel) +// .withDirectionModel(getDirectionModel()) //如果表格中存在旋转的文字,可以使用方向分类模型 + .withTextRecModel(recModel).build(); + String imagePath = "src/main/resources/table/table_ch1.png"; + BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString())); + R result = tableRecognizer.recognize(image); + if(result.isSuccess()){ + log.info("result: {}", result.getData().getHtml()); + //导出html内容到文件 + Path outputPath = Paths.get("output/table_ch2_result.html"); + FileUtil.writeUtf8String(result.getData().getHtml(), outputPath.toAbsolutePath().toString()); + //绘制表格结构 + tableRecognizer.drawTable(result.getData(), image, "output/table_ch2_result.jpg"); + //导出excel,如果导出失败,可能是因为表格结果识别的结果是错乱的 + tableRecognizer.exportExcel(result.getData().getHtml(), "output/table_ch2_result.xls"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/examples/ocr-examples/src/main/resources/ocr_2.jpg b/examples/ocr-examples/src/main/resources/ocr_2.jpg index ed91b8c5ca2a348fe7b138e83114ff81ecb107de..ecf52124a6fe8f725eb48d40bd28c889ff1de51e 100644 GIT binary patch literal 247054 zcmeFZcT`jDw=Nn)K$-|hFHwphReC2Xiqe(dB+{jWNDo9odQ(7<8l?&dNbf{y=tx&O zgkBSBfRvMO?|t^!zwe$q#yxlcbMF{mGS(k!vDVC-?>pCg=X{>|LikNs0^HD4*H8zL zkdOeL62Ab#EI{qAv;WrTFCqUe0e?MA_z9rDMtY0*CE)>Fp(i1wCn0nJfB*mq*}uyM z{Ktpn3h7lca*At|RMf;9YHt9pkdTsIxk^e#cJ(Up?qK5I0axkC7;Z~EAZL7TO~K=J zQ}W%%uh)1VR<<+gkD~abY+i>_QZcizva$2u5fHp9BrPK=C$FHW_UN(tlcyS*28Kq) zCZ=ZQws!Uoj<1}Yz2Erw`uPV0hP@Auh>VJkNlH%nl$!QAJtH?Szo4+_TX9KMbxmzu zeM4hYM`u@e52ClPe{6hWa%%ebpPA(q~D6_6YaWk^(yIAiofh4x#CAWNa?SV-IgF{c<`LU+KZ7#^4+zY4?ljbY^UUv(nm4b zydI@u=96CLNB?EozghPG%&^e^SC;*!VgJjn82}9_332jB=>Z@BE~dFP+i{7zl>o59 zAMOfKFsjfRo|r)(Cv-z$y_X)vhSeRS`1- ze20A?x@tW0rC-b8_AOcPT8j2{#%m!Z}?3EXKs#pcd8PGJ;Mt&J@?3w6pvwc4EBe(cGecLm~XKel7L<(<9X zUKw)!NfP1s60t2QaZZH`B>*C!SfFHeaI?9Q&kVis$eMT92T0E65paK=9!3Dx-kLYm zHf^Pc^gdOt9I@gdEfhW`TlQdRC|0k#!en9DS5YEUIb zV3h`z;8@%aEW3yRU@ss5Zq&!XHwDBmTa{a2i>uUO-r3RvO@8=;wB*Y+0-%@fU*zRo z>Lc-iGtk8z0$`=|gaE*Et)am9kdu+&aRMOzIRS9}5F<_ifC4u*h&uET6^^p#1|Kt~ z;TCeN^bQWm+8c*6?B|sT0Kb7g+#+<9;IWB`PP0D!f69}%BI8bZ57S1K+j2k-}a;sR&qSC-~BbR_q)34rFcRr~?`ko3Q* z{0%?RKmg>_!OqZdObHH3oSeT&>uhPGCIChj8qa5-=n~KhGui}qb&aTQ5ph7U?>ybT z3)B5{34r;-L#W^4Imv%j*bwS$2_3P}yTmMFjY8lAz~6)|FM!DifQ>da+2}D!- zm*PWT6Ez(tjs^aHgPg*+c5@MQ|2zQHe1LuXFS>&M%N)bO1i;^9p$^1}o_2Ir9^wnb zIwxYnTZ{;R2MW;nE#fE;0JkhTvbG9ejKj|$EwK4B{Qdu`DR3qYcr-PR$IB4_OyCLv z;P1lz)qx%^2oV5Zvf%s2#36uw5ThM_GcLBZc-dy9w|`0essF0!|Cu-D;ct5|L4WCj z&jJGiK=57`@(o3&v^NL1MdHi`696CnMbjt$;yETe1i-)gcHaNRH~(?i(73CuqU%>Q{h{Z9h(x8(ot+4VoUIx+A6dwTs(uFn2XuKrJ1 z`uDQ*$(=mD%tzJtZvxL9x*HK_M|tiX%+476<=>0n+XO&pAcPZeu9(nD&1qNhyYCiv zP+03+FG9dQrf+cbg#rukH5D14q>Pq}Q z6YWUr$hf7n*2fDv4c(;#fKQnTYyom=n>IC2(!{jCE%3cOBT1{EVr?2ve)=t(ziICX zuh4OHVNxB^S4DrR?N1+6{Zi?MgpKs z3|a=JDcv~NJcui}$gqPR$_)?zyLVvg&)683mT`+BvBUm)O3#e4}lObQ4(Eu3>)TE?#vZi$$#D}Ey<~W0|CO(=}bt;cq zB?n%LyVvf5s?xAo)1Aw6dc6d|TT24K9DR9kQV%;wT&*QSWK|!4sPQ(y2_yR8&~m*^ zD+09or60O0nYJ@~aqt!Sze2<|^g(5uP~s{4)WCRrKM`@BQ4E|1SKtT!;|$+;`T95# zpA@o=SA?yAFWgDL<;J|c`TQ~9|BpuqDFM*#%u1mJY)mTC#wyW+J7LW@i_B_2)3U^bR3u%WK-oq13hRMGzv0l-^JWGSY% za2C2ay*_z3UY&aUIC|4>amNF+2<&qqg7Wq}pyeV6$+jbMcC~RRfb8sIcH|6kXnsNf z{LuoT#&ZD2N9TViYvdH0>hHYR=F?1@xCie`1HVqM!%aJ;xOc4mfRO6Rv`e1-;vx{!m> zYJlIj;*b+2zYL4nrAv%potmFCtEXR$hh@R~gmb=cuzO}}D5=w@ZgZ3Oj84QS1E%%67| zd@3MsX|%6P-3p$IBST;i-Tu z3OqqKV7XsJ=7@7e7ln6@Tb%?BPuNzUEzG)=*DIk}vs9Tg%c8sVJI4~rjZ}ysb!U%irfw@bu+scMJ=n~~8IgdD~WlS`Pbq%~6BhnpxAp1r( zV^GwLRI_Am*y#^BDY9oz>o^&_CSJR&-$p=yGHg#`swj(H34of-aknEuBE6&0P+J~f zw--#ui*sB)ci~(lAOGX+yk|e}ehi0Z!|!7|_eQ3%PRe6}ZoKjb(`-a?>0LQ>JB(gg z0qH@v)y6Y+BCI}MD{s6qW+srppCLQPgp|A~7U={i3KDI|pLe+4vr_ewN>Ao`9Zv_gqd%WXeQ8U7M&m_)G&Wv^e@Wj`J`- zl!rziCh&cY?y@)iWR=Rus4NymN|t`@MgRoa)-B|H!wL_z z1fVmQr#sooM6qrw@k*TpfIG)mmp@!g6CNB9y{q#5^jV${4yON9#CBSL-IHL=xOw-F zoMMuOSxtpsLCv_uf$eyyc{Xd1=BICCg{jrW!WKql`=4443fmk@&kNq#->o@HosSE$ zUE~Vhs_G~om# zxA*nZPwu@9svGgMf_amhKlI3Z`-+15pmW}y=J#0!irGdUE)Bj5lYtFokIr;ei|9d@ z^*3n4A47M?f9lEz-#LHdrQcjT9>|wGX?a+QRZFqQ+(kMg?-~{9sc#L7YSew2EI(_T zg^XTuCr7^MylN`|ELQ++p!(0daK9AfHx?Ta&e##$C%9V$rGN6WFRU2rh&~8cR$1x0 z$&pExfsuZcoWRXC%Sx(X6L7o~lo$thIe1ss#y%w`O&7Dknrs}ao4#vfR$i~+De*)6 zLYzidopS4Z3Zr{(8Eb42ImDZ_bX&cUX@$Mm_^zs9{(gDE;7OdVo86ejM>SACnw393 z!8ZKVJ2uOf>f>puMt`4NX;rXz;|V5Z|8|D5Z@a?T2xuU&BYT~3YeW~01iu4TL$No7 zS14ja#Ai2Q3_kN5Iw;eL!U(s=`Ays}awCOB!aKjRQD?^l#$6AE8zXV$P)E7B9kfTs z{>L-M7faldx~_;@tMnsdEqD$jo_$4G6k^PB?;>B&ro_5PPr|dn+&c{1nxA#j**~QI z!p}{vJOIqN`lpO?>T6tqhJMNBz^sP)k>-AXA3hV?mVI4=G;td1tclX22pNTuxA>L) z*$iRAM1S20EZD_AE(^~}6O|sRgxN&a#e3J99{!~9S#RC^p!h*GpX!oEOpY@CeaTC- zp_d_2OvCUizhIB%-NSk=aR*q2Q_ei+V;XHs)XC;_oOxV$I*iTfBFo$X(Tjz|cbn7>fkoF4(n5C1UZzc$^|*TWDfUEbtlQf&@v zQn!>WHxcpQIlw&@R=Ofl&vlfAWJ6K_6SYHRR^a5S_b`scxvT-R``a8+?j5+u8` zjQ0M_kLvU_3fmy9LS{`DJ2%DGrMt-Bx<(NmE;p0sloU(8dlmB~Jth4zqN2b#{c99q zjsC1@I>!~LpQo?9U7)wzBcajPKQ;MS$;I7+_gCk1=Zc_y*J#KcX=4IXLRN2xX2l2d$Hhuf*rVPbn!1yH~UpAjynd;ZX_nZnO$ zfo-vD*4!{}z9hE@kfa&R>Fr~IAzoP4Q5MyCITzA0EgF5^MlG1_`mn!L(KOBgyxTS= zCcUPaaxsu`IBS@HP>{%bcZG)83(TTMHJi$yZ^YeF7nkDUS6Eiwx>uJ|5@+Fg>&kU` zg#w%VS8dVXCVw#=-L(k}y{^Yby0u6EOlnD7kyE(LQx3cU?totg<+4S1<_R30AS-sV z43rNwsBV5d>4A={#bC2=%^=qUu8?7F1yoTaR@m0w&*{6rSFKA?qf=OZr+#w{RhwaD z`TURKzN@Pzq)ewpyVYm|OH4D$vE52(>T8VB0lgFHvrLCw-puAGs$!9@_{!@V0?O-c z7GynavY;q$ceYMrA1UqB&@QBtbW)r>tHuL4c(Z)1`$djAi|PPcAxBuKNX*shdTx>V z_3g2>_h-8k6-7ZlmB&9frM8~4`9|=)VPhq(h0FS@J|o<5eKW(t zg~gmUd7(D$Wld79vQw?m3)bwhE1sPI#+kM-mgodVY6}+WIY9#-hflMs3DvVT?)WDK z=Cz79ON>kQbW<$Xr?w>~_lRu|6l%Z6+>r7JHb5K@k&O519n#GIbTy7~g03t?8b1I- zJqO-^*kHOAuA;3XuYY91!X3vSm;QLtNJAO^{!CKtGp`xv-p?iP8Mxhwy~OR_PC?#} zzoz3mvz{lTt(fbS?sNq~RH%+RCVlJq1AW&ME&j}ddYAgEQde+z32PXJAqv14odN4pW+;`qiITWbZ#r7?`Rlb;7j$5T8jG~g}$EkP|#lfOtM!@ zqI7-}-~7q+qtqrVMA4l*76phf6cYlQjLuN!jBhx|+Td>S!Uv7z+$M?XC|2OD4KB;ykiZ0lDI7fL4*|L(3*y(W3o(Mmq%CNq^(P^UCE5w|S za@^|2-W9ls>UIBW6Ev(-oZLUtDq8eJB}g;ja9uF$9d%9eT}zFI#MFCp$vq0khm!76 z{Zez&3VlCV6I_k8qQaFQZREQts0)d^)GEB*D*bhYvQ&FFD8=axj!4e>pFescdFp9v$Fs zVOae+6!+D*(~dE0ddDh)BSuo&*e$;I2j;@)NV`6H$N$y5COu4`Z5%oz-_d{SKAZg+ zGA-VPtn{a14L4wnsM*%XoBPIjaLnxFXkNAkItRJ$mOrt|=l&8*CMdwhi{gewH5k9IN?Q@=VE{ya0O_nMjgBFa~N&-CMyvrLS75W0_Zy3zQ%6Cy)* z=7n5t^-hHzsO30z!PXAj?6q@DQJEKT3WA+<2joGlFz(+s@e-q?J>8r#D|YvGpRyh# zmI+`*34n-sinuqDdiTYj5deA{mZ-rWLc%b59Ss4W!Is*PGdIY|uC);6Rb5e3oLxrK zdIXF%wUq!c`ID2r&eg~(z;+J3z+9XhGf`O{2&@V!G2_L*Q0#!YLdJnL>K~VYVQsQ# zk1%DOf{}{Nj0Y{_d&Ya5b2_J`{w?319IbwjTfDnTXO4lNa^V^vZs;I{)!lJbF=zHW zvAJ7#E{i=O4^{sut%E&k(<=^+(9GT>^`Fnj+Q3{0Hf{@;y`@4-m*QvaLTtCSvvu&U zVqurSbP_G*v(IpII-L!jP8RBv29ya z{gvIVKA@{59o8@7!rvAp!=c4QW|uwrRIJ#-czSVtbQeJ%4!;~^_g(PZv2WYiSgZhR znlUwT>$x9atSW<$KwAIo&zp*q5#K6*To+km*j=J;_;wzckpEg;aT@%>a9h8a+u4Z8 zlmL)));u*A;8Mo>%EWCAS!Jal=cP{;i;^EazG)mrC7Ap6zk7g42!N$pl9t7H{_-Pn zy5m~}0K-gjTs$y$%_MO|=nQ?m{P+j#SB3k=znWt<{Atps_YZjLUGced76< z-VS&;hacLz2c0g!LEF#YfcRDjfOA%Hy;1JSV0_&X4vkH1g`Vny#E^W0#dhj_MO4wgG|T zJDl)E#~lBH2mSBSp~SgGgaKBI0MPl#x~xH5u8L$bzgf-xHk*Gj*IPL|({(hs?;9RZ z;-Ij5pG~D<-&0uKpicHB^-D+6;?+PjIGwn_B+jVrECV~EAyWAk^WEi`DoQs|@lm9y z=yUA+k>;aNJ&`DGW%amo>%(`+RxAvT^6tRCYGovcJc2f~L!r%Qzo*pAASF8|Ye-bK zuCB@Px?@5?>XH|0)8uU(gCcQ<^)cOClQ{j!I-KZe^X!%d0u!v7lo z?v99*mY_^EJ$a=)QnArP?AVehWXiNd5-;7D`g^+*SopCKj|P0Wgd;W5^Y*14v`)lWsr z=54Z1KKF&H_hoO?uzgGbY(#ZJ207O_-Pz!11mMv2x&6P-ZjqPZ>>~S|H{}B zUX(5i%w%|at;=>mdv8t>n%!U!Et!>Jx-UN>+$q1}&f61gATscy+Q`e)l`q9wL-0w) z-PimoR?!6o*>L{8YAiFAd;!P#r@Of?3^1LS zHX+j?+?aYaZ)XVmgwNS%%?M*(zEcvyd5`4}Sy@?s%Oi`!ujy&>yAMXpk6u`u_Tnmi zN@-o5PWpJzbAj|}J>I|^X3E@cUTk-l^)L!2hbmM|F2;as&xqCMpM{-HD@sh|(z&u0 zvU!hZ>db8Fjn)h0KbFo?mYEL8iea;JsfL2zdz9C7rQYgIsfvuZu&sc?0_$VGLsW{? zn$dkIq+6tj>pnuP0zCWV`nK z>P6CqBKvt_8k&9ZJAR!42;??$`90DSv16*}!!1WDO#pZ^>7{ps9EeR@?dF${tQjnW zyAq?|*RikgVwk`)VgyQAp7N`H9Ppv0`wtt{50C=gnXc(Hkq<%50_#?3#-Q$)q(>1R zJpT5z19>+W#Ef&Xr!o7kRE+H z68aQdUrCB`C=n=ef)N{Iso@Tw>zhsR808yHwGpk~GD(;}I=vK2T^22Z4MA)BXhOhg z?uOQ}?z{&Z*@rI7M9d@{Sh8o@%kKrdx0jBWwGFe{{1!nus0T z|706-Kxc1S@`qWD__mu#1UP^rdxng%&Kn)2&-wL##T7UySQt$@O<;ELlt-6p#A@j# z#BW81Ze>5OluL1pAEsmRo9cL9P(?QNtKRZU-1m_RBi@(tSbY?z2Y%%b_9@CeS~bkRSd4k>kXmrR zBh-`MMT+m`+JkdDmHR`lp+r=5k2DuchcQ~A)zlhyS+Qu9G=+c|l%2<|l)`7!e7K*c zM~ra@Cp~4l>>(vhvG%`G#gx{ODnr(H*p+= zab*(YEEO4GKjP&0dh&V2MLO9s=3ZPt*`0i(Prgs`Z@do{isJ1Y4;lLaxruokP=XoJ zBmmwgsR$TNJe)}vxmJ6W@6J;P7QAHzsXzam5WsZdlm~4W136~vJ%e?EQ8l%QX@}ad z=MCc`FpVS&J6Z;1JJp;)CI?G;xUa@ecK`*Jae*%U!Y>NzjUHHm$G521uW)JZyNI&M z_%*Rqc6+AQ(*}Pon-M8x`$F5073X$fW&I7JGlmD?XHgh3BPrwj9iI=)}9mhP~y&zv|6CGxJRvC!}>)ek$G=r@;q=jo3F&+%d@}I zoa-JXrXB&VgzhOViBtF&bYPz4g%o*n6B2#+fqa@;vsdneLXR?gH9nsXM5vYwl zZ&YG?9URrD9Q;l1%Z^0dhnawEx|tVoX=ZP32unZa5mKOSvE4rc8?9V7N?tudo6gM1 zbxpn+pmR2zBlj?ur}7aX(+$@|VaAH|4cA`vm=}EAMmx}nb{JP!nYsIzC*@90IrV6U z%Lxj-nN3M?ZOqbd&ECz4jKdEt0`P(m?Hdk>l_)@;AAwyA@ z=u0+gkm>l-r+nhz$s&%Q5${j5Q#{Mk`ii~N)BNqMr z_^0!obMMV#Uwc8OCQy==b#am{9NnJ#!RbaJlL*F$?qIX$MKS_p0-BIRjl->e81X_%w3@ zA{F$_KWC@Y_u^*Aqj)DCb;q~01IyS=e(i%S?oMh9cjxH?RATqyRfs&k4>S4QY8x*c zeE((!wrkh=(BzI3jn@b2h;0f5?oaDrls=FdQ`!zEsSUR6P@Kd^mZ&$z9O?Ow-T(P6 zu0QE|xTWNZ zmFm(q4?C7Tu|WMCDT)MBQ|lMzzqQ?Y{4=l%Q#$KC!80M zP+4wo!0S`B+~S{nub+G?XIUbTyfnj)d1AsLyl9u_4KJ>c$^F=7{3!9Ewf-WtzM6iI z+B1FO=Z5FjJFqt55{O@j`f(0qE2xsyYP8fHE~n(BZ0k__0Oox6>u4Ml`+NWcj*$$H z?StGKLQP088vanIBO86)0<2C-&VE%~FWBd;mP73_4OYrCuRZL^|T z>=!v{_C932v&t+da%KF=a`{{A@u$h?>jZ%Qr0DKi4nn8CxmMK2H;?2=rfV2YKFZEY zU6Ra8?~*%Y;ZSxh3o6ry`WCaZ- ztRsg*>ns#VpI$SuNX|;B7Mp%}PY+v#xYD)@LOq3(3CC)A#3)9zyYst5yO%+>fP?Mg zbt4W>gb&}!ui71L+`Z(+FM6Clh9Q{-`1tdEG9cmZt2 z0X^OILav8faI$tsl=rE6*xvX99_fe!T?+T!(RZ`Lc3FNW8)w{3Pg05Xs-!G@-iGDY zOQ2QQxXh(Vq<8NTH)Ds%^7pp3CvSTuyWrL=_|b=un5>MJ0*6N9)|Kqq7?KK+IFt<_^DdZLa1y!8d>3EJD@agcPX z!{Y8k@_J*t2RBmnq^z&f!`FvH!hW_1VFhpvcxlVdZ&)>q*UFI2_mteqSe-sL*SOdM zc^r~4R5i)R7!xNo6U(@;$+zO&lRuZF*RyS+yDUbzjlmaX@q<6Ykv zelC!{PUAMC7uQF8#03*|?J$IT#+6s3%50kx_Mi?!!%fl$>W4EA-Qz|I}J2>3ICa(=-`ywfjaJiAPLPy<1o&za0fkv8}LNJ>_P> z3|)Fbf=~eE;U< z)>sAGvy?pFY}8RPHQY9=_1WO&%X^J@|3ha}6I~x$-MsZ=Fdg5^?ZWJ~ zj@F7%&d6{Az(H?jyu*s8Zu*Vh`vuW@G;K9U^|uyt@5s*zw|iZtiCOjJX+^G#CTVSy|2dDjdCFWXyE$w`+;u~ zYnGS{;Hk3D*+rxi6lFvJL@UOxWjA1?sGf*@z3R;oWHHp|bk{T*EcZ-^%MOwi+AOum zbj;>Pcl~g)h50!G;u2aA)74|dD`V8t#`8Kx*JImI|9$>Ub6WpQg%%w*d;55?W+a0b zm-u&GA@@`kdM4F?2ZBcKNtOw2V2AwVv1yPR$c9S|*_MY|S>HJgOR`?^o2=W~3S@mD zgiF7E?Ljps=!flj;MpF@LDvi_|Pk7$d*@Rg4$PYoE+T3d_q>JE>=tX-EO35#6 zedn_5@v2Eq$})QKCfPW-b-!SvXs7lr(ledo>!%6-EH`CQnTMt$q3$}mLRDLL3WoS< zoOXhrV=wRvM7EgaE$Ed85MyDElAE;|uYuCkeR|^_C(4(ct-iklaS-tNvo=U>LHof| zVko%#lIO2y$lF*TTo@M=9LzpBT$%s7J74*i%;&cn{A+`;+51T(P4<1aV2Buu_~`=f zv$DjcwEs>gH#@4WI0$N6h6%3Cj6UePz4946Z_b)`XdUmH@|N*~?z-~yA8-{M%Q&zM z>xPAMQ(|Bxm_+lM37HCB^ZL3f=Et|5I;M(O58BuH(%99DA1ZK|g}i(~dJew@i}FHK zN8f(ivHE(&J}fVKD3#}cmQ~2mnmr}UK=*n^XzjrW)EBWD;ASNPG0Vj1N4yYOFJz}< z(=BnK7%tX_Pwr<{MT+jH!G{*X`5uQ$Isy-DnmX}44r-&*j?L=gZT)dkrZj~LzSaut{#u?;MW4?gJYQ-M<9>VVaB zKMx}WFhO4i*nradvp34&b2dq(NM+oT_rwL3gBZstG2*DOpF|WV>LiiMpXI@*jQ{W1 zf(01u&kQyR&bQ1-PKu;iO9}oP6I#)9}_Td%`lf5dXT(y=h@al;cGFx65fW z+HX%DVvfJ_>rE@W4!2$Ef-s}6?o`5%|ItPk+2BEfp27jx5o?Fet|!7O5pBX~qd6_n zy5#&hJwcm?aqrlZ)b4^8o;D~o+<|iwL5ReNwL(lh=b8Q>I{#%7?~RYy(pb1)h}0t2 zb?@$wO8hAJ`bk9$m;&pzq{8kmS}RV!aw?USbSJ)K#9frG>5HpHywx-eV1XT0(Y%}Fo+ z7F`B7Z?QWOFiv_Ne{ABueR?!%MgT|y0X*ZX68P~{yZ}0Q!rUq)qj`rtD=jHeY}wc` z+AS88e)MilYeH=Da<&0976;=}y|FnXoeSdH6s66{*`kWgV)*s*;Wuhll0yI&2}lhb zNkli=g;bS9j&vN6K$<_1A9Fo!?OfZ9ydGlhbTF6T(uebT5kItnUt zVqO@^8)$h_VG6Ou6;;&En;0gjh+CndGZ{fexCzfL2AR43?+-t^t8S{OdksN#xmhvY zsIW(<)A%CsxHr_SyWidRrlfo+Qnzk!n^AqgnU#vH_8DQqw%|t=t4+G}W`xaKs!+BT zPVspbqH8^6-`LuQlKDJ8lbT2KA4O$@$t3Cp6yz(ahUYQdU0i#DU%k50A|JY|rHYqwuuq!ToVHF70+V(`1 z+veCGqyH3A^K?1TNp&9?WW=m_R{1#_7_6NOH+vLcxLrQQ1f6=~zF;N%tV{h7os=uW z{(S6@53j+VNF2l#F$e;QyZGo*~EIQf}+XQElNtOhgfV#?ooscvfmQj;9UNhX8>wniKg8 zJd-ui_dfxF>_GvH2vsh-`PMuQnIDoQR&aY71S?RATYk@~&Ra}!#Jh!p-Vimw^ltEQ z`$tLXOYcoEzjnR z=Qg<&dq91hh`l!f(!}$N%$J2l5!zy;27)7@@mUqjcdw4ythZ?nAOk9w#Fc*!8N z(Vciz3(dHE3SYW(>9oC5UDR4)b=Urtnxg87JFx++jM>zcLK42so*dpJn6iFLr6q0Xw@rB!-&ixkWy!b;jRguH*dDc5Ju9{mR*$5y$23vpo%%EUL>crVeqbTI?j8M`FgUB?Pxvbm0z4~*~gB=FT~_j)&PDEWph zUTJ%UOT|-e0XeW58zIr{o6352ho!-7?gZ zPAISzXQ7D(CJ11HbGMr0P74Yfstc+XhE6xjRC_PKHhc`@i@$e;A9zbanF_lhIl#{? z+Iz2RSy}A5&$ouoyNL`UvlOwl3LrvEOnDMareL&u;j?A5)O{UQ?{dWO}jpXjIkummaOfw7`}hP zx^X~0{t0t@Ey&AkoEX0q+GqtX)IugqemqvDykRmiJ&?LAW~|fCqAB{LWbeGLZu$4i z>3LP=9{EOSmt z0juSNdnkt$O*Um9TPO#QD;VkyZfZfhmGYsB#$?ip6fO} zF_Jgvk$~kYck_FC!#_OH_8wUmz5qquOAoO~UXsK%eG-uc(y2=%Fl0YAvo4Y@QWPsm zY0Znv$DQKV%2JDV0z^4gXxfPFXhLuF{1+<(4cBzKC!2}Av+?tIvAoUPn6pGv z!Q2?YfA_#%`-^ZS`-^aNR3#FQ=>JJL_WV!6(FWS?OdSBff|5#P&CZ*~^JnzQpr<>D z7@*xByO+4^e`$wKJ0So_Hzu&ju>`>C%T~~%K>q*Q^!zjt> z%tPZH;N>B6t;D@T`LITKLJ=knpZQkRS5}d@hbG5mWpSqSR2@~n2P%3sm4PoRes#FT za>w6YdfDAxME{L~RJY=%St&a*&eI)OjooGZaYY))88jpO0GACR^;r?somt1@x9n=i zMD|*Kj9IbHPo#cac~SChSJkWk(q!1{kz`<_sx=L6G*#yb6`Czuajz$BGIwp@ZcXM{ z5p$B(*Nt^U6w^JQ1^b8Dmi&sZi$SlBo5lu=9P3)$Q;U|Md+C>wSaw`y?zpnbGK_gA z=Qeupf^ubSA6`8u@!R2PSVP1NC0Tn@;w$}2&3pV06o7A_?b0B7Gps`XEaVls?d@qs zUA-gc1=*+Lr~E%+2k(m;@^V@+-mFYaUpWNtdnf?Y7ML;7OYqpm8s!`i2Df5{9Oat; z)eP0{P-r##SXce2d$e8h{Y$vXeQjHTWN@u25m*V03mMVh31P)RmzrrE_Se__rH6#Z z+P@p$eQs;7&+L@PbMN`*cM(^)h0h1x+5W7RFR5p=akevKwumfc=Tu4?;oOKV6YQan z;3;G)et080>ubQ>!-o2s-$Ov1uVZy#5=#;sby#Tg`Z`ZJ0Cc#WYlQG-Whyp|7@UbNaqNn9v zv304*=_dh=9*NkmR5AMmz>tgVe3Q>hskw@~EAEmPrviUOOlpcWE`{n|XA6r4xVf|) zI0O)@$}qfczuv``4*^i{+GHMxGN~0s4J>0!Cui{t(YkuiiG2aBS_yf-aViut=M+TD z-M<+_ihc-;rhmn2+Atwn_{SzvoVIhmPvMpbnyW+R?tmOEqAbW`Pvg>K8(7N{{^o_S zJ}S^ZLm_H&ww*eV+F@J0fF&PJj<$0>cK zbj7nqB|I?3(AmbRLDQf}*Gp$>ADsE3#xd;e@L{i~tXuK`L#jA^T(OYEsgIj{|96Ls z8jjk$N%zU}MB;V_%pD$*O9_83`$P}YkNYFz$;VuvPPNl@T%r%%3{($Qwb^ubDmoku z-O#OOkv*gTT>c?H$C9y5O}8jCZw*;XG(6Nc--QS%nIUtWCT8v5_x-5JafJo=-r-;c zrYfvAFU|^|mP|k$#OW|WWwxOZ6iERIPe zv?$!{jelbL>VWW<%JDeip1cAMnQI1yxYDz1+$Mk7RMA$=?NsBXIbLFF9u^QRWF)|E zEW?AWXlEK3D4R_7^R1f0reM-9Nih~nbG~(OX6P#=acZZT+7h)Qr&JGb4$I<9uLcjP zCn*8Vxk(a{q|R}1aoI$6gt(4LR){N;eZgEV!4mk;2qLq;Jp-vHOV61guV?HTaCG&h ze73}We(Oy|DzRO9W}S$*<~5=w@ukZg9(0MckPoq5zNN35_km}jLFtJW5zC&c&%wG7 zVnr94vjeA9rd&z{SU?o3+Nyp@f3C?3QnX69Puxq^9{GImT-E4fR0mxXojF#REEW~h z=9p>kgL>ZSxd)*UNOc&veCWUZ$>vLIb8fMWhPvi63%OpHX!?0Mb3MrbFrj0Bnugi@ z2f8B7f&OA{d#aR)RrC91?OlVlBp1WX=d%XWs{EUa_3(}ZDu15NhtoFozaXMt+$K&; z0sxGB)7o+pao7RQN}3cTV1o-JEdyr_`30_}h?h>h=F*fZfnb_#_3ZFlYB3xw? zEnj)G!v}{*fwvomaaXV2d7^Aw14PxSE^B!tWy72>j-Bs*9 zO%7*vs{Xa8CSsLQ1!)n-N)~0jop|o(f~4U;oyX#ah(y z^83OG7yX~Q^*pJvDUFVOa@T^bm|G(G_kS$Lj=T0rW)IWtYjNThGX23;a3Z-3 zBM~J4Ug=#KSt>2FaT6l{(9a*G{(`gc=TBwHa^E_MnmF$C^)_x?3<2QL8zK(g1W{uH z3N8Qm6ElsfgrLVDdw9mZt|8wv2APgIH=6w1C(ILo*8w;$qxdvmUAyPWJqJbd$Hx`( z7&*$1ju2rB+V?|J+`RpjeFmj^vR#H{R&f(|)$7=6Rx7wQ?&KFYK1px9b5g&}siGx@ zu*1X{dVOwj>d>3JHgcz-Fwhw z0wDGPzRWXab5vH#OfQn!%b}+eWjv|+>=#R)@vY>9Ng_zq4vvD>ogwl9oKVOWE_|_9 ze&L?JlOS{xmMkzi4 z3E`?c$hg;D?~X^76Mr({DnX9^C+iq*R8g1Bj?oW>g&F4$v#~*$u$rH3*0N`zItzwE zdDqEVB}%!$or3CS z^wd`TGm;0YThd50%LDZ8hKrdWR}C89$fpf3l|a$5C-P2TOIkna@D(u8s!w_K#1}pqaZ-k&_;ZV8=9l5 zI6uGeUdicwGAh;tH*)d^2blHN>eG8us<%R>7HQgKFr+K>aXwu7V|F*3_7uX`Yom#q zD?g`FyWiy66PTxspS$#@=pKm%)tmG{X?2#xS+KXzSDQq-qD9MA-ghS51Jeb=LHbG# zZ9j#I9F;ph`u>t?&WFt6dEw9gzde)_L9|8 z2oq9ZIg%*3%T34F^ErpT;$az$12F2F?WEk09_GU*bD@}I-x&9TbIS@2%UOYJOFf3A zZ=+_rRT(Ke`rT@>W#%mb_L>^<-3wHZxzfzc^ulq?jd5_5!0wY=`~Y!-YIJS3QkRK& z)&ft|qsoCAp@^UWPcIJ^U;`e#t$1X$eEBG3EQbo0E&W2y1P#$2BG2=8{hC1gZU%Zs z!#9kVXTwe>F6(3{nEjh}g924WmKSaeBy!=%D!-wC(n|nW!`?z)sqpHKDhD{bi@s?_ zLfvX;(Sz1WSNioh>NE$2)#)mjaO8)sx@w)Zhj{w04y1 zv2>3Ttc5T*F7c;aJJ&`vGUVwo>B{iFkGB^bEztKVxZ7VO!ue@sYBYIjp8ov2g!o3O zfoz9#w`#d;+vQl~&hmzHoZNQpKmNw%SF??r-QPRrBKN?`9qj58h$=29}Yy2>`?aMH*Rb9ygUXMV=We3UC*7}f z_NfT!i5Eq8ukh!ke7*b3Ak$4rF{>)b{D08)7Ex_|@xLf7Efi>>6xX!H-Ca_{3l!Iq z0>#~(6n80FG)04_P~0^XFJ7FW#S=6@NMC+;aMyi<`zHVO24|4LS?eTc?Y;B;=#a^} zhp7RT6{StgpaCLYZeN2FDIPci@)2veFg3j?gXeHuM9iTKpXkIjXUX;ox0kHr;Wvy z#On1X8LxNATVJaNJT02_6s{P7&y{OP%PqM(`ctou#+;%pJr(Af#ms0i^;6rxD-~ zj90a<15QRHUd^7*h8?r?pd{ZnmfIg9a!7u>=WI?+@R!Z;m+#9i3(S*SDs2BhJW5eD zCWBt-Qb^F}TDI;}j%YR6L}fD1l!q^00ft`+77-JWs>shjwrTep5Ix{!3Vv7=*QcLH zyx=UrQ4%%Er5jpTPODc~J`-C$+)^MV(^leSP?B!LsGg&iB8=G8&og>cgHNvNSvX8^hGKNtWV1IBjGecG<)Vz0g~I`8D@m z{9_-vYglWIfMZpr*I2*)a37$q|jXGw;WRTBj25yoT4i+iB< zll?CfH=e2u;eEoI8RdCrT%Y1eog|XMK}elEmraGvhV^O&gm?KyrO8K)v7)ZJ-_*+` z+av1!_lK=xqSN}n=3YnVP%=hJ!e;b6`g3`Dzr_#6l_SpD4@iuE7p0k}bKVkhtLCyi z9HMI;8d`N0N9#?!zm48JtAenV$B)};5A3M^R3pn2V>`SXP&ck9ZYer3Tj8CS$Dy~O zjSj~8wJ3*fo>EYce==IGphbMDxKJGXr^}Mg>K9o<*i+x0y@O-OPvFrcGH!joM<5D& zza@6(%xL;(eQNa)y$JUh-%J(ji>-urZ^!Wg0X~ifTQs;Ac`r}{$+wdc%<;!TI=FI! zgr)pXV?9d6i>*kFuO-V{g}WlF&{g-L)X>sY7f#mv88_N+Scjq1dOGi;OE!R%plGj$ zVR{gaFRz?dESkUZzBij08hI0r7-Tvd2qsIm##m;#9S7vNu9`fSx60-AtVE!y-$=@G zL*ERyzue_4E*i5f=V@pK@{hQ+zfIc6U8751UwQ1!A8x&8!S<+?OYx~&c6jN=5yJvU zKxM~!0W^=*jB#fFNyPl`4ZslqU&&iT!&|L`E*|WnN1ACcQU7S0C%Orzb!& z_+=}lOvYEpciq(Uu0BCbo_upt1Z}|REF=x?X^M~TPe#%R_8A1$f4}~rvm0KOW{#cU zk&{HXqB4AR*n4&0L++C>4UNgPT#J>hYTHVgCL7O82W%(wXzW&iJPB>Q zO21n!4BABf!+VSUHYf#)?Q+pv^jQb@Lqh0JBHIBd8k`1MZCBs{wY9gqcVPsxRRz*} zi(*_;EA|wK_GE5t0V3-T2{bfmaBRO@*Dex9g~k~W9;~4>dnWFK(4F%GL>nqc-`p?hUEbPY9=H`eLB_d>*X$WPhUsyOyRulh(sx3GOd@kWm zH2^)~R5fr=;}YB7G0J4nAF1_plVT&LtU&DRq(V8QR8gAgJZ+BKLA<*0j9eX!ROnP7S&oLg8V35+;5hw3zMxOaN@`%blHNn7goo?(t%{vh0AKLk zFMg3{&*UxRx4ua2Hm7B)-h}4C-Tlmx5kFsl7tWAmkfa#r&#v26Gf4&i?Olz>!eq<*Ip z1kZoouQmK*nI-l0hDbhZe}$f4{SmLyY|uQ{+w|KC)ZER|3<~pVoO`kd;U0(|e)hPJ zvTUY+$zGFqBgZA<#&bSM)95Y@?C7Amkkq$WQj@b@`7uCmwq{_o$^Cjqy^>Mr@NQ+9 z(fY}sMMbh&s!LHEK(k`hzQ=~Pv@fH5G;cMhdh}qIKFaj?Bt9S(O}K7}2CSb98p-mY zM*4&fq~$sbTQ!QT0vIX`mA$2x1)sg~5!Z4rnc;=_$N>xGC@zifzIm;1j~GtN)A*_$ zuYHJ=iDsfkeKDfp88$n5E+A|3AU}_1BTx!DnANBk-My7-@MWkd#iAslGTp+}xx=ECYsy_X$U4>dk`-b(P) z4Dar=Ly(3}uNeTy%IZV{%@Hq1AC?`6(8o<(>oqqCcb7TV`S&B&j!qTuS*^9U>DWjj z)&e!Nl1{9=Q{++iJIn4$?iW8Lhb@)f8lVk27^w8 z;V+*@A^cR4K6pGRkxN^&RCVwqkin5p^neqA3u3AR**6g}FgTDrNcD6KC+r~{iVkHs zkpK*sphyEY%--g1Ein2P@r?^Dlo)FmnlckmazqxE&~VK+h-pdK4rXR%oT}H)=3hse z5BOO-gpSzsi=NTXR=(CNiY$dGKKVEf6_YgLu$2KBffT&ED#`Bq#%fZbbreyQm!)$b zHO3i=9(8kDJ=i<}N;1W4bUNMPl8~*A-d<)Q8&eTWKw1!5#q;2#Np2uMge|K*%i_^i zk04jfYka(8MhK|9o{4(?_3!!>_F=JiKN=$E#FD&cg}#66bDNNq?+Eg(hQ|M5vbJo9 zq`d!{kXBA63H*Hw{AROmQEJ?t-0G|2Hy;n#+Y75eSyPI0j_T%-f-Ls2{U_%+pq2Z%9F+3>Y*f#D9Zk3p#R}1 z=ih#aVV2M}y%ws;KK#HP>K?VjQ3i_$=D|pyWssMvP9nWPMvNMY1C*jj;s9MP;9gJ0 zTY)ZoekLi?cE@vJc{y+4C>Yc_iJhnvw;1#`8t1bhPjIWK0Ly-zY8wog z;jMQvpHZnsryxnJ;t%`k-B#L=PbqH04&p5VZgxPVnnQ<(f_eZPz#s}Co1luO(b>yP zpwur4zIveYY3qoCb`jewMTKPAO~Q(yVaCh=wBP1hbSHmPlMDZ%iL#?*qa`HM&V)oW zIjqP0_s#qa=n2@OYl&i$_X8QNZ<`Jh5?Q0#aE8v`aeLkrc>l&?4`qrUtT8*#`%M-m zxGt#!lIUxGA4VQs6xrt*902$I^V^eiQ)t-G^nOLANIK|nRN+0aPD^vOJO+B=zHlZA z$x1~CHrPwkI@67bm?~V`>F2);=ki!^IO5(g;8wlz>Cz}eO~eC9Iv-;Mnq%iiaF5&W zQBL&OU8Q6N{;AQ6SG=_&qLR^R{C?A8p=Gkd&Ij1OR+=Mxgbaur{dP04Kn_#JF)Lbm z)oL(Z%<(DO&;P^w!&68MAXE0TS|GOTC$xv`Pa)um{i-Amc#s!%@cCJs(!OSys-yst zRJO4c&v7LZ5+?{g;8zH)!wz-PcH&_akj3iAj6PLl8(Y2mekVhiqk;ct^t)TJUw&+4 zocalasl4S4^E<=I@*@$F0xt(p4;=}gnV-u3BtNu@hW6E?{AS!J3y$m*)HQ0jMcGrz zhc}6*ahQUsj>GcMDQ|0|H?pN)kkXAY(QQOufD)xT6}!Fj!SnOxn1f=a`ay*{EM1-m zceaWPbW#Q44ypbCSS-yV3OWA0^~*GCz9nV&Yf%Eh(s9=3eoJ3$n2SO{HLl%1Sh+zS zcz7J;CpSo-af}|TCwxwQ*)lhtA~)XuPTFS!mZtb3Hq@Zt9QHRKfXFS1+%xF=-0W-q zW40DxSgAqLKYr1XF70$?!9C})94=49Y1eQ(F?)Jgc`t3cQc0lT!e7t+)~GLE4*+C+ z;l2HHJ3eT(aJnOzh^8?@2Byup-{%0<9 zD94#BC1Hud^tgvIE$gJQ1Jqpn&$y} z{tOxeI+X!k9N}!#I7;PzlX=SdG5=S_?tkPlr0p&Hhldk=Sc(0ACLD1byKI(Y+5X{` zu8V7ZTMjT7#c2j%-A_1hLmUYjsMi+ov`%zxKAD^DQ*Ncw&cKe+^R?P6KYdz`L=VvW zp}d5!utYNG%PtWdg6OhXv;jEWrirMLr6@A8A1YU!9MAl^+WsoM?FHn}IVov{^X78P zV8kthhpWdGYZ*O`obKOFjD;o?5jz+5Yqi-YD8CL+da#t)ym5Rb8-GF*+WU!nLhuS% zE$^=tzN>8#$jv{YP`K^9*QH61yuv(ti%@|5Il}RybZF=Y;=rK-l9FpjKU#fp&OdNM zKDf~D*sCtwfYu|`Wh3y=VUaMFufE*G5IH-nX&&VNyEySzos;!xo&liaQ}CYaSM9PH?YZAYPyWa!uJx-{%$$j!?FCPbnn4>HE?70&(m~glxQ_ft3vR+AaNxD|r zUIXnwfJ6+GwSX+bLs7fl#rpy%P|(i^P}j+)h^%dnBJP}vutRL+Nv&|X8!1k<`m5e{ z{>4Vfn%n{d8PKVcGwq;tCzAdvx9-LfFK>>Nx6hK_y+*XecyBp#Fa^vfV_x5-0d0wmVHPM=gHiokExQu$C0l%~qF;YemGpPp;Ir9Bc%U5f4oe`Tja^xklG!sQ&($+;$ zli#)Y^JBV5PAYFzU@zW-2%hG6Qb}2Z>cIDPEi)LBjU1krLtf&kevHn|%hg*Mte+WO zAHikr9Im9cul0dE<=JZ7y;}V{w8ZG<+3N5T6AIPJz8Pl`FBUl|7wW2PZ@bmOJ@-B< zG^ULrzB&W3ZvfLM0j*9(HC)CDWa_2{c3e0wU1F`kpC<16vTSf1@l^aZ<3e`Efs6Kf zH}>b|TpH>>mmnKXX9H0S-%bO(#(8e;LoT)M^7tH&T{L^aPc}HW+HB`|a@%9bhkDxJ z-j8#Wh2lL1z%r0Jn*jl04AEVIK;YX=1NxwF2?=AT^Ii9Px1g39*`8bs;&S(S{O3)v zTanEqG#AYw&NW@Fa!y8N?2w(7aYgOjD1O9$&5^yl{sh(E#p#&Z>?yy1QR*F?4*k*2 zm(?ecZ=^{PHTsjdt{}dz*RY&BcuFgSNjFI@w<=9PL}M^x_Mkl zV!vu^zfN2(SC6VLg0^~IFK8~0RB4^;2#Tzk*a*4Hmz7y}n0QjP%9i>Ld4AcuY^THO zT`lDswYbwl)WQxBwO>j@&CLIW#AE5vfDNa-a5F*VQBpMTA;+yL^#h9&#l?307zNP)I_e;mEyMPC$vjy_ zYTCs#Wc?muclgI-guHTZUI`7ekKA0zsr z)JOByZy(JNrYf>E&f6FZ!-j_R-XA zKqCM2u3{e`H0nbP<^DoG*b&Sa=zyL_wOvAIFfv{n0#W*~fWmE2M5(boT!hXc=3cVt zI5?=&8=sc;xn)OyL3kht2cQ$+*0ziyaxknOOk*pp5mk5>3-~c;8}eMOg@a9DMA=t~ z^|SYGb+Ahs%2`1vLFDi>ip<)mR6V|XI^QUw$8}cajaa($hjjM+p5K>&sN62EM`GS5 zvzQuu;0PCr+9}7p&bTF#j3OkiXR;(z3(}-2lrA-0PNTpx(6s{RA3wg5U%x8~CR4f< zM2Bgk2ayzr;(Sq5SG-GTj~!FHi{1LIR}5KJvB08J?(=~6fO>|=vC(Ec@}^w?dGHdr zQ6sa^-Y574+5o!ODodSW@^SyH&$ZJw>%MFqoq%CXy49a|S`mxikA4kZ-O*r)xe%I> zkVhDSBHtZ#%fU5T(Msz$<4GGeimJO><}HeHo;Vptt0K_3&A9j{itKCYGlM(a>ErUz zL@)aZ^+|WvV@LYU4<@Wl%AVzxiTCf<0_~a|#$e-&J= zZYq}C$kMmrMKQhWwI16E_E|OZl-57cnVJw~-B7dvPpixsqc?#Y&g;V4oBlp#teL;_f5*}DK z=<9dvN>}}ScL3}#DGBFnENW?NY85{MyL=u^QDpGTQ(H-lOBOCFxPhc082iXWhRu1W zqnnE&k-k;lHjlk|9!8dAS?NN!-5iA#@ z2jU}p8{FewcR7Z+l$pXBx#{*^R>Vn0DsTto`g)r^fy0wqY2TL^Dnqqn{K!8GXf z4G?j4Mq8obo1v0F_GD!vo-QwpiZizPN8Oa@_*ghH4#mTM-TEVTn(DtM6ZjTc4E9rif%@#? zdG9p~9ei%f`(*sp$y8Ulq_z>0Y1|Wj#_EvDObcB~AwS2=pOw=2N2YJW)TgQ?nEl=q zrfNGKP=CFR)ZuOQmDJ>gy(QQ1T(*iXB30ZehpW%1vmJ?PT&nQ!I0rKadgDg9%s>^i zCCUa4B0Y!P+mp_vr>>eK0hisC?0l7t^P_y1Y*s-#GE6AsyaquGxP8U_El}>VQ-~g; z=_YYVDCSY;OIQe3eD!JAaz-y&CM}UvWI7w2d0F07JKf~~X11rf5;j^iLJt!iAG7KS z#(9N~xDTDS9E5dG;P2r?J%GFRuFnok0F5dBb}dAIelL8HVdhqZ=2@{agPV9b_4x8> zaJ#{}l!iynt`Ur45{`~aKtyD2)*3#hNM~g8=T4wMCeKb}dAgYM(>(?HFyM5+4m|S1 zsL?!ORD_;l&T1U@xk_lA9d)c@D>5v@gfuH%Vsx-$+To z-{kY7x!zPPj;vtse+%w4Drg>mbj$kA^CXUK^9)vY(-zx>QF<>^cVNP9w?|bKRB{7T z^UqK1(^EyE2&zCT(vp59jqMFW?qLgioDDD!gaq!Lq+zE@{xo%S5=^NnxjRLUT1~Q}wmwJWJI8$i@ zl*pt!RNFh^?KOmU;_amPK^tsAsi?D2Z@Ub{kLg}i`up*c_g2L2(8v75X?r~l99rYK z6+_VB@AKU1s(a8PY<)XhmDxcs^<+cax33&Cyq{XrUyRQRm%Ajla5z62SCL=g*_v4L z*w7p}iR^lk6pN)YDaPU9XTU~hOC$B2JiF~7Y&vnlBe4JP!86{h)nJ`34*P47e{d%b zVXyCBPnj1Ncfaq50GZn#Nvm;U)!DQg>vEfi+YyV0*+i*<&Mglrxg@2mCNzQ$Zh#bF zmDyIz!xd+i+}F?JGieSbqh4frDe+FNUxr-->)BgO2$Gk4C zBAf4U^9dD|5KI;L7r5OK}NBuWSI8 z$TrR5a54bGg%x{gcMyM8;nGsgrLwUKQ&{rATk~yKAIHU4x3icLc>9ZF*JC|;+R+o5 z{#?s!i)+Evd2(qaluIShlgnh`v`10=N@w!)cx|&?DS2Jk^r3MBs(ExN+F?vJVZWk* zg?-ayg|lqHeSx*G{wmw?uKzBi9ha!Ic^QN(ZL2ZfoAcPGR^i20SJ9R#GGcyu$TqZc zTSq!Yh+isoGcqWV*$Rqi$W!g7@Ph6&Nod8B2{B9lA&g2W31PXjHgG;Xi#`5g(6r0>jS;gt_M`mw-btmT9NDcTKB%KD*Y=pYu`oS2E!ACjX*x);! z8_nmv8^~Odv`;^YQ!8Uj(j+(Ri_4ogIxTo)5o%%dSskwC*r(6u-x}|pHo26e!!B#@3fsBXyBOB`O7`h%x_r!OqR(w-Uq!QB{_%2i zqYNpzmY{HQe$8w~`ziJcz=ZAXY7jU?1$p=&Yp!(W>j6?Vd?&JUJaKUjRt#4wCF`Lq z;ujoG;X@5+>&!~wI2hy9m-+rbyxN`Je|QR@`HOt>M1;6<`O9nj5>~wOYlE`E_cs?* z7!=}!U>g&ZYLk`kkq9^c*=lrQDo-ZSt9OHo{sNk-*5eGS-^*3F^wMD3&(d00yjM=H z!Hva>q8xVoX!Y0wnG;zx$htNS<~Zx@#qu?eQbL3m_Q z;QRGUe4xR_Y}+}SD}p(moCK^v?lYA~cZ*UhjpA}-jJt{8fT6mZK%Qj%J{!98pN#T! z6%DAC&~tlMoZ1eR^0yo6Lmy5@vZ<99jBdN|lq zV(llCi(9KNFq*jSw#_(Np3j$9a)a3jX_WTx-W%xU>`l-r9vu_#=He#j_5rWu3HJ*b$blX*J?0`a|>FUBAKD2<;8o!^wPRbA5Ip}#7n%q z!iURy5i$gc~Dr2ap4JcY`b5+&Vc!5#t46SJ`Wjv1y3ePlvcT_?0B}bkV<{E*Gh39 zfl_ogzvRaIKo?#dL(Rdqz;u&Uk?Qs4pL<6qX7gdct8;eR%?Hj3_ufhmmYMI` zDH!xB3{+(Lq@-^$xDP1>iOaOhT{bVfJl&nvgnVljOm%`Z_)3Z$8s!bm{ z6}akHX>sny6Nmla{1pAEg9@i16_boHl?L46!+%RWs9Zb!3{AP)(dhX#6>Oa;MD1~uVb+&F5Z z%=n}qm*>OL045kl+O}5jO=DiYIP3g1S# z=J|gM1o9En`b&7!-2EB;P@1CEX0WZ|(lEgz+~3DJBGjBKM<|`fsj6E*+t>n?XidmV z^*5?yIrF*PzpHA{Oj;FA!fpH^FxH&jIS;YWpy(l1>8e%Fn9H*19iRAFmAkYa-autRIg zZysOt508kJzCw*Wq=4tOH06HF(71}ktV}gu)9&>XUSZ zViK&mEhg@I{CQQlz|^`{8{7=h^R~;f?SyvF2bF~AOxi_dd`XQM-7rh|A*z>-+DWv}gu^XfCorV7$l*4m}!=k_dC;*H`m%tZ&|dn6;|VhK#5ML7hMK zReljVF_q{OKy&;TF@y?uKPPKbzO?k$?VE{K6PQ9l+QGkJD=}tEv5|blmV`y*ey_+W z^Q&$kAZIsa&@JKpX0Jb8J`07B+QBg;m26|%R;rhjy^e-XW0OY%cI?O{e{e^5aCgmY z-KEB&vNybJvm^?7_@g_}wTP)Z6Gz%H#(JzMM@(QauBsI@8G zqmHk3v9w~zB(D&SZ__LGGs;>xHRR$60sYoI{CdlabJBSt6%umYj8}Z?cBVK`4>6DW zj%cbQElL%Xt}P9<>MDqIdc-j2IOD6xt$Ofar`&y(m*in{!LIx1%u&oftk_IrL`2gWwOvuOg` zv~oY)n}+r+-$tDBUI`7lEu+pQM*!MRn`hj@`GAM2!r`-wvIXjIa-|S-4r6?LV-7{I z2oGKe58R+1#aK(}(Ke)RQQpGYGfuX#{nK85iqCP)A^4`Z=cZh}gA(b#l`pxTKX`Bg zsghft_QL;6Y{r+nv(UBog8-4`jL13TR9JlJ!<8bx zx>F;=hhvNEZjYfXFxeZ}5&5l}ujZxOZ>NhBO1sv)i|T`K1r9>a6yv&IFJ;tOKUuMG z#FHw?DS(xKZg`qfa^fZpV;atMZV9PY_*;`m^7jtAM=2F7eoK!XgoOx^Ce zQLas{K|*alSzQ*M56(i9JS`rq*Qs`uox2-0@U>^y)lLCaO$Wk39SA=%r&_-Bcl$jz z#8<-`b4LE%A&M+f`3|A2noJtygz-&<-182F5eH18u%38@-_V(R=PJ ziSt@d^2!Ex6d1|>@*Gb@;~+3y2#s&&&ZY{QYyCl8ORIO9gG9!Zng@^IX}X1jNmeS* zGagRxdiQ$`1!4etlwRYz`yRcClIiz{_Gv!+t20pk@qk(Ebh^)nx(cgvEwtyl3W_k7 zpCuz^OmvlkZC6e3BQ&0mW-t)tp)zUbt7E-tUC6Y_N07}bLt@%_vd;8|_V!$5HM#}I zYu;PI5(#~uvuY$35ND6Ci&1Cp@#O}Wg*CC2!Gb0CmjiYs8q?BH!B-Iok} z(O;+W1b#6Y0Dw(=I(J$%Z9|hBCzpbFipEh>o1Nh~gc%p**fi1KA-&rNe%jCsJ2zrI zdzfE`VjGw#c{P?=?3N@z_t0o|d|vW6(EjZ5<6Sk-I!6!vwMa>P$=jryFp6I1VMF_8 z{-yBT?HDhHSi*-UNiMUXCCEC*5gE$tR(TFf?^`G*pp0A!s#{X4OW78%ZY7eAi(De< z{VX)GeI)##`$4H65Yj4fDt-M-=j@?Rx41b-FB0147R4Uxu>Q_b=6;*m0Um7tuppM*zy$fQkBwCG8c zzkZ$@0!Us`LGqDVxyZyJE(J5-$hWv&5pHe1+Zr^tD>?CZ#y#o;?)R(bAKrz*xe}q} z52Glu?Ta5$iyOq`#pS$%3l$nr_E?c9Uiw^1XB*Da6ORkoDDtTO?QeF!2PPP7IcKZ2 zjEl+&A)PH2EiH^Lwy@5vb1j7+sl$hqTCe`67XX)_fHaQ0kK^0`J~+wYufR5I6R0;48YQ-rzL=#)a^h_UB60yqC>_-DfnSbw@p z=Tt-BAKur{^ShV+O#gq|?EX*Fox{!U(a03wMiuA*X3|6n+)3cMswR_?!Aw0)OqF6V z_y`~!iTQ>T^@ss=Qs>%{>vg_S-(!9ayuPL#kB?*Q+ZW7vN+8wPv~vCM@;RtRwNx=w zql!iQnaTpoDLgcGhzx0fPy<3BUiXO4q;37ULvVP` zewi~ORykYht*kqbBI?T{D=({0D@fgBx5UyKXS1BhAYm?|3H9RLf}V@3YAH+4!}_>T z{ps|-net^nB(oHuAa3ydGk;Tw9=pHZZPOkcU{TV5F~C@G8G z@L?R@IF<=(4i%;>+pOJbuUWzp5g~&m?oQ${HZQLW-w@lPf8BHYYC-hC>tvqM;NVJY z~kuWDZ6nm-KZ)f!ScRl?s{=5Cdm5ZFB7J|H?*DTHXVBo5R(IIVMXCyfle$heyB*nmEPme zwO6sfPT8)c<89;Z`?Q+~kZKIpupzlj>FvEBGe9 zU7nehT;<8%8fK1?(hD`9zBFsib-kC>MDF%rMSrGD3>Z^Gss~@D6I`w^4m3YqHBygi zfp7R{pFfS&P^QC639z{d4Q_!}@Z(KLuHTmqW&wiy5eO{hP_xIq=wVk%d%YfuTFG1| zW5YHJt^iZrnFIK&ZcDm~$`D*-Q2m*{+T$*^?(IL)&D@^lnq;yq-#&tVjnj%z zS4j-qVbiQW-IX?<#%Z6CKKk`bKHO_uMmq}cUcSoE*5}~hcDEC=IRJjh2=ag|w9otp ztrzR2CXr(LBgG=0oexkdQiuBEO`pbAwza5U1Tt-wmyh0)3(8)g&IvzNk)!dNhyB7x zqHz%80|YMq3};mfW<~Y4u6)C>=Fu0k?K7SUDJhnx-}V2jSvwQ=SB@5?_`5nBJQ2VW zL2iW;q{V02pI9&EA_p&rX4E>ib@jZZAdKmNz9r~``M;$E8?T>xB;)Po#_x8@TDJ& zwbL_leSJ$FJs?ZP-5@05#r*~7cbwlyrCi*%2;Z{gkY6=0N?3vlbO-zrv$(;`3$O(F> zh@%#7tgobjvxzugVuAQa9PV4+bxm-=QNkgUk0PTt^GxYO=M9Pw9y$J{Ys5>N_MG5wEZU0fFRZX{1m!Q|Pd6R3y z?PDL9u#_$2WScb>iy#H`!*lgP;eC?22^#j4mKr*4i_cuQow*u#WGV+tw=opOAO8l^ zOx8PGzjR2D?FN%sH7rXLVgOp!5lbxRD zZtXrll>=t@(r(UGefFG?Prnki(g%WG&Ic{?*l81X4*B7Sg;Z7irzX z%ynLhz1@a?RCI%bb&igePD_{#Z~H&{>_VHG=DQG}7iuAr&XEBI(afZJKZzUCeN~-x zBIskuqr^BTvU%R+Pni7di+a4RHO7(C%3UL@ts$HX6;m%8FcwV9mHsGiR&Dx#o8aCc zgCZWDz$d~c=irv^DuHLVN0on#OH&yONQ-R5N-wbwvs6=>3BTsX#|Np@M6uK;%DHnT z>N*YW`;&Irz&)gEg+O$l82v?tNjBE!Kv7XbJQ#EHAs zvqq59o6M=G*Rn2r$Vstsu{Z|;1!j+5bp^57yq4Fm@{&fL%aR5F=4lH*#bmw+{o=jp z{Hh187@*UkB5TPS&n&rjeYQ5A&#n8sj0O4+4+NQOmuE(_7e%_Fu*-MV5ZLH=th#foI^-~>s#J64vAX>4HD*@Z=KPfj^i6GDtYRXu$8x`UIol* zjoc?dqVRtkFe z7fX^BRFrna&m5_+R)lhw`5ipx0)t&YqxzDVFrp1;6~CglHi&BWvFdXzYiMwebpL)6 z^#O~h`S`Vjvp#55oJMkR@+eeXWc}7-5XyqVGD?utSNZZ2>OlG-p!w++wc5v#x1Cxw2%?G{8RZ(T1OiI6dSQOyh#*(W))#gv(RO zQKYOIAGF!`r)eqp4}KaV*fLKMmQF1CYML1<%Yc}mHykWQ=e%#6J+#cTp`KSZV|n?Q zi<{Pl$^Qax-dy25g7|A1_ZyM$6z%wp66(}eb=f+jS>MiB9JpvoW!AmASgN?w)QqYc*yv;BOLxFyas@28*gr z?CWjqE1}Xb#r;W^CsMpL0q0GgW%fcAEZ3nuZI7yDsn$EV;X8tD7aU(k{6skEZ0tUN z74E<9{eAZo_Fa2UGPLToMS_8CtmwP96WSGcDv4^mw>IN2WCt2<4)y+P)%xpvH2l(6h+Tv7IRv#g$!9fx+j)S?}H zb&6X|+{La2Ho7?~9N#1y#V$kirp{e`Sa+jzrMhZL$6ul-9aeJ#fq?2H4uK`Xqy9Aw z}~2z?JU)b98_ zT8KVaH3dg1$FS`3q_fqD|9tE&bP)ZP99J;7FM} zSNuDidey>O!lkNu)HbYPym+a?-Dl$s1`9@HS&#gW~iYGbwI6R8BPr_FTV4q)Qy84C89G5@*^odj?b_#ulj3tZfB58k( zytbmAbf8xJZThl8?#Bv4Wp72KP(!loWYYs2eE}{_sgQbZb3K>za>O#MI&-ocQTC~F z($GK6^}Ha;y|CIgz*v5~POV6fGJUCIp~@ZeSnxs+C#B*t-(Snm<8~_vI60Zt$_}o^}rjZU`-X#Ms+hjjw2$o^z@ul;=A8d(lqGd=Lf z7C7@+ceN|~4B(~E1(v;c?TNv1G)ESpxU(OXt)GW7l%^ zY43J6qJdtOg+MKTyV~j z=E8sC7@6%;v^m5cI@v%CYtzp8VdO!}WAO?3xL7bKV6*1No!2 za&ZDUmWO*cEWb~>=2pR-z?69yb{Hplj?6_8LLn1(D391vlK*>k?%V%eoeKdUjCc(G z!i5dEWd6hZqYj+GhFRWL;`F==_KQK2=zn-3dMy9%&!Bmrvsv;#yeX~Q9P~ao2E5ly zc@iH=Bjk7{p3rIS?IpF+lxry`1`kgFBcR4B?b4Z!e=zUBq(QRK+29pV6cOc6X>R6V4hn>OFW#kyR0IGdDQ61VYQ z|7D)?j+6#{zDZp6pjO^|QxdO}oTXJ!-9eF0Ll%w{ds2HVNDyn%6`QTz$@u!=5_?hY zA1=!u{P}}5o1s~fQiEW7GzjH`m>-B!TTw#n##UkU?94eskN0Ox6Mi}>^%1Q%VTqou z^ZOCjiXoK_laNJyAaV>(mG8ucL7%`qBfD(u_nV}!`j}Ou*`B|h;d&yUg8Hq}Zd1B! z->;N+3)~*bukIcp3BO>OF~EkILNvI*&R?#2rJ8rk@O7br=l!eoP`XOwPHHKzOHm@Nu6u3`Y6btCDr1m<7c}K zAu8bsbN1A9uFdOZzl>zP%2w6WhU-lS@pEsR5l}tgNPPnmu}0a>fgHr)uA~iX4EtEt z=^X9YB_)R!TroCnO>q8`BYBIm!t!IR|$`Dt>`?y({V@#6uJ{A<+ z_&GjkRVeYghU790g&QD)8H1PB2rzFq_FmLZQ}k3bm`RU4Z!)jkPoZOf8Y9^D+KjSM zd6VkA`K|?Qy^a#V@G{(E%)t7kJ6^C*`X<9IUC@~{EAZoRCzjm#u)6~3h5P^|z#!R6 z?D*I|!u8yWv97`BcLoE0xt})tcmIH*7>5_zqcJJb?D$ehgc_X=MbcQg!9*~wop4oo zvTvrB3(lQ41vZ9iLj<^8Rs!d>I#O&;873A7FLPq&{G7Y2G->{r3^K^hr~JdSx%Ngu zN2lZ`p2|4U_AC~kO?AfNaslPXP`AC=<83|#d8B9WX2-vJ*o)LJSGVhA#EYJZ|7Ioj z5%-DzqWHvBI_hZXR$_IpHBB5V_p^oU;cTP+$OmGh>W}Hj8~>D?XtS&p_(Q)a3((kCh9cg;H42nyF65{;Bv2NyIu@t#3g^kpE0PKKn>Rz)M_e%`YSFGO z8DwIW3F=nK)e7sbGY;k~^nPB9(r&r?8dILzI$2^|?qvgB>khN!*`K&Rj6uJB$gHn6 zSdL&?(DqhRv;PT;=Lc9Kthpwe$O?C%pXmI zkT?QK#00vaj6wAB3Ewfo$fj6O-zo!&N8Ob{>vQdoRz4BYl!AEC=`x`=QJ!yqNF4#4 zG!wl659Nsuq}#7*-&Q!puzxLlcigLT!eJWD4Uv~ENv|s=70SLIK|p)SBI#WYLl9sp z-+9CU4!|(md=I9yUjx1D9> zuQgz0q%O}oTE0;vrPZgM$xc*S4%cz{qg!b-9h-JSQf4;4Kk7j|A(BBts4u#isg+?F zo3|Kmy;#ql*?w-<=3(bt4p9xX!0C}h5GVgd@B;e4biM}tW^B{v6Gse8(rT53&RWjT z&({`ALluCamhqaWCh4-!Yk^`V_ zwiB-TCu)Pzqu}K#3AnD%leOv<8hUTsDDkK=KKBLpO_ zh%^D|AU%jw>AeP!E;Uj@hfsrb>Cy?INRtEvL-p+cne)77-gD-hc|W}8!|qIGH<|2g zHoyD6?yK-7ikigpdtg$L`Xv&dVI)!|(L4P{m4@DRX1)ftoN)7}SJIs`tg25;W>KnC zva{1J_xGn8T{enh&J8(kc?Kn1%-8C>u#@MPnDMWe=6p=?{-vv?5tiQxwq9F1MJK5= z;Sj+7_xYXd66@CfU6#Ngc+1fNMo`)|)za!jotmK}S)z8{zUyby^XwKpC>==tjUzlD z-SIp^y)KtBiv1py;B({T=3*(l2ft(TP81(|{_p|)m_2+TmyiBX-|JDob3jPbQP!X~-CT*Uf@($=iekvzyqAs3ajibUroo^j2; z?UKu}R{M^3MRMkN;aFOcpMh1SkM-miB^#UUQ@Tn`WT*b*bA7W(-Xr0gjf?0$8R;9E;THt+p1z*>PKu-u5Tb=-b*_Z3_ zXIEkOlTZhAU7Z=(yfrpcqGH2brG^C1EuRUOSe!oAQ5iS9VJU_*DzM0ppzKYIM&TK=Y>o8I489jnk~i_&_+_wGzCQCR;oc1p!9E%DJey0KE|}i)XyF`_plGiIQ+8|6HFpsXO_`DxOe_ zhiH1Pv6&Cb@<@r(S9UV#qK8$nMOLL{4E+bLNZk@1^+6uf>SwmodvKR^7Kl8KZ=){i zGO_C`atS^_PLWV-(tV?^-z}FbC#SY7a^VV3wD$q?1@~_ghTY@@dt$4Rm)!8p^;ifk z8_kTbv8!s|B~!Byf6em*j+Sh7UylO2Pcr2f_8!5#;j@&ZLUjJ1UO;3xkngg2zvIfT z-?LI2R*4H-Hq^{a>c`@D@c+rMCc6N+aoL=Qi&09Pti&r~CO0P<(c_MTNt=bs%z$qO zW;e=Lr{R~eXRkB3nV))X`G)tNHJL@l;`O3K(rzO6&EkdUCkRQf%V*PDwsPL2IS)f# zqq2;tQE!%115eRN85swex%XC*86?u*y)+^!gOsI%tjv;koI%6gpsL20kigQfN@7sq zSq~gkLY3?LEGQ;Jwjs9o_uR6U_UrcUii5&W2QICZvUB;vuoAxaifKdi!TL(r8cX5iB!rHh239TF{_JhHEnFJP#Cj77k*71E@EMQW{J_IxclhM z=0YqxviU=HS7(dn6li4Zvp6zDc2JFD^zM^S+6oqS3)3D8^hQGqm0=TW_+Buhnug1t z!4O?5i&&!slH}2P!z}oYw#BqHS@DeXP)Ew{*}?2(okAwLw``$Nb;N)kj7O(nKf}z& zS=+f$OTAl}o>ApEr`gv^?50G#@P4ozX16aPR149RhYU1d`DM2GMbhO%3q4hBd^~Hm zhHX>k`vCtX#T80gv^6=xMN3n679fzeV7K$jGf?Z{Y&>a7F`3zul}iaiYTb#-_@)Np zsr#A_Hbq*!WOGjomz1v1Ly_?|X@^Y=Ix!orvpSJ1O zmvPf2rKrQ(aW;P3{u*;PW;QdE-1o~bw#PqHS9m%;Ej^Ai|Ex|iaYDJyGi0s!JGAjQ zp;H|_8OcVDHBNW~|Au8ll#nZ(%+n?FQ+3T^k)WHpn98V0}IW0Cp7^u*Xa2f3)i%v-juE1X#AI(C5SX zwN`LH;O%L?NgowVP%1Sb#A@xoY}i%b(khi@WpqYUHJX$Yv-%K&SV@(P5-Jr;j=X-W7^u>l%Uqd+O_Snxcu8=t|F$0WTuQ) zG&k;CuSHxxHq~vsG}n0vk=YOUB_B)vujIGV5d45BW#ReY=_=y^z{da94RRN7QpUe1 z#6J-$WXNB`%}5mP0q}+dTD@WD`l{j#RuA5Q-}_HIEaOnT_SC_D6x?>X{~Zr2{GWJO zM7}&AlSx_nhX>OH)Fff2`w(@Y{5=u2DlC3ReRboydxgFb2n6W3rK;EB#~+`)x(*7? zl7T(y{)cBCaErUuv_f3*INmaO_plX&g8vRq@NG%k-fECl-1hSX_}zA%%Lgajs$7KL zM9kdi{E`H>Bdl*SYb#dSlTIb{#4_}67j8%X8}9fY@P^L+mH!G@CGFd7w2c1nl}{|Z zt+&nwWAwUQEWcqK`>HLeZ;#cPDhAYimiLbAPZn#-rrm#*P-QML@Zk}zEgu)owEg^w z1}m<^aE{Axhyi4+J)K6yq0#d?sfUGs#=`0hE8gM{PoF z!_F4f`J$F2SS))wLk(HSn;^?zqCUY}7GV2LpjRW;TQ5U`JJrZp^e3h@w`OlE8?NC- zTgEH$1wB4F$@@*ud40v`OpY9Ueh)LzW?#0p&;Ib*+mlz-J%pqw*H!>No6IxDf6g_h z)N!XZ)ys=g&jo?ep}glG%a=#_;93tCy~#(qTJ2f3rdgKvipR);*YxE8eq68jk6jqJ zs9ozzOA|b7f!Wn{E;-)y-GrU5!qX6b&n}a>tlQ3^R)OG z+jaip_xnrA%p%ZcHKaeQO9g4)g@Trht=9?#J9$i*lEJ$Xv4O$Y#q-}vtKR~ZOUO2< zLUY%6<#zU~=6vUDCf@kU=}KN;^%#~QbdvSuV$fK92#nXjyl&uk0QEcAXhCNnE_Bfr z36W`^ev_qCKmRsKJ$Gf4c6rPUEM*z-@r8O)vL7cI_7F^G()9V(v(Mb>_uRFR|sfeW^~j>^FFwm6LEV4is{m;DQm9)4XxiG;h6D3_Pa2 zp2v>fHoPZO|F%WO=FidXtaD$-Ug~HYeIY&|+?{Lt8c7E0&ygzxtT8 zc7H#l`n0p5T$Fu(%|3C+-ekY+)&YYX6CG(l)J~KIEAZ9`#izSyG(>QO$D^hDCh}6< ze&}6T$PMTv>L*smk78-P$^7aE^)rpb^zwz*qcv!&$IMfwo{LUnFR0b87Yw5PFYc5qy}^-gF%fr zdNgIEez4L!WWheDwk|z41bJYZJXUYAztBhq&m)M5!Y>fB1Ro-*oSNC$z|K!40{C^) zhPeHi_BG!Xd)_2{skoGaj*N$Q$c6nvw!xN|_WO3vM{fY4K!er%iIWhkpgaHeCv!V} z_L#)Q>vGOeqC5KK>-wn$9PD9bpC*MbqKwH@F9%h5YNG;-OyynW1wj&~AGpP_;%F5( z=iY>X3Kf((VFg;gnU1o`V3NkY6&8V!&CB>W+5DR7m(nN~^mzz%8hM>+Xt1@lRf@LD z_yGa~3(M1oI1+w7V<*k4=J9>IZZosu?A?<>!$e0_rDDFbqUc5*Bz462 zei%c#DgN3ubCaFC(}mg&ndt6XSmFgS0(Fbwj7L2<=YbHt(StbVoXuHd)N;Fp(v|%u z@6891#hwXu9N!5mUWeSYKoy^y9sj7?yyptp@80Gkpl%z zf;_-U4YEDPsx-Gxl-Qr=t607;f@@ZmimmXun`{&J7~=6J;t@j8)ACwZwAYj~f5&7(abp#eJAW zPaaW+|&d)D{kG{Ux!OsgH|EieW;aQkmnA;&1n>^8~`DCQHfRaAvN0dS&zlCisGJ&if zb;|p@D_}(QYlVhLg|>S2T;;=Uh6;(r9D_b|!>bNLnHQAC>{m4Os2Kj{UwCen!Ikarjn*1t^SJ+8W#(=4hq2(Y-?ooEp0nTwe3$U- zRK5~Z6eql;LL_|STY>D&?$uJs5fg6b59{m`9fVtZUdzyfo3al3-Ik(iDUvor+~)U} zfV2eoH&cLUT37L|4m4g9L7mL+ZvsX2E~#_Azb4JwwC)d$SbJ2DTP=OpLcdP=Hs9y? zK}f;+7}l^x&OY|{uW`5^Vu|dLDs8KR#&z4U`~4&e<4h2#RVN1gdT^t@tzzLR=8S3f zseS~jI;x&!F-ie8-zFeg$?KDw?#kT46VP;GVE}cWg=OjW8hj}`i_K9cRuU9^?U~Mz zCkveq&($C|QajHXg>B;zi*(%`#fbn9Z|Svt`LLv1w;lz!((ZV4;!TWQZu79}t3787R#nDJNSIR}pzx zz)t^ZNP^W|TO>{MO0o zk3!%5)^r*_!VvEp_!DPPsl17W+a1hRYvjcrydaNEAVR$xd98Qu2Hn=q(|;z9AdV?Y zX)1~KzwRAr=Gy$FLqh^DrJ2O><8@V6g9utUXgJ=h_iLhqUg31zfMqu%J(8lX3LTYz#LU!RlD%Au|GI zS3bi|O*MH?c@>^v%9913z4$jHt2W-J1+6vK)W1UM;0e?s*uu@iXt1L@o+hJa(tVtU zv6i;x?tHQ!8F>Hys%ixU;8bJ#dX)MbGXqee-hXw9zvmATw#gY(0(m1G3?pkd1F@TvrVQgMyuo> z=pW>AxSXKl^!>e~M?;TG+GQa32PR$ILIyj{>K8HlZVmPI!t>qit-V*-{}ME_y#8sfI15h)wn`iz>i zA1^yuXrjcHX3k?vcQW>EPKj)sBrh!V3^-(^w0IM>UnlJd4btP(D{#w$2%1~@YZvwh zXJCA*uJy?7he!u4g~h%>mOzI1y-aSS`}-~pCjqpU7n<3b34E;^@cJD^`L+)&f6-+HM!E8@o$F z(Uc^?0OS=O%v0xY{=D$Y1zEtt?xyp=C2iw|r)W?9Xp7vsl_?Giv?=O%$;sq z=~|Wwks*}#D>ijPqnb72=&`EJ67$c11{b{a3vI9XBjP5#%6=pcMv~HBX|S#T8j%tQ z6+Lv2%zRQ{{Zy}-OOwE=n~z3c(pSd*$de&Ay+nUWdAN^P;n0?$%#j)9g}b z`?$xkk z=XlW(RS=WnOwT4i`(7g9Es^yYmFcqC{(O8IEazl%S4T;45;Q}59v$c2%@BO|rm#>0 z%QXkPeiCRLnGkPg*1es{NPJI#+G6-=(6E^@+1MiJ3j=#HqP`A>N{U)%74Hh#gyeY_ z`+E<}pS-{nzQ!`Om*@LRTsLz3Y~nv)dy{x$Xs6#_zjU8GFj$evRNE7@~E#(?n@phl0Wb9G2C`oAfwP( zsLl8HKYR=xMWlhPMm&#!p5FO<6#U8N(zb^|Np0yi-I0sbrE7YCp=6c5&j#9vLXcuA z-oVGNE91z8tE6#F<7sjG{r}x~#{cT)L4dv#fOO}tNwz}2Z+b4XWBKRX3r}Zaal0tk z^{4lUgH>>C$Y)^I@b$GFjxp_T=UeZ@pVMcBr#CKWSX4NZR`Q<~ZA3z-tbYc;=70k| z-ooCEwbd%+ur%UUg>>f6=U-Xt2kzi?_F)&GrnKk(hu5#|PcSpNdy8uu47m3{Nn7!u|g0sJD-NoG!KSJ$Mgh#A( z!M<3o-Sa==?*uwUY`jR7QP|xVf(K+Ok-$@N_xlO^y7c6$kTT@}+%=3iN0X4>Qo`^} zz3$+2M5>3c4RN3RaD{&zB*=Y|NNx`%5iG#}3UgWo?FNRjNLjB5BXyo?=QN9vahJcc zJft;|%}`05t2R8_y&=|+amzPqk3tKa-oX_4R75(0>*L-2I*aIPT{OW@WvN@xxJa*V z;c3u<539LaOUBXmY>h)xBI@}QPzFi#n-?BHxKu%)vFa|R?gf+l__gN{UxD1Da- zN)m#zdh4}z<(|6ck9`{?(D)t5(>#i|0u}D%D1h&U-VL>tvkn6@bQ%K9JcU*15?c$Z zzszir*v)?St!=2g2ek^6q1!N~W%ThbTY2|waknvf&$U!Ufw5kcHG{zjpGr3!WV)uX zDV}Gh6DC=Q*5ckwGY+n`z*c(v*3pnQ_~z+OY$bsL$wH^04&~`x2f&|BtoEwT1fTm) zzJ5R>J{e;rhrK>I_~8#Ta($uJ6dM%dF!R^k?4{OF#<{_V0kNn3--Y*uJqJnIf3mA$ zT97(X)>NHT-k#;}tf&!JMinJ%TKs>#p2psIx{dEXNthW-IDuhpYayAA?g|A>-5%%H z38Z1Ip0d7l#5KzN??nL70GvyAz9PGGM1IQ4XT>l`bz{dv-RiHaAj`-@79(2AO>}~u zFRx@(KaISe=2{D=DbL91s+}9|mC3X>$L1tx z&cT8^EGM2b{YTyS2UE8rjn|}CcHxGdn40#NOW}8}+SU!Eq+i#c5l1n`!#EdyUbT4R z{B99ue2W8iI158{!`gA4$vZKlvt!QWjKlNIFydw?54>bo$7bZ`xv8`D*~kkv}` z-^_=xzcIakzt1b=pnMMK)Ve9tw7Xlvk!(V(vvu#wnj1P=+gc>&QzbRNqn=Rsk3cy* zR%OwDtUh(L?k4&JJg>8@{!ji651oFPy8qt$;X~~Y)$g>b()l~Dr)4Rg`a4NMUL-{& zLl*p6`yuAE{~sRv189+iBac~aTju_-5H~C|A=Dvh@l+Qlf_Zfi_asNL z>yx9`&Wqbb^(RK;9}E2w=MHaaaB?W{J)DXT0Zw0&i7>jH=5y1PgwE5r^klW1?2$t$ zHEY4`#xUp|D&u0bPv5<+?-L64=#}qk^7m|t?$h!v`Ibcu z{HRbW1<8SYNt`F-+nZM>bqGK9y%GKyEXWkWp-QUa!&9kjkky!92dx-8_6;hBx44Fi zio5>|+N2EInRNwLMo+9?ggEPq*SUL5<+tNc*2gXW;T6Nqwfa6XfAIqk1eDE%z736o z2+pkc-^$(;1I6w$iVaeipCDHew#^l_Mw;xVcX!APRJ;_2G;Sox5~V1fqrtZ&sz8X@ z#Qw=1M*D@vLmQ0((R3%>TGiA{FugV{hgJH!*$fSjsv;g~Wz(hMva*aMu^aP|yoh|p zHYKg~WrOXE-AN~Xy)EaXoBSj?Omv?Pr9XRbuJK24OG%z4ODRn)UfaNrLjewrO^1mo z515Q|S~7X><865nPO{U6+H2`-h7Kl0L7pJGW?Z`&gP-NQyLQr(yefAYkg96vgs4Y6 z$g=a4jlZ*J?I$UA@@EKPjm)#k1Vww*7*Udctc=znjqur4$v<||iw|H`*Ep%kFwr0I zGVqFSj<6qfrBvxQ|2UvmqU3L;TcYGysSTxx2q`sl)@$sXo4bKtRIXc@Cw#gW`*iaq zQS!@_m*IH4#k^k$%JCTfToczEDXs1JYdoJ;$7V&J3y{;3KYG|G}FZ0PgOt+P89hS^(aMp`2Y3bXX ziJMW$zcpir7I57_!On%u{(0t>`sgt>I*h=d8UcQJ4*YWXxzW>?exmzkEI*F#ig0mn z9m%!N?}i~Z3K67#EHwl|hqBfl;mT^3g>xLR7FhL%S$Iinye1)$s*EgOSW*%*{+_)m z^nrlK7RCk1zH?c=_4IeX$S|2Z|8e^RxrsHX)Va-noo{VJRObKn3dB%8ZO+Dvy6#w( zJ+qA&m~$PLx8m*Q=4pXgX*8yTw4NEtra*-pW}L)QegtzBAELvR;=i2X<`@C+$c{s~pPYwo@MYU*1;((^4F^@EvD{%5sxa*7HWl+b4g4r@&& z;n?&y=^Vp{C z?hiq*4J^!0fmY!m8%aY;Da$~e7SKOJw0@Y7(((937y;c^x)BJ#GVyfDbM>}`QtGDc7W-e~w$XChPAFp%KYzfevp z2}E8>vRvaJd%M)Xc}vT`|HCV0{x9t{Eg|%o>Xy^5t6>%ov6WC+ywq4;?sPt?AvWYk zuHs_T7s4#wht6hrPrt67Kak#XgcfeUI*tB%oq(tFU#7~mG#WrCeUj~S>Z#W?Uy?<^ z9D921*497%;YH%f#(plti>JC^KfMj9{I5kCc$4BTrTh1(|LlI;{bRuPpQZc1cK>gO zfXDv}F)(BN|4w5q|7~r(t7a~})Z;y&xD^ugnxH{c)l*Zq(F0Gupp+`?;21FPIGa&? zpYgc)FGNP+LLvL;AKn69a(nnEl@Oku-4w(86m`7dpQvbQY0$#;>Cr1=vX!>Gcxuwe z`<|Z^a%BEIyyJbTt40ypKNGfaecM*{1wZ!b|6`-<3B?J#xC81LOX@(=K>a4NYX0H< z*Cg9Tv1?2^cw%jfd`nsTd8;G@Lxt92cm#md_69mjFcXg-{ zTOSYyOE=(Zr}fGU5ItZs-5Ur=c}DU7_bBpz8cg1JpKtF`-f*%1kH}XvaBr;&6CBs9 z%b;G9d@7lkmnY-=32@y9be#2%DDFcF&b;(veirKN^POIuU*Sq>a3GW`EIjzB`7Jf# z^JNp{;S?wg>+R?*$~;^if1l+hbHYbP{XR>)r-zsqhpqs2fL_ z|MVqgW!85U3~D76*YZjk;=AWS@Q6QqFFp~zPqR8i-6mM_mj3;2BqQf;*hlch9oYT< z4EX&+QH1@LgnhY|r6QK3(t^Hmy@-jfS?(8{SyE)Xbe#;YI_N(;9&XX6iT6ZX^i^Y;~w$Bnh5Ng&)%21y0XtSxZeNutjk@u?TvlV zUhwl_e(mSaHB#~JsV{xmLD+T+2IyZ)VauA94MvMr2dU%<2Q;pez3%i*NM7x9y3uUP zFUt&{2D{ltCenzEG_o86G)TI#yWirLY_@$N@4j-?o@recIH>0BNFQ=LeMZ{s+3cgL z3=DWC)wsT27{I&Ioc50!eg}5~J`K9oi*<~f8QlJGWqDEM!o$*d`)*>h9&>WvS7g6iINdV2zbm(z^?BB7!2~dF!4WoU| zj?TxT!-Z!o+9c(?kg4JQ3X+ssc#)}Q%5CTQ$4FOHA^|xROJ>s~YkKu(~c=Fyz9V=;oB^A$Me_M}PNo|Hbg^kSt zU8%lo$dYwOQi=0%>f+hixR-LH^RcTu&=dz9>_b?v-1^gKdDV(=aCAOxC zsl2Ls$ZJ$>1a`>h)r0P|;0!F_?yhdXgHmgXh-aZ`@jN45PSru*yx>9an7aU5AGlC&xm+0l!mt+qD>0?mQpMmgMOR z0EC}qBj;;NOohj?WM6tN^^&PsJ;(2wDA2wezVPf|7ZxMcEv>nHycB*Oju~4}cdAi{ z#H^zvei^}2)M(0|oQ8=iT=kFRE_CqT81Kj=F*xc(@z{FI+C~oTq*Ur9@)mpdZwI{c zBuS88YkC53+3Ofbnt1xe(yQA&4W`>p7&`xonDW66L!v;8fwRti(&G&+uJlV_N< z=f@Z$(Bsua^Ge#Kilf~t;bHb{1(4HI)wQsV*-jwJ*MnTneJ50Yq3<$jfDW^k;<_mi zdM-KjCyC7Yu@Fb6f4XL0nVD|aY0QXXRc*|S31xt6R&45{&YJI<{MlU}AlYp&NRo{^i8 zn(DYkv^UaB>#lrk0TUP}xP6Iyor!6x` z?x#_Z6W)7y_xy?s{pBAJ+Kb35XE`=$n74{+BM!b?7D)UZpe^G~N~NtCD-UR&VWQ10OhRE0=3!8Y?C28hA)Ub|eq z=Tt~$M%w~(n((SBS%_amKS4{!&AFh?0C9Jj0@HlS2^it+1-Z zMH)K6;p%+t<_mx4y^@u2PmB~Q@vC5`dgU^{t1mlse_LqdBL}?(6@J)bXq4f-l$c~lO*lf9+M$z-XoK{5bf274gAtnkplH2B%@?d!eo6;f(6s|caLwE znFP!gP2*yWP6>mrcLLvd0gqT_R;8iKSX$#~BbhcBh$hfdy%LRQqoFjs_QhsuE`4Z7 zYnnPP?HKxzT;JbUnb%(k`#m+; zX3QWn$sv7qc}t7K*4DwUh_2teKdAAt3MR%C20P4^Mp`WS>zn3a%!J8;=nv=B)ICN2 z;gw1~${;uk>MIZc_&2eC)y zIWhKH`+!ddVx5f-er>+9UaO4Sbs>y%S`2O0^~~FzQV07A9P^A-#AN^}#|r(Ty#@x( z)9vtg+OO03I4@h_%(e7l?v|}OaqMgm_F$&w>EE59Fq20$9yyC6&KlW{Bx7w7kK&)d1=hD~MGK{6QGsP$w^gX}k zMHKOP^N1f#TLss*?FdPZVOnL|KF~!`bUsyKfD1W+c*U!DfTYg^?OQF)W`x}SI!4d> zBa>@W3tfXt;LMOzszB4Ss#pgt84-;#)6pZeTs!ty-(T#OgmA;1)qhkKQ;@X5&eDHV zHBzG632)Lg`J0JXZ#s%6d3sXNopEN_(hrNuwP;W;#p(C`u&N@+Y*ArWw9dQt6Q4rt zqud9HnZXL&2LeZ~uFN#Dly9pNrF1cG*rgulytsEaY-Otez8>A{S>`-jHNVMUCGB55 zmq3=;!vFi|_o;E!^i-|0RCl%g+zZM9V|efaQ2}%$)0B4>J$9a8C|=gQ$DP-rnZM$` zqso%a5y-c3#+)um**dO}!(398?(5srt4D-bsWm`%3f$c7kc1+2wz$NdTvO)kgOE@mH+-pAMl2$k89l*qjK%9u^&^IAtgp1?9V zhiFinLR#WvVMu2&3lPel3DEr=SSqL_?@p3}3h>)0$9`BAeVAcMvCSvuc-Mzjm~V2d zYUd#)qrhAx!NS6+Z6=couIRNt3pMkbzVr!%`Y*M{8d!WQG|mqSYN~Edhnrp1?YX6$ zE!gV!uEW87V0PU4?E`m0{}y#D+gAbdF~s7yhjIsTgn~iwySFmG6rXSWCitL8@T)0j zq|Khd9UT2Su<&#VK>;E4C+)W;3*9K{=D_$xHiebFYnS?DU^>Y7w&kvi(fXc1yD7zq zoe$^HUSA^o3gT$p*wmqJpvwPDNyYpOsgUnE49nZ-{3|x$kyA3kW3z`Ay+-HTpKhZC}OfKAK?+jUBtrRf}=bO04SW@e> z{tuAH(VFwz$)OHEo!^=`AR>~hFyG=vWI}rE7apr21HdFWKINvHno!rtn7?ciZF%|_ z>)tlAAx%sInyx-}o3k-*jPC)-u0sV#^OHv)h09W?8*CBaLTO?gb+PN8#9b)K{6~AR zs+A_r?VRrSW)$y!FSKoS6fDPcaDo_8SO|?bcRRGudP?$<=k5k5^D_^htQgN0y`b@n zM;eUo^O{^tx3sq{r>`E{YZx{9$(!YL2gQ_R?*s&UxbXIEvUp(X%CXt)J}Z`Lz=^pQ z<1Mwtb7^4R;!eOqn<}Lw!>^=L&Qv71r(lRMEeY9ejcd8GxMszHYs$kmT_HRYi*Ln3 z9ZczRQ4i%FuT?YO>(!r&3;-@*Jy9p*ihVKIptI9e=}HIVlO>zq|BX#+Omx_xZios> zc=CMtM~bxwq!)Z&JSm2a7E3wg{y9_Ee;K(ZKQAc6@9sP3^kw>xSj}jOiZLQavB?o- z@jLC<_DL7ik!DAFq>bCWAKP~mD$_G6QXM-aiOKoEMDcJ}ujb7x^jQ;Hi?3zu@6me9 z1ePH>4ZixPjd{-<9Q)9ZoVk*QvhN*^_SCja4?01!rflG!!*rn-}>R@er3QMCo#F=9J1ut`Z<{##cf+;Qm;e^sZK~Zp;Fx{w6ELYf>9? z2zfNS8^vt$r}d0E$FgsTsev!8D+Uk$W*JuN=P~pET@M!v?vRP1LN{2bHde>>w()a| z5g$4kn2e}5Z?^5~W$~>UtWH1+MF!tiJ21$PF6z~08f?|-g*SojY17J-jR(o8uP(kz ztM3bT$1S3S!n(;iuq|=B%eF4tL$sxwq}9V)DGy$_v6DfBP6gpn zy1@%{3(#Vn+U{|E(l0nmv`yrjo3f7W6FynsE*Ib!kwS6W_CB4r-xw2KO_Ynm@6X@U zHsKdgw%d)dsGOg39lJ36(=LCo5jw<98SH_v&x)6q*L~b+N@^-c;O#s$&MCm!7sU(9 zjzg(4n+9!B+9htUyMJ$!k$0R;ypi?YyFS3q?&n4ya$zUV(FU=Gv58-%Vk zues#E!{9INS-H8#QZ**&v%CT*{BImEJBTt|o_f=~&oiZ^lhN0oK~W}lros(mRrwWn z>Z5w`VUeeY+BRC-i%>)U174^{&NwMt&s&?S&q4a#kop_XA3I;SUAUK~#{yIUiKq{s zL4tfgAy>qZZkk0(P0bB_+E~x1Ar8LXAiujgc|?op-zY<7xnM~LKFB^?Y6HL3Oal9i z6kB9aDGT{qcqe9TEEHm17cMrhbcFUfy2}yV)-ea244pO(J!^vz-86~ku~Xg7gf@N% zt8$tTEYGQS#$PsU?p)s*b#JjqFO6gs&YPm}Jg={gZS{p2kU>_H5?OWs@H=Q;fPMvyKrPWuf@4f5|0T z{Y-QGV@a7+P6e5ziRqy!PdV+o9SHDF$bH%HZaU>3W9gByp%H(Q?)XplA8kFFh0D1Z zG$rH}ImDu}1x2nE5YaP0dq)mo-a}~>MqZ1&=ji8n;ZBv}x%w);Um-6^$0}pa%;eXH zUNdGb(TA_`J3UPw<2_I`e$AYiLkNA_@%2sX_}fZ;KN|b#(p+ERR{5w>Ry97U+eO)F zgVMtVA&x1Y^|ksLP4WSe?E;WY@1n*^dP8i229NS;i{4+i1J$7SPMVdR?;6K2iuZet z_$Rl&=wHj_d|XeJ-MC}@_}b~FatDyh$88+ ztLq2R;9-eww+ub?iKzcMH~~V6IUWEL4x?QC2BoYR>gOmpW^3GBYkxCoA@)(3_Ws(m zq_%f4>Kn$JprNEiH0n6KTRxZu(;SDDvYKylXIxJJn=PD?-YA6PRC~1a+(l*HMDV}N zGcll7W$%{R7um(`gbpw&FvS73_Yyps>}h(ixB}PcBC~kD9sfYu%8?AU(F}tU9v&%I z&NEBlO_oXF(O>E|C5#gi4PUR`m1&I|*PyNc>g{alYi5+MW-PtCnZ%`FU0&c^Hxk^3 zjFU%Udu!|s^nnR=VFF!)o0X}Y%v9T?;k%%uwTln#FwN58$=^t1XY4shGqY`cwc}9| zTR9*jMXszt;m>d0<3LrHUfc$0Q$a{@f$X*!)Yq{4ql4)BB&U(G zj6Y6YflI4w2`2luc#s7vyqjX*d_N_OZ&bBYC<-}IdB6NyX|n27N^6yxho8kuAfBLw z*5&-uO@&X=;*}oey%pCF2_AXyrk479xDZ`2a)}#oWbx@e*3deKyZ%rSO;&5o@SgC* zt{-}C3K8L8=jAuWX8`;1scTyj39FG?^-bUCer|ZRshUbQB=>Bglhvf{bCABdn7l!Lv_Su^VSPX% zJHh8Noh4MgCUIiYlStAE-AQuBq19U!&i$qf%L*WMsCA{FPEfjruTs0Sa)>VD+uZx5 z*A*e=PTsEYGgZq?a8e;@C*MW_cvG@|^M-f1k174#xmm@fLH(*&rMuwcyVT!n@aqTa zD@X#dg-k$4cX5+7x{O3nH+8N^{xDlNSSq=@9W#StWDOBHt4l(G4p%YgUI<}Y-)C=M z^g8Lj?B_XwqJ_@SsAd78NE>`g-RVgRlh+cO+7=FCY6TJlB|C$L#%K$@p8dR z!i;Ng%dmI53%F8;*ev0FRR)*hM0?H7wqfbIsEgfPO{#H={)qYc#Z6}9++Yo0)D89% zXyaA`s-*&x**mw5vLEK&rwkR*0+S3<1*u?p+^CKBR~&(5%lwh+x{D^6-_xAO6hs9? z=3r@gvbP8c@LNPAg0fQ?OL-n0ZHOuO?0r8CE2yRLBbzh%h2zcV(pLg~9jR4z>D_qy zE@#$#1;iNGCjq0GzFgMCI46&-P9slIkm9JD1BgrUx6H8xlrjJ>L^O9 zhkkCymQ>O!b!kxR?N%GE=f*dAXI|~6p0)A6#D?Az%R=(j?ZMSc*?*fI6dF~0bP{JBzCob9-GY9+9gk>#*Eo+sHigupgI`?La zWZ3RVz4}AgB5`2Hd;g7802KNUPv>g48!UUhbkiA3?u#mQ&I1AKgESqDTY?> z3kSq`dKSCAvybLAWqkEF_NNct8!)C z=1>aRvt6*xbkD^7L&t7e_k4m5d8~8$CtZFnyuxy`jW*P#uxI$8r@SzPsD0h zoNO4E7Tr^1CHf9CC$uWyP9RezndoM#Wti=s_tIL3NN1^%&at7Ea=>Eggl44W6CWyuJ z)`(?DRnQMlr7s}f+VGG8zpzqA7bYS|ZdnHTZ-uZ!(SLm6yqcuwwHNNq`DOP#PyM*8 z2o@$t?2Nqz5bIC7kTpb;&Y> z+E^m>Ok(5ryB|GNF^$-cTaw``rC^JB3DRRyi~U@Iqy0`Vmx7S7mIb|z-6JdOJa0E{ zn-fp`BHf;GywFnMD{WMVHb#eE|84AO#j;TKO|l|$0xDFJo_yCIT@Lbe<@Oi&OGtYf zfxwure?snUCcTQ9esE>$a&W~i63s$IQQZG5=#8JmtzG*r;F9fp981$&5l|JZH0O|< zA*$%g=Lj+1GI322_AXHIwkQQSa$u*6kFy}m-+`;hN3GeWR`2yc`LR5b23n7eJGV7f zM8MVeK*rZV?dDo4nzgJWb%I=7GiIwlQwAWGE7P`jQ`)>ghO#T%G$J-y)*F%z>oE?Y zF|uxO`lMjOOt|Kl*lZnI(n&$-(4l?8go-m+NoESVExq zI)8ms>%Xj(%h6@EKYuIlD*at_3B4^tD9>8n*|8Z7$P#0_Wtr?-;^i$X_mj(T1AeuS z9t>}*kw%_y>^gB2NrS?lvo9XUg>LNYBwC5g!WQ}gljoofJ}$q+;Z#?jeI4RQ71`uB z+lXwji5^1y?w2@#`bQ`aS-xeBGQvzIbb`e@K2yEA{J4}2kp3RJ4Wps9YY3>&$6J~o z5|c6Q7EI&ukSEPKD9TecLv{HmHanT?B#C|N7EpMn6e_f#AGOLfqpj@8@|o#7=gD=s zd!BbgDuwW!z31%p1oZPvIZaDrJYG=J@jY{ehipH3e%V}ZG3%x@^iIO#|tStr>$o+f~Bo4&V z(ITZz4H6fAc7<)f@QP!f{@*tO4ErtVYvyqreRw=>;7FkmpmQq161$lkuee7+KzzcD z%b`0?K+#%k!%ABOO#_fg92#%<048VWo+Qv77W&-6um2C)-YcpJ_-nKTQ4whZA{|tE z@4ZAtKx(9SkS0RtMFNCGL3)#pR1sq6QR%&dfPfHs551F6gM|EVzFBv^xp%F*W}fDe zR}uK-oPGA*T{Fig&D@08&)~VT)c@OKPPY^E{|CrJ*fMCjSCChjNk(Sy&Y$Z zclD$hZUChtP$TGc=Ii~Hx|IVYy5Wv!t_^EmU#w(XXC3x7$stc!*axaZwC@<>zb0|X z#&cu)^pyrZW1RfD_`w3!<+r|c4?!$hZ;InN{Q%`0$UuxEX7^BaZZbsYeWCwWQX%NZJuXuS=w=vv!^j+5TOv$ zoi{a>=D$Y5@@!T7E-aJ=`z2Df%d46|@Qh-GuD7_n9@3cZN_Gr0bC~z6euIkHQ?r00 zR6{2!@yyBtLi7z&3U`hZ`%2DOjiWY%4cPs(M=0k#l4^C1f~LID+&_A+`k4v$=U1bk z^8s6w%rO_({aQEaZ1_7aLI?bKQpA#zQ9GCPn^?(Ju1QMM{gi?>;ye*}Fop^4@VbqA zUqBUMfQgKbHqupPojbpqptR>i!SideX_O^-7@%iEnBz43eOa@0l`%hu$_*|Re{U0| z`CHzHhS#DH1)^<^He8Jk6TdRw&}+~7J-PHrTQil%Xkocc=9;6AUEj0SgbQN8a1tqU z=;3A{gu9u7Wk=YlXKUmMna zBH8_y`K@Nr>d`Nc&~QOTI;1eVLE=P~a3Q-xyb122gO0%{Q(S^yLK!re#GJN0mlmk|o>6v^kdRcm zVH#+36G59(QdY7*&TygowVT7H&(~merK7KnQ(yZhJQ1`aoS3zjsi0F`#AAh5U9!+l zj6G+0#{u6^2`)h;Vbk+|=3$D0`Y6w6?0cN<8qyZQxliK<qh8y|-js zshfG?J;dctl6Q`y)bH`c#UD7T@?RA>rc<@M{JMM9I%a-IPO}M5x5Qqwuf=^9O>Hh0 zs`pMt(JIjHu1TZ5L4PZQ2OAebz>FroT)^`)eW@AZbhcYnUVA*CrOHa2@*SO)8=^CX zVr%Y;bD~MDb{fwFr-kcG9aJ1T#Z^ZG$NF30T+tS4bhzf_6)o)JX>pnB#shA`L~E4t zzK7c`XVIzPr?OkN2tR;_>m_dCh|O7OM|0wn%d1@!SN4Am17|K$u^wtnH^0=lKu~TB z6kY}v@3J!1_oPX2VbfA85j&1DSc{o$XmkwvY%u;IwBrE=TcH2BR)u*u&&#=b!O7^Mg zYnAHB?wAU9)IT3F#&KL5;E-tY2$@0(_l~Rl9Bm8BO0fIyQcXypSH*&j+sr1f zX;qchCtAR4;>G#Siq)LogY6}|oFtnfEseX;uBoAv+t39oOge(sD1le`ba*k>d?%z; z_C=IKy3@|Ebia9INF7{ZuB%(D1bM6@IRAcY{9w9#+Fk`L-bo<2$sjyfoSU__nBB2s zA1|3aukzC?w%en~{*$KD=N!CLz{?D5e8{QA$#%^E-d@-0+dqyW=CMP|hG{>@hVD9G zI8)?@SuLK_&VdV`{BiDi|KVyNEwODNo+w#RY`LjW6L|L@lA4YW=T6_jw0a$u&?lf_ z|NFCh^}b)^t>z@$cpeX3BtUR!M}U#UrkjTtXx3`SZJg9_r;;fl*6sUFPSAx}A(i}( ze(bQI+OBz1(D9ZlhU->4 zFF1-?c|gh@|G3etk?7@Wcs1tmB=y0gjkfYq2iBpl>dN2yUqC%(!XZkyiVDIApw{KM zyMq(lhc{g5WrZxMpsve|oJm=j;)>#ifDz0Wkhh$l{5!V_`CjUh6= z!_}V3Eu?QH>L?HvQ{muRZ4qqm@vDu{H@$Uw*gFnnNgLDz7Cf@8Hu(EYc8Yd3eqEwo zdJc*z0MuGOjwgerb@oCMc06zV{M-=(%5Yr8wHuxuLz5y6bXZj`#2EyB0f&YvcimJJ zhh=+E4NJxy2?D?^?-j2a-k9&%NGc>cT(K}3-aR0S3&`@EevlL6t1hjIncMj1eT{f$ z$&5T{u*0rNl2dZ`3yCEA@U&0d;REP0)424)4uMg(Q;p#zLp!K_)*FA18RY>-L8X*` zd&QdYd4mT%uiI+%>-c$&YJ--zA_2AbT`Nhj+edgZc3ACLFWdUMH@!xY${}A8)ssUp zFCD6PDdCOvOXIO)c2j31u%E#G-0YX*&?&%!Z`LgW@Y%$Y2bFuNPFsIz*;m*3d7R(m z5TB0WAD>mN+0X4MNBxIHD{W22n(_ITZ^>ToBmVlS0d~UO4vm1`#w^_lxWi^fUBlbh z_J5_Z&&a$q!Q^Z)UFVfj1q?&wSn`Lf6+l@F50#Tw<8b!>kSG~O%h|`|r^D(>G8R0m zIs#4xpUQA{M8b_>$=Au^1!@|5xTle4byn4XfVta`_aNEm{(z=W*PaAZ`<;h**@J%BbrJ z+bz<*w;jI6My|5qO1{WhoFj3SM9CD>?;wGTG3|9Zf1<^vBV^t)mB;w1+p|w&u&XSz z+RS<`E|V|Py)o`#xn`hG2DdKVozRIgfwF!nPo9PNj^_EDr880aCBSx@QTm5Z+qmDm zlTJJ#{|I*He(EQM#w(}}Kn7$Ri?~R^xnKlq`*6{f)K96cj5+F11JYevp4p!v*>E3U zHvbxws@uBntWA4-?>b0`8C+I2>6@1xwmiA62-~PEuiHU4f!6rS#-jl;8@akKFXGZ* zILdRPwm7oW8z(rBM5@Szi7XxXE?eRvTh}Nv+Co?A5e0Ju5)tMt&X?Q2M`#} z8@r;{(Td?LyFs{mv9grV4KwqmJdC98quW_MBbZ*fMh8!u+p+Z1WGW^6w5WbE>YFYg zFJMv;Ri*Rk-IQUT)6qz`yO3FC;r-^0eoQrh!pO3U(N=MK1Z0L_(fqaz7t_^Od@ihN zo7+62%av!30Wv?aPb{oWjQEV&g$m_=O7$1++t?Y{;@)~JQ$iIkAvxcUyQ!L9B#d-0 zdGRjPi{Bp;iujl+R*528QB^v>M2?bHFB1Cg>sFHS=4%{MS87an#JTt`RjWb@VO4~u4Im1LP=(BO?bW6hXrq`LuJBQ8TD%J-M=&2eq3?S!pO zK*t@-uJ{ar0)$+i*Wd|ifZl1$9cEej56KfuzEW5pzal<$v`22fJ z|D*q96ye~rZ$v?FN&$GS1xbsiiO{&oeu~rcM8D^}AI7&PU=jmwN<~)(mH(iwRSpk& zvk4@Z#n>OjV*l%k@7G~+O#5w!XNMY_H?6yWB13P`&qZpY_d9#Z(=$$s7_(w^vpM=8 zcn{|lUQBLKNa>2_i2bwBy_!YV^E6PX+bfaERC}Uth;jdgG~$Ku?1@qa+#oegx8RT; z91?J%*TdWI#Ix%l$u6%ho+agJ+#f3Ds$be~AU5uP@w#Q&D&G~)$XHbf#0eu>J~esA zK0-P*WLj$Gmr|8(vpidW>^~Bkb;yM&CgQIBv=k6o4B}&}hIka7sZ8Aow4VL=t#$6m z<9WRkYp;{H_BjFdenDCvTIHYHi{sXN4G9adT3AmD*KIc@`3`C|mVJt_3S8h~Bp%tPP*Aq-~34?t-q#sO;(gnq?242TP=1a-&B{kauyX>rbxjAU5MrGMuz=LoF@{-BR0vJ&wEc z+d}p^d#d1g6=p-mb)>dY&EsAWH{p`m_@6C$!Tt`|z6%Rh*J>ZHO;iS=taTe+iC1x3 zOul%n;_#i{CrFBV(N)DM?o4WL;*WoJYq=Dn2d^A%BEk43HwqNr*E75J<5%id0)`p< zf_=hfY<{UvE~%1HBd#A+7KzkM0%mCZz0jr8y7~4x!BgR6%rt1~(mTwUsA?~~gLlD1 z6{GEm%GiISUxL_ro=Vb}NPVtMY+{yD%99X!&h9_Sl?i)0HrKGm)!D(0OQlfp`q$9I zwjh;r_rtNUjfMTq_ocqcg~L>dVUN^>$fUQnl}$FA(H|g8xTlO}CKGjhGgR&jv9?l7E4pvFtPxwl#$u4SeqMQ3e%65YE;jwjDS0?2H$6D7 z?DS+nNB+@NYtR>O8rs(@HeSJjJr>0I;I6@fucsKt$`=dxra|tR(Ue|yluZtPTxG1*2H1GS9oWQIwp;1Blc?gLYrOR+%)C& zc<#yKPHI?|5&0%d4^1#~lCi;&B8|=Y^(@Ou9*JM@Yd>PvX=>)rP$P_IO>ZXFcedGL zP;k4Z$e^8pExt>!yMW;iCk*td*Wtf@1T=j&{qoo+AgzP4>#Kg1a;}MEzKm$0bkJLq zB2AsZ)m->(xbWXHo%cp&mu*Yc)hPQY7>gP`1cbB23KKtq_uv3-v|MA}Sngg|t;RxV z+NZlc#!X{N66ahyC&WbjH9g*;F{xh>0HTd~d(Y7<^|x9|-LF4OFf^BFf}9%h zUSYWkI>#V-iddn93gH-ZrfVwzY+$&@ zqpiIoIhP+N$1hVwlKmx3Ppk60%*fLc%=Zz8L2K5)dr;#3lpcAstel%$bzc|6#_w5U z2jm?or?ikT)MXUqqZI7s)Q8F~;kJ7U#BcJ|s33P{sYx%|KD-X$Sool`xkO(x2Dko%0+l;9xyg}*ExA@BqnFoH3+a%MNB2n z?xVMoG5h^;T(k$=sJbyGoQ|lp^pcIfdxN-W{%Vfiq5;0H(QILVqSYD=Z#eu({NV^; z`XBX)*96`$8WV)SzU(MFR1{~#5?*6U)%_76h!>1##eC1I`aZ!eKy-r&I%iu4u7U* z|Dn8>F+1swK4j%OlrXwN)vAms9O!s>=1tuLTDSCi+*oL=n>#5lE!!7&)oOc4{&uRn zzas!U3M>ZGqRj`!VgZ~1w26gmwZvm1u%`Ik-4(~bGNRmvBZ9En5%DhyEukslS;pc= zTT6MqgZ#wtUwD+3tN*n$xO{#Kyq5GbabzL29&^yx0Fxrln9@H9#*F9s-+K1?mE|Z6 z^D^~XgGw587TU+;r{Q4wuiwNcN5Q9#c|n7Ol0}4|PN8VVRa%25cC!6v%C=wGM`Tp@ zo8_m>_YBg%mOn>t9`|T?x&QL1tFC>&RHzLd6B!^fOhDx~U-06dB2}3lX(IcHf3&&3 z^5o^cXWY;EU<_a8Ta>KLRU7~ahlB)@Hyy^|BXR22x)2}^jwZV7VAN{2z)ILk6=jb4 zTr6e3QmW@frHB)g*Mzl<{8RMjLD8J>>4`T@LmyG+RhuoW+DJG8PQs%t#)RbQiRqYmbM+x*0J$H>Yo?*oYSO=&)xns=`o zj|=}3>uV-;`wb;x0dj2b%}$69pmdiI^0q@5_U18pA8_9Due3Ttsoc|41b+j&bIz6d#UM5i8PVZd7|WVI_G@cf z9VDu}b-#ZP)bgtT&u5D(kE{C>{a|j{W9*#}89$Y^z_&$Q2Y46Gx4AlScLultlXl6r zkxyKxozB6zv|D$9X|_q}L`2ux zw`V(l&JaO}z>UOYpJcAC3$!Tf$W-ynd&=eNTwY&tX*@KaDEMKh@sMaben?_0pDgVf z*3_?h<93#P)ezJVusQ!qo7&-loD=T^)XR6#vf`Pr=$sWG_I>KSle^sO1zp&i5nt+o zd6C3P;}qn?8OjFOX@7plhyw@p!@< zcvnhMIo9)!k8d0y{Qc*LRP;1{FfMNlUXbd|2PsV&KV@w4>CHQ`P2?SDf8no{o}JXO zZu?sh%CFY_CjFVs&Z%M>ufORn+1SuOW8P273qm^DN5W`|8>|B`K~d|m=I;C#a(OQj z=o+#qjzVrg<38G5{Q&lBV?EuuXZ(`|1YTXQ=R$s3X05>#9?Axxm!!D3bx^I({k`AD8Fby27pJGuK2Sc* zJFDI&+<*B|7UV)glYf`|-}iDmif4s&ynAP;1lYLv|B?WE3F|Sykxh;b5b3s@|GfZ~2}2`PNO!l1qX>x2 zTKoT#0Ly~yk2(E+CBPI-0f(@I@Bh=8{Xb9BdHx$NAdT95oJY2Ty+r19xD|$H+OrWJ zfs?yw#;x|!ENgQ#OVByGY&#r$NftvD8KRrnCl5Y5tI(`yHZH=$G=(Z-Xe2br8=wVw z>!<}xwTBl?5`fa4!Y-#mi%uU^aku&^D53b*mQjtt-iTY-aANmFzr&sb==mhvR>0&F zl&?C^W2wrG=Qy-@Lwt_5PfevA7Ysrd#@cZ;O||M`$~23Z-&n_Jd^xRbXTbM6!=}UPN3~J4}$*=fA!D!guqbY0C7r9um`bk!LuzS z@p>z?v9Innep1m9Ki*4AiZdqAKUupN_lSxkoa_@!~p= zc#!*kfHFF+f-$N{z>)XRSEen`<^EKDT6&QTns?=k?VPPdfZ&j98eIMIJa|vjWO8>@ zPG{-~uUp%hTU9|7my$X$jD9QWP&~SOg)+Xe@K4lp#w|xEG=R&!Kj_gO+yKZy9cmW; z)s2(mR|D!yUv9>rCgNU-wI;Xe^E?6eJhDBI|L`D{iRU?a0eC$x@J2{uzUjWsc7e}x zRmIhdV*>l>>tP>etE4LS7IK}EhB9@^$YMi&@2g;)L_x2#8c$n)yIpA|MJOe$k?A|M zL_qnFnAmqN3Y|{}i`JLus#+`SfLt~5oA2#+ApSTMauyfbvqH~{N$wHVi0p_DI8^wx zmzBv@^0}%)HKUjENs4Ee_*3e*B@D0w&xqq*T?ubzis&IQ*8%J9p{~s>GGM4c-IPco zYS(D|MC0WXdYSHsA9C84{;Vvd3WNNQ14fnn_mpX()k=(V-_iKfpZJBGg!qsBsJ_oG zpJDjNB@amT(=y$8JF3^wRZxXrN#`|!D@2etSdce#u!?@>K8qQBlvuR`lM=2>Bb49H z=4@xAep3MPK0iNAdnfCCFcT3ZiwX(qLz3bmI_G;!Gn{8r~ z`2$UgCqA#b28a=01-ztT{Hwb@*^5D(Kz`;C|E)a!thci` z6X(S|c0pN6fBAR2aHMd(fxJNX`u+eNu{dQtt0J>!nDz;-aXl*fJ@+zq>Cju_su0~V z2khq}C)~zskE^gC>zlCl1DUD%72u?8zO5rdu;4p>bfsI+OI>s`QUzsy{}8{dS@&WhPT3aaCX$+#kDQtS0+(0%>5%a*`OYE_YFU&oACpHmL2l z?%rQ7)Mg$!)MnYoFDy5;5cC#olSclj7mbxkYbPhw74PahC>uico0q3)&r}$SS%zLF zUgd<)PkYQ8>Nw3Wo8Bby zwmn6W?Q32F z$XZ1!dysa0Yu=3DTyxf~rZSs_XIG=!jtxN=><6By^R zHknl##0bMZ{kl}?-^z91I@xh?Mt*G}Q!Ywr`3nX{WrDIUp*)$59@JEiXqZK0JT(RT zjdSd#;VaVqz9{RuUC*&W041zT1kZ~eJd&k^#kH%}PkL>p>Q=-n26PEm40cdWSkU`- zDjtxl+&^X_Pz+$IY^ZhnmerELB|KT z{zDSVvaJ$wZQ?jOdFK`*Df{m>u;0B!#WNl!gsSisW|;$%L?fL@6+t#^RNgxoQIVW1 z$15{=gf*o%Z#$-NgV&+{^k9&*uj3Q^X8V+Na~R#51pCS~wVRN&8OLF4Cjg1|Cq#uH zlx~mksMVfA*B1^RrEgGU$KFM#oV4eMab^9Cc!~O9`|ZVx-i=56w^^^9R<|Qn+=v2o zHAYNFqU>zlG2?FY^BZm+g9ioa%UqX3 z^6KTbQ$u}|3Z^7A9^bl4{(RT2z`py+%rps^y@`9bTCpym6D1VMng(`jYDAa{YG_X- zIK3O}bN|QPK%MdCN8{dH6z3-a^IpRYkrxO-1rzArr$xI{9XY=h+j%u{9K*Go9g%>q z!UEO`V`aS$mz?_Nb2;R+HMdX!HH@*>BAy$4wej)hY4>d)+gpQ=!_qZJX-ICt>Aou7 zCmr=u^-Um{tQ9pU%plS?Ej$Xz*=ZQ^CqC_&Y*+9KP4Sx<_-9zoDax#fcrAS`X4?sP zgGt8(wB^gBrNfmH!^#Ht#hZs+8jR5$^T%kWc!G#KjA9eB=>8WB=);08e@Qql7!48r ze&tqiL|cD79t9m#+yNgP6hVrTCho!Q_I`h~2;k=T?%)8KevM7iN%)vNBSQ&}CiT67 zMc27T4!uMBi!4Yb)%h#Fah_T#7UASHVUpL?{Za=yD7b7c{#c|=m(cfoWUBv#WKKrY z!*5lAt~J{By2l&(uSXJ#x%v6uohzuo3g*hnD&uBve!cBci z|5Q?Lgbjh+JXe>m3tg_%s3%88Ez>sFBi_?o;flBA4_z@ic3n^xc!LR8k&!st9olU$ z5E5<6U#dH6xXLq+C#sL-2j24ovEI>VH-6)Pi`7HvTwD}+=o6A>QcXXeSDSYOZWEZn z^qXEP{Ag+>t$^g=K6p#o<^jq?y^>MFX_m^V|@eNl;w{#j|1WTKN^AW_J@g8 zMtve%>^9ej2(3T#tZHw21!+C0RC-gbW)tF6%5I(oo>?Wn&tgur#1mUtib2P;i}F6d z?-#uC#0*lVod)qEH}{zxR|M`z=4}(F5cYUgT!x_ury>A+3E0L|x7smKCl;y53DauP zMOQGg*1Go@qagfW-Tv_vWD9$`&~$#(TxPCOenSNrU0$6Mk1e$!do7p#>3|aU~N5`LdLu3g?FAQ%&;KUnYAD^I5ZlUeHpT$|?z8 z25ilP%sITK(tgeH!;=SB?!mgmTRG4(1=g@QH8qsCnBnrlZmOFu)#m1+?K0?5c0qi8 zPvrUj&TFE@SFZiRFKZ6tUNzB~>{QpR*X^%eJmj>mfU?wL!DvT;%#TF!EYI+D9Sg*3X6>Gx) z+PbKUGV9=PlojJVds%{8C4Rn?FKIUH%#Y$O9>t3$#+kdZs767L)$oP;+U!Dk;v@;| z&SWbjA;fXljc|h)8d5CO2c*k?t6D!b9&|m~|A`d8epU`0sleoC_&saGM1R>kQ{0rQCFm zHLqv)iz48kzU?@ei@3-t>ew?thV*yCY}+i>za>V3yNL&4sG7!7eYw(I$rC{n}q*3x~V)6(_^DSTv=}NmhCQd^89;qr2Mrc6m zZhqIEmm=nDPNee!&maC!RAUqCUB@zm5ZPQQaDSpZPW8JAVL zl*x>n)-7aGnBT8twPi9MhDc}WKu{GqLks-5YlmU-b+u-5pe<-1L=|`E+5{Zhcabxb zqy`|;q`+?rzmcLTqSuoguaghJq@zG=LdZ7b*+4&$yJf`fqE(f<+a((tll8)P#m469 ziQ*TXpRLXrDfe97^y&QixWdAM48G=V*A3Q98zhRcMAqOZC=yR*!w;%=LU3Z7oo_IK zKJtAtMb=AYRAy53Vp52JgJP>aspe^Ix#_H_0%DW7NS#`R+jLvgdpQx};$h_5zF1d$ z)Bv9kfqN$NVC1_y?m%^3)p9xmE~wpPl8@lK%56CY)mEE94n)iq$jO>I*V+FCon0bF#FL>6*s((vY!>lZ zao>63NQA(Od;1QrwWj*wHKa|$F;myj$+K>WRez#O(lW)|U2^I39ca!&<0%buYvGqE z-*v&6=E?;c&p48SU!r+J8Io|-kOe-=b`A&kwS6=Og}mQbsd|T;O7HPxhTy2Lv7vtg z-{F)jNLA{Ogv1{woqWqt87DRO+O>o48F^-hZalf;+`@XOuj6O?9CFjVN%Ir)Z12z- zq=rwQ6@UEeJNqxqx+1@#pz&dWi4#ss5Bd-z*@-mEkiUmYEM}hgUS^yiKK#4M$Wx~6 zIMDOk^g>N_URWNtfuO*1t4B+`-+%^79YcK&4Xq#jpCtB-r?5j0>1#!Z^X`90o@&A; z*IVtkiJ~|ASLRv7eLovs7w5Llbz8&#LK~u8w7l!)yDZ zGn^nfUe*~mrp7->T$0Qj;KP-j&WZaejM1?SX!`yyl5f20U%7Y+?FSOhi7L0q|GMoX zZm-@F{sJ(1D{=K|QbdtrGR?zCG6#F*k!nwAcFpm0~d{|z8swhzw zF+4B-Xawp*Kuw1X#HulXbq5Fn#6NUMP;*^!zSK%sAzl4-$<289JbU$Q=P<)$LH1YL zL;`IO_g;NVq434VdeiEALdLcF>TLbqe@LjBi55?9b!MWB6lg%B9JJ0%JhzvI&`x*- z+CSKR|JhEqEC>56MKVK~_Rn)Oj-RLw9m1E6)hsTIo;MMU$Kt+}xwo_8l8fH0P^&S6 zYxF11Q5C$W?yr}uH4yH7GAqA!Q49F#OJ5Q%g<-%SiALh=ThIOzPhk|4e9RD?$jpT& zjm8r-A{7(DnHaP10%lBg1p2%{H?73YOeu6kZ0ST5Uh7lyxx8HdA&-ORa~78B?;rk8 zaYw){*lHcuKJ<7X9OhDEBe1@$}i7!kIx$lkCQj*_KAej|2{cx z4gcRPsO0}wkntrx6i*B?enux>2}l)QQ^B`a{+%phPXJeJoBtuXU;v&2zRvbDYF*o3 z3s$&YYOUi(Op=d~Li#9+UFR{utntPS$JqP{j`WtOpzao>QVRaDU3=O7^p^p zPEIfKu!!Emgm$^6n*svM+@2ltU&q=s8(i%Amu@Y@^1IJ1&(#WDp2}2ZrFa(7FYx)S zNc={U$M|QYzHyC-GpaJ1^$?}e)&VD&zDK{1*(gbO(lM`yNkQtM;b)l_~XA3-qENYP-?Ifl>12RKxb$j32y{gMOdL-;~WKcojc;a$-PwTdyWZcrv zNr2<+%-8qL*ZBFh-HTn+R&EBsPqEvo85v>hZ7*GwFS(}dLI*_gg*K`r+CSk5i<{DFY z!_q`$?Z#C{@{ln+$s%$-t7rqD)xS~JHORV3nOzOM2bV`p5z3l6cq*a+F=f2~X2M0h z*eFEBznLH}kj`WNLL)!H|Hdz-q>v7S%qCIv;9bWu$6WiC)T-!2SvK4Kp+ltf+zv$Q z=36TlR=m$niU=`jBS6cvP2gjhg~PG{k97-{<6C9U631VRg%%oMG^Da4IP;4ixS%x{ z6aG2ounirrhYJOJp2Hs0ua{JUIC5o%pGIZ9OFiU%+|Q+`Fd%!C1>b|!1|_u1<2A+u z0C&J#-Gz(+OEeq%L?`}eb^S2n%c?Q^-i&`oo|VM{LG@MtGAQUmVgky)|A-)h^Q{5{ zx*G_$Yq8WDfQRQ-n%@IOLvB1C1$|^Uh=FSU`g}KuF6QHU$M9<7zEs~jMnS>Bmsw%z zvXayxyy5M#N6`;9gA3xiLqWk&v`6^`Yjdk=T($nT<(;McAJvVj%Vb$WY zaG?|gFNL~zVa<0W-;B)hh#{z|_*xq2GbuC2i=wf1fhr^XO9; zM4^{H)~vMovO8b6yJ0>nDv+`8Fo!)SR~CN!`Qa^bK2qzUJl`K165Dw*6`!&1!}8Us z-Fz}c%(xk1i$sJ+-u}$6qh+$Dv;spID)$uzU;9VP6cA3`IFl^%-YV>@ZYpjncvm`B zyv`8Rda$oxpaJvYHMu!2qv#b)D z4U(z=FCwg>KZK5>wX=+26XGz>xiW6nvmoC_UgSKFuMLb|-5zPvWRb`>pT;_#wBL^7$9`$lLkl zU~&w;n)uht50}+D4@`v0LLRkDiq&<{;H78mdR|T=d8F8K4IgDa?W6n<_g?nk!&?cT zd?2g}DoGeBpux>yDSHZpu}f1^N3rO}FRK__jtT0>`%AS}e!vq!?zfat$(!j^Linbu z3gUID$AAj@pJk?b1|r8Si~qvwslEtLrcyPlfA5fVvJAIQI%3#B$7X&dYe}MO3zs)6 zSe*UlhmmoGm=^1aP9AGceA;GeTH;E;Opp^w#(O?u6&NEcAd|?p28%-02^T!BO|ni; zHEyhaIPA&Fl}&dU{j_bKt?_LvZ<0m|hk8X3RRZF(I$8L=-G~vWIdqcir+t^L^y)yo zis9-C)$TGG*8G!9|70za-tE^4&rcNwjoKlX#-=+I8LWGdO~^&WUMgEu$bmfzKr|%2Nd)gNTZBiTHOQgl50V;;%&jGyTep z-lzJ{c(&MF7BWG{S8lgbR|29TmRMT#aoO4ri}ks|7vl<_v^QBO;-+79$Eq2BAti4J z+3Y|Qzp1WV|AdyA|3lJ|^&b*^tO3@%6Znx+b-G$bV+~1#3$mZ`PG8VfT|{|<<0(`! zByC?x*aiiVul(Ewpi?^D(*ENmF#Lz4Gya%hX_j4S&#c8@=}>rIi*k%GiVehKkwPIL zfRmAr*QGw3vG_8eWdg2uVd@@ce9c*PNBD(tnW@3N?4Jy0r0X2WlO`1~zkLyy2^8Xr zBd|!_S$-{hrR<-$g56ATO=@d4H(y20lcBkK^>y^QyX=(5TQ$mF!h+o#8yq!uC&xha zum?K>$m!NWm@{%NlW&QZNORujiS*%PV){@zrY8sgy{@WsofHbZLv&#)V&-vsob;HP z=rjEn>S?mIDDN?5?!Z5-AMPrLOgE}%F{x;XBh{EJ6p>J{)$G{2dR$mls9Fn3Va9)Y~3?rS6OOOHby?huT-sc2B}^uw1?4i3;6W7?SvZd6-%iY%DiMAd(jcZtV;Xc zeGCihgjI#P&p~q`$!`K58a})Crxc!z)-v8*sB>A~&r!?yrAmOizWu4`@_Er9#WTUj zp60ob>NC|FVNK6vt?he*I*>gH6RxMSG1lPK|d0FYHhZq=K?exYWnZ zqf@mJ3xcU5B?9UaLw^vP>`8TN+hF_{>8E@>gm98~{H7PPJ@R$ekf662bj~i|Tbg&u zsa#I;B6?s=(jvqctjCTu<=NOs(|M5K^nT+gCukwG5JZfrYkkCf@}Ai-V8wEg!~{Tn z2P-7`aebc9&p1tIjYY=4cz6`Ezimo}W(bB7O4bG?A|PFoXOieD&^q*^n{SXtp(4yf!z!!Il4=z}1H<$*w?Sn~_Fy&lQ}$n( z4l&<<;D;*8^leW}a#8ysW%h~P>ubyvuG zIF|P_^^lg3k|~k4HphixX#}@0_VF^wbXp$o{{&nO%Xmh5$oN(I1-PgP?kCk+^Y^=lMDr zIVct3;;H(ECATOV7PHUWI?dmVj|fPP(U?b|RWjrmU?S$UDneOaES7xrmD`hX4v7_e zg^CNh!8rHgeSWr+KiJ~F^kJxq?~Iz*?F3}sR2Y4~?ghG6H`%TS@pi{wdbk}$HBG7I zqS$&F^*tr2TzIV5O8aLY_3c+rvTKQmbdTI7+kN_08Y8+XNOo!pph;-O2+t~gxh`vP zGo?!Qes7k)ua?Ha^Y<)@jtu8(I3#|z{EG8B*GrHRZxSgs4tQpZ?c7;!Ck`9;g})J% zu)Zv>|CFfi~NY1uM zZY<+cO?f5JWlWoHX&YzgtVhP~Xv)WlbQ65MFO$tJ#$x2^x%2AMfV+P|{j#~fBjdsl zbw~@0q>P+$8&La3;|7z5(_+Q@*|}Ca+<$J+_tq?drMS_JHK#eeYRxU*r(O0d_p7K7 zeP@(+(UC82rF#EZee^pQfuFhLS%!cby7n!xq&UZx883dF?(e6CkX%yn$vdVmL_DnR zo`l@bdVOxdA6wI#tEsXNcg|zLHw?llWvK&4Sst}{He(SNa*;Jv#dZg4wY=Z>J})XU zsv5tB&*Unx0cyvfRy_I4r~^umY}7`#USh*bvRKNn!>_hbAtlhzbh3K0LPsmvde4@0 zXz0cH)4s-3t<#0y<|g3+h5;rrVw|^QOscq+k39Fej{QfM3An)(VAj|;{-bT*Mvh_* zrjD5zsNq|&QDx6uRYKBn4wK`C$oI(p5mj~%8sX@?jQz7b+nRGFtJc-Aqx58Pp{viI zWXGKK2YTR-aX0azz!kQ-(S;0e>IgP0dQT5nXDN%2#JWk^;g5tm6lotka_=vcM&CD( z<5(3kCfwV``uFDw;a$*qH=&buhgas9W39ZG;;$B7dDr$*{g&yr8PCc3JkeAx(EVfF zEQW;A=ES3&MKkZCtEg=z%1z9DXqDP_+?@C}*okMX%Aw-SzLc89z$VMd0W9m1uKmky zqqT~OpJ8r+9LkgKr8BS6+*dZ^Q6bmh64rlgpMT0{-e?=T-zlJp2{BfU1@(z@d_peCWYMRUcKf;f4}04lROmrDVkL2koZz--*cC} z^+hF85o%?f^zp~g!m!(DpcPCTE4P2|8(}i=5oQyy+I8m@R(O~m;0Q%X^}W%*A!?I( zrV+<@QYqe>1JJVB7V(#3Q(pj_@kmU)rvfXqM-5x3Q|F}<%a zRke$A^MA;fB?cMMzQyBE&|Qc`XD~kGyXOtb3d&7kM9f3%B+^Fwjdf{6~^RC z{+c*grWk z?77{&Wf&*&=+rCv=Tj|%6!D1UI$j18t;?CMdG3RMMBp06ZD2!vap_b!TjoYKF+_%~ z{PL4xghzs`vM5N2H8@$O?4klIoQGKQs^;5CMejy_VhAXoJy*F!QmHn- zy;Ru(w%w+_c*>6s@6k0o4npDCwQug#5sTQf58vN!Ham>)q zQI~;i%`B^m2Z|4pk(Hfosqd;{<2Y}V@$IZZ2GXYURhk6CpGjn^pFf{rA5{=e^pc#m zro=~!|8jkp4{%K)D>y$F(~rd(X@yp=c_!DeA?n+v%t{Xi<77GUyr{S^!AO`_m9d=^ z72WZvYQD%~-*Oy(L8kee1bUWhNbkmVYnL@ti4tal*-46dkXbk8XX%{jhm^*wtrF@` znh)@uBe)=ZF-iaUy&Vs}3C>8XPx+JluZ;$Au8w=2SMr+*2c+~&?ej}9H}c#tmpG{g z5jsz~h$fq1d$whc7MqL*4P4+qa?&eIhe-teGmOndo04iW;GRXNKLk^-7AlHV+WW@<&y2^A!^Y0n#gn z7b2+ULAkIM+Qe%DGk#DNt75PHS&U|?b6UNzpY@C4^W5c|_mnN2Z(NQX##s)UH^%qY zM3}w!4StNSGFwrczEXT1p(ftxP`sEYevh$0>JA4z#jkfCH8lLLq%Uv0|LbSo2Vby2 z_CByD2`|7N3&hQ`XHp{wgTM1U{YO5+dY^0$1R_rJ_HSSyfc1Vbo=mfFH%&^{FDJ&? zQ}EHe$=l4$Solw6v~X+m6L540S%k2yi`!7j*7<=`uj?;Na{XY|rJDOiis4LYSf3(s z`n9%OZ>xcxp7QT7<-y#df}NtOk%Rc#8Jm*fT4WkD;glsdoqXU@DX8D5IwupjSSLd@ z@yO)ZVP{F}M4@%9>n_x$=Yftn@?@V=vz4gV*XPAgHXHR-)^DzTU-<{1Bw*yjyhc#B zwjN0zoSa4|xIz^1h9G3+E5;QQw>Q*e|7WM6}ywZ`Z4SD*f~2L+(GKk@(@r92%Lk zR^kn_R>@HxblsKtkmBQ@5n&h0$o%`idu`rV+R#3FJ$AnlqSrgo-=j10-Bp)+`9;#V z{Skg%3y<~bZNPbgV$jh0hGteYd#u&w@UDVRC1<8F+OO0L`S%!mw))#j2TR-Q>nY4d z>+Bx%#rWAXqFYSsA*MB9?Jg)eM#H9jl)CqzNxl98@`b=5oy%*}hiA-nqk)`(J?8bu zZg^Ctcz@ZI<|@`pJRVUd%hcxhg0TdnUlPNo6AyNv0`ky_h|`_ z4sY8Nt4=MQi7Jil7}x!(|9MTco?!BaJ=cvRAfm_enCy;vXt%}lox)UN>X?4z8SP}7 zLY+r%?iRGBB+JCA(~49ctT7#_d3&Vucwu#)>((}uzE}c5_GOA3RJaBFCQYi`J1mwC zs|Tx;dESyrE_|h7J0bF_exr(ySnY5zm=*Y0l`7PP=|j+h#`&*Rk8)MNp87F@k29g^P5vOse~`kD|njG zyH^q0sl%)Q8=|Wrc$?YS-9lHtCt*5R>2Qi(?0d_fz#Ko@Tn!<7 zAoK2up8c@s7VRiQnS{hn<;=|TqWMARV1QIYc{Zx*MN()DmeUq4mlkkh3ACm)y!;<1 zb435sF;1pT%iB(^r)#?e=*8tk3#IOAp8P+e-MoK9dIml>dcr;!BSep z0>itP#wZt8L(Ux{`D(0dDNZ@47 zB!rSMYEHo(5LPsa4P^`CFkVR}@94~NwIfEu=lf|iY3&k%RAr7nA&JhN-QgE2VekT< zD~9Q)1-X589=@-oaZj4PZojAiGO<3yd-8@&IW4az- zcKaRooP3WUM)CxLe*A=p&~@k0OU@@g za(NE#ew>JzNfN_i@-+^Kz?RH@xmSS*R@vFR^ae6F2-#laWdkyQZyIqG{C`vwxf=ii z5kYnN+<~+%v2}QsR(xe$Yk@sD=5Wzc>8f8KVLu_TA`KrFA~be z%Ej^A|0bajcCO(#qo)7WeL17_U!NgQ#~0Iz+kNWx2 zC|eXIj+}m3g+5t#`=So>VKL}KL0v@pKi1KK1!vr^dO95D792chXcZ)!b@#T^FHPjR zkGp#ef*VsRuUJ^2->fCJQ9Hj2a&F(#XmsH!75}x}VJbenw?S1rq3j6zM|3Gnm;x}# z;71q|B~0n(&iP6Rt&!EyZo-27u=O2CTbPJw8qEZEGD$QUUG;Bakpx5dtu8x3fb?+} z7Jx2uzVd-r!^E&mn!W{p{W`M~cZAH9MTz$&#Bx(pgQvm(^jI2l9Pe6R7XBa@c ziVwy-pW_|ee?33tdPAHh7GBJgj6EsNMr}x?luPOcPEpWn=(MTfFN7=JTryBKWM^iU zs({Nq`gkInM+NN)<4X|9S@1Rkd{IrsvD|_PE*CGdb{J#cu-sZ4awlh1lNxRh4ffCC ze!{8A5ZFM&-`~jfixBWfV!7CrF)4`mB(GNvvC?};bp^&6#K3NsLRdH&F}aJ7SUJrg z_LGFq`&qw&%t)<1cB0+|UwI7rUE%T9Sc6sa=1cX!v1W!lrTG-M4b2%3X?AI5JvcJF zNWAlv zgKm`4OwZy^Ef0c)%m(iv0h-jSJ_Ku@;cXZDjL8)F&|a{@Gr+HtZg@OZu#~BjUCd6( zZ#={9qWZMY5=&q0%Vm>ct)x4slVSw*ozC8zR5?LOsywonzD!U}k&WA0=j=ILuz0`- zph1vpw0LziPLujCP<;&6ewq2Y)Qhu$lss#VUrW7h z@UF$+%PH^1TOWh2R|!u;5(Xe+KWlya$@Ent<9sv_)LB>v&V8gbG$#44MH1|Y@XSb7 zVXsqSm-yF6?RltNL&XS)PA5(cwX zbtwEJ(yE*ObX0Q^Dc_IgtNr;aile9ADw5VJqDzBzwKU#SbADh1mxVQ1@XoLND@78w zI?11-cE?bBo4`!k-dJaKLsC-Pk3vn?`d!(lcoJLsTw31zuwIl~%T8+?=w5i-vtpkV z3r+@)^0G^LK__bSCGQ+%$Y{BsU<`)$*?>hVgKqh-PM8D#wp2Qwo}r{^m+x#T{oZN4 zvSH~ZrQnNTE3T+mAWbF?5*GQ?`oij=aY!PCnUulc3c_|@zT}GLw;YP$H-7;Z2-i^$ z;LPYS8=ve=M%-&aS$-948ehJ~c^oO#v{G5pkJ;|Ox-^FRuX6S}i5N6TNwyoJuZ7t# z1CeemvX=;fX5l@n>uG;>ddiRvMUu>OO`K^xUJP~Z;1VmxND#C)aZIlQ!a!RhMs0&`rn?4$`F8ByD$%m2FbN45 zuL9D8D?lTSE?#-H;@Y;~xtT>23mn7|z7JPE3rC~5ri~Uwc+`Hgx%O!08eWaA2)*Oo z+eJd&7iAL*riR6SU!}i0ptSVqC?rw!Mvz}!ziTc99)Y<;Kg^Z81uaxV2KJA3Pc0}u z45IoF5h>Bjy#{9KfE=&jhOTREfu5~MN7*}9GDy#VMC8z}KC;=1uEg4gaB8PGvhkR0Hju7Xq(S-C;lSe8-^SU#I)nADBDuh%(G|Qh#)fMiYQqA5H|2!SsGxk&D5uKb57&$E00M4u5+B-)LDdF=` zGLV7gr|IVS-hT1NX37EIR(_UkIwkBsc7%TqxnU6#FIXxmm%scN>l1NhTV%AG%QRi( zWy*6P#9n-zC1U*N?fY-gk@8m4;gQ6`6xq`)OcOcXmA<&hSOXJ_$ctZpzXPYBPdA6G zkUP0-OwuDWuhEM~FHCBtm}6s{^tH07tVzX#`=@Sne+KHqA@LapqRiCK+Dwpas;F6hddrN5C>p@QAcTfUNpYMyZG_qG31X?1WvfPD&(LN`kkvyvRn!tszzb8V>&{teQt2Am)j#$+6w<$P<)1x? zL@3hNAmIzN=+0P77AhhdEMI2wW~;mKPqehiv&u6a)u!8VRO~)V;%Pq!)NGzx8;5vK zQ1GcK>1x&BXT*p-e>Kdf`VX|^&vz>C;P;~iMOm$5PUCe%Oc&=+0t4!7z>vcrBK%n@ zv-SM4ZN|I8bel(|W}<~5g`s-mln$s>*lRP;=D`mKq(Dwj=(On}ULEdSB z2%8h`zX{+N#lFH^O2?BB6mDI%uV163Y%v3}GuH)yqQ)>%3|XgGYJW`vXOy_oa4@jv zV$68V$wz(z>|L!EAKv1Hf>sFs`ZiPchIg#NkILGZIo?^qPt;B~pK6x>MVX|3JJRW3 zqb^Q0bweQB4wzA)GsivE34;|7l#NV_tv$IvlH1Lfo_BzIl4dwsCa)Diy%yA zjhXgs({#H%IQ5?2aiIyTq5TJwo*`1=$BtI(AuXV^803z5bQMezTZ0me^vq$^iO`z3 zQZ6vzQzy-mKjcc^pUH|E|v!T$^sxtX$!Ana<)r4( zoS$m6__~>Ork#3lW;^J5ly4qx8`5jw(Kos#Gdq!m}Su+PS9t?q5U zxi?j9pU>*-P^P@U^eq3yfhN_ke8?&Wa4(HhX zQGV@X>NNcDxg`DOH^S^PZ{u|-K^Vr5XT_W%%_ke9;lFwJyQ0TrUT& z*cgPz9mE-p)AKajKjLy$+6hkrwPmf%+{~+T$!hE3yO)`p8hYM85E$wyUE*Hn=5oKc z0Lr~9$z;*Vc-`4az0)ahowv7zsY+;{Zl@P3skzg~H(@+)T-Cq_K~{)69*+q_TCQ{8 z4)Aa@UOWi3WUGf2$SK`1BLAF9V5@J<$Zwf)aoT_`EJKU6o_@)X98B9JlR6@Qc>yc0 z0;k1#7}1A$h3Cq#q81u?8WZDjUJOvMHQRcX1ENBHRJ`3cQSHpt>)XbaR;Et>x8Kk` zF(xyb3ZKBS*t@T*s7Jpyi!Lm+%!4>!+K?;|p6jq@o{z0n*}0PV&*07eNGJK3i*u_K zPM7rZKe>hUd-nM6B63$$$K7>1YIKT)w%fyRcz863Et9BMYwWrt6dkL*qNeCt>hIv2ZeU!L{4}OP(O5hWMN4YI%MWER< zj~O*d6e_d2c*2ZyHtacvQ%)3z#)RVx4J3C^ofntD^Eqgk?2Lc67yKno! znrWoWV-5xQan`v<8+@KNo?jaFdlkki8tj*8!NhsLL7zbgP|qj{%BFBl6+}DdFll0U zq*`P@I~6H#P@4OQVWIb-{Ii#)E0j*C_qH7EB!qi2m`mM>c4BlM3qC^i1ugtBSn)-h zW72EAqWV<25BjeCS?v3gLw|*3c=wK#Q6gqyD7uaJ#oVjUujDf<*eU#E$7vOwe=9DW z^eaheG%_sQ^RO*QQzJaf;GE<6=G>|SqxqK zE||r^qbnh7^1Hvel!*hTEP(N75qz)8LGbcxS`oEy%LldS!3|TO2V%jUtIGjS3oU?$ zvxr;@Mv96~E&pVXSU7=d6xs_eRx2)gM}NTHJ*r|oa0?rN{LP~QEGGCu!u=boqq5!n zMe*HT6tWZ5y<}R5+H`#I_D!{$mcQmb?k%{wzS99nht{nel%2A@?{)?WMWxhrpd`i%!41=TQ0!c{x-c=YOA zbV+Fl$R6yAbeFzi1bZ1U&3alI0N5CHu6~_&Ju_vT<9Ex7)@W&_Tnr8K1Ud3C8ppUi z{0#aqHkY{(>mBxOw|1Mr61Qi1qtG|i=eS9tl9G;?@bkM{~g; zfi~`5Mj1ghOc=74Dv{JlKzY4coC3{gV;YWDj(_CV=JQTbMb4(?F4P6x90PXi(_?e5z^*an$R>DPPBaZTh?J!2}7e`uSxQ;Q=RajK2rFbzR)aJygs(`V~*Iy1Vn` zUJ<2SGT6Y9B5jj5#p1!x0Mq&ho4Ox5WekQd?4t|QM()L-Vcst~EfttrzP+J{`Q>22 ztWvR@BGvdD8=B~!-*)d6Xjx-hUgEmTVG@e1r6)(6-w^^h;57W$*~r;^8A5c};FUHhbr<9{N)D_&1l z9O!i~CIF=rw1GE_?WN-P&e!6}VeZ(jsA}1#_*PFw^G6O8CDE}IicWIx+r{;T<%<{J zHe6Sa7G9sY_h}mpA3TZwEg0X$$ltDWNsTW=lczk21gKM~-zP2H+lGP7dcWh3n_K=j zT!etZ8OatVJ{qitT8ZkqEoXUn3lHy0!_j6M#3jiiOBx@t@5hW6L8JEC6v2GmKQf;) zex&=j+_d&RLzeY%Y^erig~6j4pn?_~I1Whx$%D|lP}mAvk{#yfX)cB@i;nvuV9v7j4X zE}@_X_<*{`8G0*5mcz*GFU?Qf!qQl}j!J!&)}*+}%LT8TnBaBw^iEr=qxFx?oML}L z_x=%m1iV%pc+*uE%9XzcXMQ;cAyjDsX&&^|>#2rAi2=x`q+E`$Hg{H&PowrwBu)b< znisS}1Lg%}Bqn_PU}jA;O!DrO=aT8ZNov%YTI9X?`d^3AdkC>U3_5Ar-j$lQ{oJjS9W9YABja%yEsi z4~2n5tTD;(y{)j^&=g8_(x>bj2JR#dx;1>#A@Scy_fYmVU7NBT=bxwk?IcA#ej0B$^Xm2=qN55E{_H*&rdA_zteRN88(IG@mt~C(!%LUAc8Y$eoXERZm zs9ZFd{ma6@2|w&?A^BrV7V+C*!f{fJ>y2>5c5?^tbUC3V0bh6G`C}%DzHPOu3=f2|qp7FMX{ajmbk+a(Eg8JL=Qho7J*|l3`crVQ-{Z_{5suNLy|sIh7h&)!fQOFxQpQ~>V04gDIx_0tsTo;awI2}J za3j9Qvi9gbPkoz(OIT;Tz$tuRp39OEOU&yn9meJTzykG1S`D5 z$`E!}6K9jV{BcbhzlfwmcWF0yjtC7_%gE-o6H?CIfD7nv)=SPc7rA5I>aLTSkLG21 zYj`)??hHOBsuYH8u6}t#OCfH1s_*R87fdExOB$dN9`#lYU293@gY_Ma{e^`#hgZT| zw;qp?IFhln$Zd%ok?c2reZSk)(A@X!Ai432U6(x~L-CN`Ms-oR{;kpvr>`-q*+T2t zmO!H|v1Iok@x|$15dezwXPFCu8Qj~(-cfF9Sg{+izqxf5%Z8k_dqm6X;gM6wNqnPC<3EWxf@GV{lTJ!=mGotX|bl8}`gMEcyNq`7{H*6;227?$C`pdeWWk=KA}L zQy+`(rFy*SjMq+k)(x;xNs3A#GbEhIA zu(n>UB$q>Tv9gP=La)Q5TrYo=amRm5AFts60`glvH zH5xj$7Pgpxj4{|YzhYK)Ti?zjGG*A}MH|k_M~@t-4;;*Iu_6AnpO+GS8V$S!3&e-`F`9IPAsR8a11HPFoyma ziqDvHCzSMy| zKoN*t=$u$~X4dAe`GN;AZwJfY5Y&jlD|kfaA1@2@F0zSPBAV2&&o0pRR*f$@H4n z;o8TC*S^lsbI?`n5uk$IE4|43?Fjb@?7HMP*8DC9+H@%Tug@SCTl*uEpbM4%CsjrC zG35yXF$k^rL7>5=l+Q!c6zXUBbq|!B*KA|NSzX@^6I-%MjET&)Jrfq%Rk!*{WXS~l ztCHW!G;-dz=mnQ)D09CmI!`&(xG;7ZSAE8*L2ZH3=6RQz7;T{Lr5K`#Az9r?RtDcd zwd$pf*kqDc?hUaAD*s@WV3m~IUg(s-udE6(;Z^(e7i@dr^EE$mg)#k;Ngf`eMrAof z!mcX2G<91%5sWJ9n_cS5^BKK&O!K`{BnP!Ue(a7{u^KD=oHzi?g9`E9r;_8?n?lo; z%borrs6Q;y6jBlnf_z!_?Hc#Pjdpb%W|NCQ_fBUe&L6imHaZNkIP-G$pzQ{7*vTmP zTOCk^ph_99E%(@tK#s}jt04TVOJ3+W5DYh_h%%>>Wx_+c1L=Ny!t3mEA2i3EwCe=a zl^k0c3OE-I%$I0ICk7tU(bsgzvP0pfn7kfAs`oISDQfUrPoz5K@U44~)p-Xg#>#)} zDP<}!x-KUBZ~fJ*%U|f%@r$t~p*!6l0)l`HNkd!sm!yqkRvx=Rns)clD-_y)=cjbLWB-$e*;KBiZsj#5_8 z)1B1#QzqtwQx>y3lYMcW5_rc9#*EkTxYHo|Jw6VmCLjistRx`KS;bLXH=0IqyNc^%`%0J8tbAE&J>w zDI=q6{bx&Earw%^C;N7|grv5p>zE^{nE3Sd(^ZzQ6uqaSRYW{gEFopYz%7utT0(Bk zgc5Vi8b^9Bq!c_LTI=4cav$1?E*7kKSFAvhS>Y@mCfcf>)`nPaR}FLj`LTBHy}$k7 z10%g>J0ls(I=Cv%`l#g1lr&G+E|wSPfY-!&sQ=!B1)z){cn6TGhp0EUv_8HQOcx@p z2uc(E_$pD+am|~NG2&)>Y}aZYynz+`@=AJ&KEd~ylOl;E(eq0?wg8#Bh0ebZzmc$( z4Rv4p9;#S^dSfovF7D%YU27=N4k}Ho?88{G9WHLPkgpTZTxNDc=(C;KM&4 zw8YapGTj*3it#wDNtApd;JcM@zJ|DYYYctIZQAw%VC$*1_;aS3zu_xOS%rLfKB?V{ za?sK%n+n)caZ<218!|Q4++aHTMQtQnd+I8oJW(|DZo|njH!b$iUp%n+{OSe3=LZ5A z@^V<`ld8G>7llKEA8`j=rA?4!(Jtb7*i*cV2Wln!LYT+S&!aovUWismK0=u7Q3jhZ zwXo8jkBTyT(1+(FExwS$3wW5q$1&8=7mb&4NaDOB-)}vb1FcV9r9mUEl3sqv4%!#X z=AR0~`gOFG&2r?&iVhlMQkr~VO&(+w#1UJ$6eMp*Ba?lfnk6=VT|)R=@+_LyAB+!y z$O3=m(RbO6GYiIb*aZ9@}g@2r5xHgy& zg~*VqHB6lRrCb;rxYZQQHKRu(YQlBDg9vxnlIyqo*?WH;;p$8D0Tf8G%uMn@7V(6f zM30kh3!JC(>HRuP^3za9F_dSn(wRpmmz6on30?zRAT4$k)9CYs3Z-U*4L!}ae60n}A2!2VN^r)k@0C0I@ZZtD`h%++~aNDzg+ z$Ht}3Zu`PK&B-G5f(WCe#mfN&d<{1)Pjg7EUO>0a3%}fa_D=&z_ zEu#1bJ*Z5Jv*`XTmr`55BHF6ktMmXCz!ITQN;ETe({#tV-lP&Mj=$s1JTr$fXkVL}iaO2gpPQ^#~hjw(4!IT4P0 zR1l4A&haU;s%6||>o@BbzQiCk37+X%_fZRO#V%E8k#R6PG#-@ZV#mSK&D!R)8? zxCbd~_G}?MjZ3W^1sYlt72Gna{j1O4pQ>BBPS*()%0X1bXcE39MI|ohMcOtqum}(hxXOnG}+M>7$PrVo7dTNpP{nuAlsn1HT)b}*s`jm`7Q395_B~K?RovDqd*euRCb=4hVv_I3W zFx8$X;*2$FS_1kHm*`MOf9~ARF_MttNvsL14qlt6!G{isA>H81yE* zx^8P)=Q|{F(HJz3e|self3fJ@$zPavpLv~${KyB5*gHMfh4&m%nk=A{|=1<%{t$&aPV48x^; z^)35m`eVp3Y}3C(76qQH<++*oZGl8(0_Yig3bUSq83cCSN2GfRlH-$5CuR?@x3@ln zXFJ`+{vwnGoG6dr+hQ&Y83pW{7#oG?ao!UWgI z5i-+-N&Z|mt3THttoG|%_R?KAOVQIBSU4(sF_XG2ec(DCb~OKCL*C!d9k=t{(`vfL zJ6pdg#>D1NAtPti!P(UuKI2jdKVpb?!%(2~LT7cL20c=J#QoUuEebhJnv0^zCl9}B z4;-K=Zv}BoBznZG!JMx@@xu(!u^_Z~&$ZVTAR+iUY1DzPzi>xbjbY81!2+N2g=o#yV+$wA}4oylHH`}?3<7$dNab*i*s zxzD`T-ppI@81F)TQK9M=Wd3B2lyP^jNj8DJuAYbP>hPF; zJf+*S7|Xl;I-{iu*_Lzz+zQD+IoGX3MDludO@JDY>v}QAvQy4DM8Zb9m|SnfnnC47 zRMg~%!pvpf9T}81U`ZynD0thC9-fT&CocwRomP5@=LRM$6)mXKZAV$y%xV9 z-7Cw8nK@|lDKz}MfZi3i)I91ntC`zsgec$o{i}rL_)S8nq{#sj@u`DR*psV5lGs*I z{y!qlg`(#Nl0SEof_>NMwy!FXg}v|Pll6v$5WKc@CTfzrgTiT|#Z=RUw8LqOF~+7& z)mvUnsqxc0t98&y{f{c&qsqV#?Fwpo1xP}_3$)7fYX11F*Cn{~Fg~ZH=!xVoN`Z%JBA@-EV2`>}WwW&ae{} zFZIGgCvH)WH+jW$s!}Ws?7Sh1Sx4@KyrjEv6ywP%c@+R3a}Yx;?DZ*zORj=8rq1Js zYX@CN`P7-Z9)u2V`pS?o-<$GuAL;=L(Ohw6W2IsT$u=8^{k!{4W+I&7Jh5OIG8;jG zVs!s1E_Iyt)42ceP z(DTVrl$1)!&NJXvUXiaF+oK`RC7~b_l+x*x*8evySWOnVV@cn(cg-IxA*c$ zB~8s4u=pm=j@kY09`0)B;WKgAq{=p}YoRM83}o(dv#l?sGP@?h#TjFmub=!;@p zOw<|8^!BErq8&!`%6HGmCo3)Mo0<@j-ttbj-aWZrep8;IAcpJ5s%6}b3v`pnvE^@@ zAfjz0l3$wN27!6Z{j)?Z6>dX$wlu4h#LM_s>{N*pY>8&dhU5iN+%f;tyy<^BMi7@< z-6;myT<3&K1n6(1!=cfjNAB>a=H2kHESElra~GCC(%Ki1o1;j5y$$c4-Z^x-1V1F= ztuP523$(tZ_I06A=Cs{`R|GU&!j~zNjB1>B7*#S5r03yZh#2dHdEJ*JfL8 z3j>>KA=x3vtDtHw;;p=!*;Xe5a>W5on2tf*(&zE3Ci6j{tmx4E8J>OS)t2NBx~n_# z0p^(Yx>-jP40!#CUr0PW@EjNz7Ii-BwI{H!mVHtP>~tHPdSZYB0{(Z}jxn~l$RSP; z4XXPDgo%Q}R{hA3SY(wN`_z?eI`3!NGN!!cRkz%}z`%BjJsrXWA06Pshu|X3fNVqa z(_0ttes0v-Hs15%>?_62aunCsoB!K`;zvB5|E0Oes+oev+T*qr2AhU!USQ1EZvO&U zuIG6Y5I9Z%+25Rn5hRwtMlQ&}(T0^t=FBkBabR7kKrugcfjlmaG(VCyKi!6#V`Kv~ zha|cP3ZzpfiUsEID!^I@gR|F|Unc_w;P4y_Nm*XJOX8N;I_=7?i`)um2!aG+O?<%R zL<34ZHNHi_xAwy#$lyb-lK&CSSqlmPKe*h%cwjE{b43tv-(|wfuI~(<8SDZ)rkTe7 z)ecgw;9d>~My>Pe|L5fI|DN?JUuNNV;F0j{7V?6^ucVEiHBR7U( zUWFG8pKHJk)L%CQvM6FzdsisHhN~{5=1R1Rn$*R?%RBmEuNXp~^&IWUHJk@41-d7? zyg72}mNrL_?3ZG6a4JohUn?~2>Mmd7sU0}gCP#l6XlF$unp<0+KU-KF+viMy@CMP+ zF$WOIb{2H82_KN+`sFWHpXk^h)A4_OYEw<;Q6-p%$ZW_x$<$MhK!o>e_+m9eV z&6C9S%L6u{Y?HTcH|5C+wp0dw?1$C4UBa&T;gMZL1km3)Ge|EkWJ05Ebzi@CgvQ;N zbl>dfa1RQVemhN}RVTe+P|f2d$WP0h4;FA;NuVZ-cizI9$AWdLjdm!Z?*@{faHWpM z0Gl0Oh6N#q1odZcIY9Q5Uk-wv35~f@C}I|Iv3LlUq*wMa@*@B`C|QPWDU>xiQ{uG*$**wpk*6PB2)O+T3dBvQMZMy48@_S#tum3!OE0n;gY-nln*Y}? z$>x3d4V8o+V2=Y}^CFkRs#LDze6!5LpGZ=!m`GV3mR3SQc9A1i7I*;ju=DTPLtSiK z5~>g#V6-ze0^ygMx>WY;YA{K)PH?{3W=*ow?$im%-l9*FBbK-xJz?=B4ZytSg^G>MMANw6>c5>rXhWaO7q~Cj41alCLhHw=J zj~GunA*4OCoHydO-;|(>!&fVBtjB~zbqIcL^mh|ht*}Ri(t5=%LuKjTa3+RnHe32GsC;N6ATiC;*`r)Ho7n`D|wpC%8@Eckvhf2Of*PQdZN((p-+^4#C6GJ2w;uIFo|V&?CW@CE>C^^$X; zACY_}WIUfDF3x{bw2Dd=!WZ~4mOlKbBFOhnkdzVcI($AF*M_0kSIvi}X<>X8_{2bW zceMc{g%Lk0R*aulJB&5GXjSA*W3Q-yx6;;GNG{@3&8Ie$u{82e+>7R6O*OI@+*Y-Q z-@DrvJoVDGWTtn}-k)+?Rv!lotw^r_ALPAhIMjdGH#}02rHSm@RFotn`#L0BLPZoY zm3^;lgE1pQ_7EaWLdceN>_Zp{Df_<6lzo^HW0>WC|NhtWT+eY`&wbx7?$`H=Ip&z- zm}B^D-|u;T&d;(}!ML?yCdwK2G81+|W+lgj;+J$TZpGVo=K2kv-xBSYSzY7lqd?h4 z=ZoqL-2q%$;vxK2U-PBjcSAYbLKlut&RQl|N0v1EGQM+b@o#z?cm$m>ksi%i!0#;!(Xw@Z{C~ zVeo<=TqVInvE(&RzFO1u!7r&C1EM%n;RPKn` z+o&rt&axRe9^n?=Yp1IDq9_bTsyug68w#S$c5%C~#=C;hG}iI!#jfceT--@_BVz?1K+mb93ZLm;l00DU^**?6rj7rK4_x45gY_@K}RPx zJz5%Y3Cd~RF32*mBrlmvA^Sn=456j#5sUEj2$oH2Ye&~>50)gR#9tuWo^MXhG@(Qg zH%YSyqUq_ydNfn4?Ot;1brZU5Mz`Safp z$!SQ>(UycTX|ZqT*v8TR;OEr!)JKcGFKEe~Z~Ar7+>Llb+DLSl_8$NKE482VhF<<2 z!Qw;Z5kzfXsTxiGtD=Lq%P+D2QQF5xsJK^WwmH$o6mXca8A`9r=epDxSq}^EG_o3@b`5u#yN_T?>33@;(joYzgFBn z8eR<|HYJCA>+0P501o^3rE0%CTy=tSV3q1!K|CLp?dt+%+c63qupv5|{-<~Ir}s@+rB3J9cXnx{ z4iCcFFiUKQ>w3JuZu81$izUa3vuy4UTY5gp);~wmlX9lH(ZgHAMq0KzNYLbAs%8pP z2kYiX*68WiE`k0IpK}=D?WT4mMagv8O){<04DlAo)g!= ziw)jMs?7n`IF^?5NTfGnK7h8*G&32;IYqKbAixk(+VZXc)I za{?<wNnE#9a;Pk~&zYjsj#n7#S6%eU`jRktKtB%xIj@69B{^)V9N; zTIZ$mBja>Vz989VeLHPtCM5Bx6YrDz&9WI?)pO%b?v-{F6WkOr>cWSAKo3-7Y{dSi zCKn<3=}g26V5+0R`~?$3UZyLOs<-r)(aHIu)U$X(@zkbWQ%l-IKWtq^$!YBrevi`H zcY#4t?03AS<3UGw1?uH2`dPkc9j%F7#QDDAAwoJ-MIx`apwfAFXBn%Iz_88Uh7t z09|MnQkoiu&r0dgq03W$(auuOE@KmPPr@|`4+PHGSr~5W`+vIJh*IiN`>jB~9UQ*) z<2OWA3C%(RyqXhX;@a{Zsw61=tXik84C$urWLBt6TO%gn8rm`~z<%GEoY9h3Gh3T7 zpPH|Fhm8m!Dm?c_e~Imhc{q4?mM%AvI0Bl6JFRNk%r@)8I;r>yvN9qtuVc zQHjfpGVD?ws$M#d{*c53$1YqX#iS}kS0iMFp{}OHPa{aq~_?(mUo% z>$|=deZ_|rkN*MP`3Iy1f;cjY+aNSk1TQs-tUw8)ijyS)CT?Ow%1k+)a|7eZ-iR6Z zRe{Fl=#9uZrQaKy^Z3%m-2w)OXqc53b`U`ZX||&XHj)`2av&^2UoEhaR(mX?mIkQE%d)Sq97VAC^!zTQt0Sc`^!N``LY_@3l6xcw;6x zAwa%-@(mq9ty^A<#Gcaj0CKfw@;cHBscwd??agEIQtYWd9I21FzO9)4NxqhfBWCr3 zoKqamgeboD8T-6+g|Pkg1J{X-=PzGQRy#&^@G&qbmjvugi75#Z8|cUDxDEwt%T+DF zQ+kh7-m?G^Iwx{9<;#3q0!Qk3%fI>p1W~_i$PmYyuruKPMe3z2qCUyY>=(d6m^Guk zBpYHQ0)dvo4sFmdrm`l#%@yjorfE25=Tl7ytfUWk@3>JqD|Bgf0>@uIkhsFYGC}SD z%yuV9i(nh=Tcz!`3_U{^MzIY*W4C;5R`2FgI~;5o;())caXRAecz2iB^MZD~;QApaA6%YIQ~|+_ zU+G>$NKc?Vh@zB4sw7Pv5jH}~N8c`WRw9 zjpxoUVF1MZ1d7B)WW_@xLl=A2I}|oF6~31LEfF_Z))~eek&@*Vp$1hGegj{|k389cb0?Hp$QeHxXx_-7d>KXp z5n(_x+LX9Pvg|<%do1uxAkJQ&DIKdf)mE$As8Q({5*R#`k;`j?wY3<{^K_nzDeX9m z;2r}QT-ez<$|MQX1BqWeqioLBxo{;$?qv6k)-Q&m{Cg>H(&e+4_#P<@p9GSp(@9PQH{ds?;PB6n{#}u- zc9e)1`ing!(5!BaTen~ggVtvYI48>FbMDa47zElN00WaH|ZzFpK6&%dqpL?vsRF# z>1Rvu)!nlHfCRmeoQ&E)bKV1-NcLnPY%AQjyJl_!NUfNiQO205zWSM=I9uR%*=VuV zQSnW9cPFGai)ao>KtRhjP>sRyZKfseGu4zB%*Ss3g1HBtGj7wxuO6I_>K?dkS>Mmq zV|vxRL-jmmNdy?BbV&l>7foEQI0XmfZNtwu=WBf$`zQ>@XONy1g3g4U&U@|%eC2oq zX?{Bvzc|xp4lI7yxO(VmghRR>X~Z*&TO06n7L0Avc_zo|Z4Fsh&~CCpI!gzIg2Di= zvFTwZ$Ne&Rt798Agyu-~>UI=pozR^uSexm>(k!xzIZX;^$I&jN-)rC*0kg=P)y%pb z^2=^d2tbmc^Ya&E5wVTvn2Sm0n=`|_*xD5WDt)f{@x~#Pepu&U|KODV6YR?Xb}d(h zo@E=+h{t?!Z&Fpm3~SdkYWWz3cL3+q&;m$O?2| z2(na7)F5{+PrlSuf+tW~sHs;obN0vV^0SafvfUMSHx-uMU3U?hQwu@qagsuJziAw2 z{k=G4NeN-JbeuD#-GYO=(DktB`aIDtps@5?pLTw88;bU@f=AvJj{N{!}-aB?18g?sp z%hqkGih5U#U$088LZt6$t5J?Ihp8xt)CF&el0@%C83Gs-Zgv0yGOOsjxJVfj;U2Flt6HE&j=$A@`ULevCD}PHYlr2G zdg$Kp@n%K?bg4vFp6q{qtb-5PM_Ii=+KW192`VNZcd(zY|57u@mGBXsxqMx__PAS$ zBbdqIRpx+DW^`a5?{|HxQF6-i& zgHmppRw|D(h_jCv1@HWY`5BkjW-6V0t83fjfP*@XplRJ)aU*naVhy+0mK21UnDsUpP2Q6oPfH)Sk{BjtWD&U^u7xa*Idz%epsNtob=Q_@J4jE;k$8bb^qpC zBiU)xQYuV1Fqo7DEHGyHtCIpuRmOXj0ThS*)P}t7bjWhEa=WaJx%2KUUae(kIwRxd zH&#~`>0#&+CiBKWWL zNMs+%Lg#Cs8ZR|Fq492gn}^!0OYT1ThT~Ti7AC^&d@T5FZtgOBiydcOUd75IEfRIF z0oMggl?TTPVCyF%xXuS!av)3{v>rBvxMiBE2rEOtuv_4FTOXdTooz6dOqNA;@j{6M zz}4X^RwrkHEg?ID(*z+^wF zh5&%Uth?#oTUZe{`z&$CC(AqBC;GLW4I4sMk3~QOR78sb57Ab37lEH<8h2A#`CN^( zQ-4{n=7TO_vl*;iq5m$+v6N9NKvCrhq`hV2pxlz4)6W)CoeZ0rlDl!5@fVXnty=LO z8|JZ?z}&PT+~sTKC>GpxSv`Dql-?6Sb{tcQj`Y5mx>#H5edn^a*ehVP^EL39#5>= zAznT>RDiD|e|>h9i^;iowX~Io;`eQYaQLn1P7uh#MBLI*_Hdu^Hc(U{5lXU!&J^M; zUv-YPNiJ1`F)xcI>&fp0$Bha;c*YoKM8*l(&-;9rS$fuXR!;&7bdc^;8JZEWCEH+q z#5sXclrms_wYC6Gst#~aWriDUaY_y>mj-``q>S885cHSf_&yug*{8B^Oaz|P<1eXC zfNNj_tixN`!R3f!)avC~nT9+O!dEqaV2d9PbX`H!no5+;w4$$rHl9Us=#o_Wk=@_P zsIG@tKdno_0izYsoh{}C@(-&JQt!pzvDUNw06E%LA}8s0fM1PeLg1`82_PD(z@ym1 z&LX;WVa_t-y0KuI+=9v-(*eQMiEDg{7n_9mSPtW80?Y}KNunW59C5RO>~EqgO;;WX z^gBiUQ_Vgz)1FtI^EW0ji)YA7`*_R@(fZ6p_5ulzqg{E(nRvd6<{H6z(B4?SW ztO#a0r(6LI>*n%7{Jw$^U15wkCi-3nW%;g8>P-#T_o1gpk*WcqDua6*v zm(XXZllME~hfPNU?2~M~njc+NG!#2e2=d2?e?N!QUr>tlIs{RJOR0VM5;|W3#gFk3 zVb8x`6-B1u`EjjF4BhPz-N67+FNZKQ!Js&?D&*i~sN7ed)VKw+5cm}@A~fM2P~)d+ zfWY%FuL#p=dEE1!G!8Z+Xx%{dz_)VRN?ZRVe9_FC9|ssl3lGv1z!f37j|VqY6B7ea z^^k5b81Zi#yYvpijnLqjT}KMl){n}4oUM<-;DQCUL7;hm%TYk}f7HaP9s z&xbf}EcZP7kf@}k)CijS-19qV9~bms*_7~dQJoGLY_p=efK*!#@Y&h5(A6$K5H*Re z=1~IIT6YW*efqO%CzJ>TbO}BnZ+4&r&w23s^6z98Co-!7cRU0(N3` zJYyDqD9r4~t2%rafB5<~mzYGvKOj+E1+op6Y>D?R_bAYrt=D|pSa$xBQTl1o5bsu1 zd@yf_20&MEr=spMJ_33gd08+wJl)~fbmg#W!QB?fPUp0`M6A%U4smj$KyH~euIb78 zqHCbKgGv z)-2^cRi${?qk-4#{m`S_lVEhZi3_cV>7Z##IOw%Zgfx5{dNztMBRd&6rzYqu8|kTW zH^uWmdY=2nB5cXF8-Tiqv>T_2;%Rt=c!59Meg)-fgC-g8OiPRz z3dE6VOUHxniN8jUhxEIxy?C?e(F6KSzJLMcS-k_;%a&f!Ci;s}$+asIGHR z_iq6SNeW$y_28|)A)KZyYPB6?NXFmGiIO2LFEBZRT+dXu_C5EY(+ZSRj1JTj%LKti z;SLD3da1ex#b-C20B0Ff=^a(!G|Z7r_vUi4Y@S__@oUQtU<;C^{;UNsc-{3UPrm@A z=hW$&ujES<=jN3TgdRT*BOe|eyn=cGmnOp{c|joosSwCjIN)=`s>vC2`srGD4LY>y z(S7z}dtrW`Sr3>`Fvjoi%nxuDb-a+jj3I{N5qoDVWGb?!}xysH_h1B%;6@y(LK7)LG|d))L$m2KhTwoVsb2F0*B0z{O}Ft z!sU-Y>Gs#J9%UBoI&In9d!Soz43jCM=PR8Ek&B^cu+i~A+#s5O>WMR)jB99z-Oq6- z3?Hn#{P;%hi?*nK9gQ8B3ted>(1Eij)Z4BtSk+cd$(X-*P9xd6<=#bOTgWxZ0cqL2 zb=bvs{x=u8*?pws$&C9OeaTt;@xxi<$oE$-Xh| zL~9qGi+^Th^ei;lJbVR~RPt_F=II*X{r7QtyVxc0kX{j{e1+zD_jATutk$D&zPk>e z3Jm$~umCT%B`95>AR>Lq)p(v9_d{#dYyvgBSDoA;BDSt-JTb4yRG|RhC}j9`RtmtO{fXq9+JL`Qx_kway{+LS+>C{NcQ@Q@TaNK`Ka0DLlyAVWo#_MplsOF=TgU*9M?!c+xwNbu79nG z*FjT+e8X$n_LQrP&ux;lhs1J7`sb%FwvRl*Rj%^g zfB##)RSKf|LUvbS2}TV5z#PIh*b=)6Hej60^KhI4jG!^LM}&2&Qm#J;ar(L-fKl{q zZ^gVQE_g#PMLJDZBAEzn04Cm94y;^AU9#l#V2X_RLJ*B20Y;*>Sx=?kogMTC!egTn zPv>I-z|kEiNGruv8#%@?d#(11Jg_`t+jrC5QdyJ8+t;#PLT)C)1|lzhn_<`Q1)+3+ zLPfBrQknoGy3j)DdUd4j<|UdR5B*X{#vUNSG5F)zXbYJ^ib&i9f{a6m+3`2T*d+`a zaHxbc%pSm?3k61qGji%7G56@))ViwSkF%ROZj^&qqHqoIovpKp$e&fgHNyRJb_#()_E282Gzt;S|<$&NR#3;g}A&5#4jB zE^Vgh)W%HA%jy)a+4Z}gGmq|OkoNy{kzD|`UXg-M+kykWqS9A;EKNM^A5i&K1KaK6 z&|^$~!(#c|1&$HB?>@FP2cq5V8DNGN;;K-8sQ)$Oy`IBtXuz?SdE@1SC>00$8}j-a!?wI}CQdKfbm3aw<3Q0~i;sz&sU9uKwz%K}PjrxtL=Mx}(~` z_ypNrvS6YD;pZoSlsYWq!W?*oK5gm2W%S^D_^&Q5w9K&^r?_`hws>f=$dLv4pGTKsO(3}anj5NHf-y0Q}eb8<$daEVfrd7@E#8?O7&D%yfR$~c%q~43ZBSgM> zNu-GcSXJiZ<+af3JoRP2#hjAaf5;}2{JK-v;KvqiF{GGSmjZFr9nFqvib{amOq8M6 z$&K(Ihw1(C>btFa>}}4j5!$gm`HFRn_Z`QE@vu0FD?ay7$Kav%?N&W4rNQ~ku<+^D zc7aUtim8ZooMC%Q)GjX?K1o(0`BPHHuwE$YRl#m>;)_|&%1=}@-XP`lOut7!W~QdI zjUf3H&uYqVo1B@aY9{);{*iFzZkrGAJRtOc$s-i-43R~LUpx5SovYg!Il@kJLKX4i zYL{-1^WlU<+jMFe#fpK}maSN~+l~Sf;#KW%uh}TR#a>Rohusadz%mne?!Qb;zLEPo z(qsq}&f~4<;|t8q^Z*O;hO%`K$FY|YA3!=FAyyJ!J;-)7mgu(xp84B@=Ih|{Tm)9T zWaW9z&LD~NM%3&gPl&f*Xv|x=2Dx7k7?~pvZX^At(2L&zHO2~Vju|`h%yM+LU~?U9 zvB#2C)E6$1=W@E>dVe^7n_0-!kXflNP>^VZy0rL9N@*bR%UaIdtIs7Aiw~#u&!Ilv zI;l5&{0%#3rI9nSR7k@c;;VE!pMCp$&Z$&KnKy-nZeFrn*L-t%)~P?)2pBrA!1a#P zjE^@{eKI;U9b0+Cei>CDbcz!{WZ#b`usu6|7C1SZJF|t9K))HYCFyuNq{PA81>-(@hFfNruGWKEm6W2U7w(HLEMhf zAo{yL;k#MFMBD|Xrd59-f_47}NT_NoQ`$q%K;pVOPV-v->R1rq*WZBvX1b9{2m>;B zi{MAQKEKA4r-suW%8kdqY!JWjbyl#}!EQnS8Ny(B-C6CK>f56L8P509iTK}hEN9GL zg{md*Cm9g1L>&^k6M25TLC$u$ddtDfi7blGY%ZT2^bM@$>2rEsJn(7Vbz$14C{7Mx z=JA4wxZA(drLe<6w4xEp5SOUW@d|to;oWv5*Qjv3`6Y;?<+w|YWkZ+XyWOO;syXy^ zW33{tY3aNsT#@ccQm!SQGXZsa{mUHn<@e3AqSm|{io|F|EtYJ-Gq`J0f7ptF^%TmF za)+K^&OtG3Tj8JRr{Z_)esyrcsnp@#nbj%3C-tMsRAm@19AWU{Zx?tc8dvD47cCxC zOYh5l20h)7GArHr4+w)LE|wvGAWu@m@s>pN>$}Dwfn;9D+2Z#DjOb<1xtef}FejcEjsU+;ft41DmnJgN z%UJMw=%taUb%`wjkb2&iFP}EaPUgS>+F-9xO z2PRH;r8;Lpd5BEW%0tpl_tf^uq^h~9=4EGr(Bt{%+DvE6o8NE_lrl|W?MYAtuW_ymbEhciW!9jt5b9rT9`${}v`_ z(B3m8^vT<1seGs}7##Jj^v=a>ZP_|F+bdao?K2ahZ^MqV`)$+Hrmf+XSjn(ksq#O& zll6svuzgb8|5TZKy7(Tah7<1E&!j%KX+Bi4<{h>_(B5I{<^AFA>X$93cx^*cYp;X= zwTrNlZbX87ZMS2GJep8B9C3}cXjB!%sCubKF5uU)_t`)079dQq98ZB07Rnsz&;}x1 zc3z|FclxU97ZBdmaj69vvhJ(zpZ<2`xTU(6EsNT?!1fFst}I$zo)olPox|>sZYZqB zzD9~u)p|oyV)voEaC@_H1-b@`LOi{KpLPz{T{2L<@!*gJ<~$P71qUaWBo8fPXo@ch zuk8n0x#UYcgm8o4@ zuM>th?pg#0uC)>+pWS`@0*fEeZ;ZF47A#C#^2tzUQMJ@GtwC9nftXrY)1veZ0(lik9F)*K z!<@1W5@gDea3#wR+Q2j;2*(YH?TIl z%u9SjU3~OGuy!-JQi><;(dSIgPuGjwvb(e&Wuv-kraqtq`Ldm zK*Hsu%FVN-aQhz0_Yxb@r^aJnbE@8rTz$it>g$T}(-j`=&%b>>yh8UwZGC+$Cg$si z5RvFZFnJuQ*ucZ1XLv^w)W(vpxY^%$Ox4(-&3`R~Zmi5gr@2D_j>pnxR9)g5x(3w+ zAO`L9DzHpTK7a?&`nd6~9Y6Qzit#!#h8c;@xIe}H4?BW!x0b-RjCZJ|_h`QA4y9@2 z2)41Lr_mP%zxjIkV=1I>sbpqamf97mnjW-Xx0j0X$}_DZmm5xTM?`{t&; z4-I<+1cUC**3Ne6N}kdsyzUNF^E~_qbdv<8^W(oea7dG}UwJ+~R4Hy?RA5P2aVrg& z#G7_HjU5l(d@MbDCm;3{8)Q1ynsq#oO_)I5$z}54l3-qpkwVFreSqnL9wZ%&k>1K8 z`%^vuvs?}L0n`(=cx^VaL|-pcwxzfGfl8Z*QHh&My}|bol511E{>tUefgq?katc+) zjU}qk6yfM4#&o0tj|0_pVYu6_C*I6G=l6vX#j?)tX_~j>@cCk|e;>L)FNIXsU2E2M z#z0|%Kq6_5(}EyCwU6SP1^Q zbg_K%o`FiPRhIkX47|NmUE)rXm{92BWAL3R2*rZDx;0Jkn^Ag1j-JmWVtQe(t>AUH z!wlbDeXg5b^q}JAfS7YCMOaiaSO@IMP+yK=vs@=q6H%#pAYLQ?pUl|5zyh}Ufn%!x zb5b;;V;9{L{`z1i3ibxufT5pF=yg=1Hv7|KJ+Y!ZTrY&0Dzx--WnkL?ED`lE4OQ+}Q2htO5?_nQ9I6NGssFeXT z8J{h&_94NVC!a)=)t^npP@8u~u%R9DaPU`(u7qLy_cQkv-Th=(aHC!4!jkiwG^%_3 zsF(hfqFztlHWCkvI|+VzD>s<3dKkSjwC?m*mbms!;Z+@Jwh=`g_;l#54~0< z9mvmrg0#D!=sVF!WGXn}{SEu0i!Ya(;)vy+m?wfi+w10RqLfHV1Z?D9G0ij*cX2r9 z86U`Wj%Aa|2n(wI21#%{l|uAazVq%|##HTv#H5mB{he8|*k4#HT6E7tkSO|6n%7CP z^zaL^jZlK|g~_Nhmc!Cp0na2z5mNar9@}Oi|-ppje`_|ERiHFkzqh zw(}M|M|lJ1<8PJVm%YKjEjqR^n6YaC&NV!dF=@Z=rMFH#C~3^IZ>Ykaw4afzz4mll z^t{EKSdh0r23utqsOWL|>-j(B+V8$~c50K@V8gpV6#J&;uBAeL)g^`^p%$p4@%06v zWUtNoZ&I={;<)E~I!cG3WHWJ%vVs2&H2kS6PDtK8UPdnA*0#~00+ta|g5*qe5 zz8S?iWqkdnSoGx^iKly5Cv*RMRni%H{W1TIP6sa*6GN>BDrB|37_ zG&hrY?O8CH7E}W37>!0d?X~F_mlHcvZy!FTo_4*kMjE3XOq>gw{F9AT5RAh&4cVY& z%RVD_y)r6`ayQaAs0mb(k)PGpqO&?clFAPMX9dpnx01$uQs~k1E^0(Ut!|idp zn3O4QL~$32t4g6uk@P7ZQQHRK?Fq{a1Hrq0ouU|-z06AJJU`tQ)pdR`n9i3=rz>=+ z*D57p$+m{kzun3<)S-6Fu?s6IN62h-hAd4qdi}ewxxoBrLw&=nt>5aW3FD1vE58$b zvxwS1rrTKxi+{tWdG8wX15#_gyM~~DUr5$}*~hFQ*kh#UXra84QRc;*Zz|-;?%M#A zH|_kJkq4~|PXbozQrQ&*XFpE+X>gd^?TP;geVfd?GywU#jh^@~d2VJ$|A3-)*N~;DJg#vQ1i$A=6 zt2;4oag{K;WkL>^Z5VAt3e)Hi#dtN!uv}AxwqRk5%*v8^NXlQ8d;jed!=LhJ9n820 zX~rg(P_vltBQ|8xRL5NRAY-Vz$jxHvxJSSq(jIC_|<5HI9@BkDJaLWQfDss z!n6ZHi9cqKT^9;~0sF;|HnFD=F2&TzoAph!i@5Tl_c8U! ze?9vtPbbgltr(oG$Y!_oVrkXjUw~)f@&!o|jeb0vp~y7(m0h2q{qOd1&NDx5C`Vhr z`P?V`gD-y!D48V+m2I=ZiC9j~T8KE+onIJAR>n}45DR+>I9T*{b4s6t*qhAzGk{n? z<0;>>M{Za3Krc)`fNyuMzf8G(Z|u3b*sr3RYiVkC*1qd=Ra|GMNS0K4t|0E>cTU5# zOvbeJ@!^icH%FAeA%CI5!xb`4)ay@N-_-?^uaj7`lP|M={@Aq0PF?+q)Ex1q8!U@^ zjynF_$qLLbb6H_z5;7fXlz;UMB5gEg1S467`6o^_cNsZ8?{^FbLWM`M4Ip#Fkl`N7 zcwIKC?egrRAYE_L2LU9?Yf4^Rs3|>_RP^96yOST^ZY1l;{uH5|91@hBxqzpSXg_fXmDmE6*v@wUs7xUhBIOkXt{xNt?5ly>)$C48E&h@KORX^YM54~bSN*wb9 z)vLf5Zz&E>u_XOSlKX0Rky5lPb!o+dSNxZ;3|CKt+GVlikPl#^k9EX-S+8=huqIK2 zprWy+`r7#n1F521M53@sAN1PNec2QqkDy_HE=*9Izb~s?-NKBq^0!+_YiYNOxYF3e zOQQO2f#wTYf7)1w{y=Mfo4}|}x+IYTX+$)HAVZhTTS?O;{mhMW0;}k$c zRSymE{d?8KCS@By%DRs7P<18W)xZDRLDnGFC42*U`|(e#wyoZv7B5-yQ5}bIErM~%l?L6m6Mo?*%ejB+8rb1R3Ws8y z(>>cPVh=*T=PTaV<|GF%Pxd4UZFX>z9g`|O*0pAa<=TIHo@=@hYPe9(3@-^Us6>wYyxkSo{h1=MsBZe!sg6~dWo^$^5DzG3s8*et zCl2D<+^QZ>{kwC#Wp%b|%1oTfU6RsP^9Xotq%T^Tfjju-QnMv0^tNO4-em#pE{{ZmtU)Us~U0WiT*3;@mTDrBb zH)dQhy6D_){o>o#OU7Sg4xM3$7Ykg}R?-0;M3w{?2Qxt!aI}ixs7n7e+=qp%w)M-{C1)y@v)mSOQoUz3u=NfCdNJ? z&j`05B=@^1<9B-lV+O~0J>%g+jJGI>kL(yunk4`kiwT-O%<3f=-@4|Xn5@qoDW!Av zj=i-b6!lD3)3(vGT$1u8Y&#qvik+;y9Ly{@S!(QRQAp7=M{nTqN?D)zXzL&ymQ}Hd z;{VwF{&N@nKY72dCoFE-c|~7^4j?b)(iaXWI1CqiihdJE;g_@SZsKg;O@|#jaM*Gg z3rT-wfAt?$CWoWy2CPvzQQt#ZG;j0A-8LdusM`m&&B* zM#ivC@Kx6E6yeNbQyak;7L zQ*jfV(vPq`p(QucW^af^S;4u}v(~(p60y}E`buif1>&(~)UY@plKyL2@S~c$zcv`N zjDb8jZJKEE98>F(!tqtGt={Ha6W#RZ>^ z*cNZu@2h@f`~BINjl%28<}-VnM(Y8ODrFC`vUwp~F2u5OE!=n8+QxkBWWhlRomc$> zfbzWmT9`;b^}0s;q%`i%?z0f6o-EBVmT~U|Slrm6&6a!~APpvE{{vcHbuR|L(>>me z`NR%T_`aW=+VA@$OZ(SS^{zlDk)QEC^`7;M8J_8BEyp8!)BwGeSrdt=`DO3-zIO6x z|6l9;Nv;1hFZ#d2w1_K@D^-)V{tF0VVTb~wNOs^DJ2Y;GDznpF9j$pHV{73YIXTtu zB3WquK%hnnGyZYDBZ%LYg)!U~Dx;NiH&uepgIri*%)DTJz1$kNn@Gv5G znLiXa00cZ<>Swm{n(GhAX6DaHq1)O03U#5oJ&*Uk2|Y$qkbT%X+lYT<5Rps)>XW5B ziBU@*HH&%?;-2pFaF~tasf%rS#X*i+swl@PaNmL%_srB#y$F>>u4QD>|`H6FmJZGGpuU5Y9f&wMY+Z(VoEA>563%RBqQ_6=%>hvO&z)d7+yz@qt06R{~+(pqoMx)|8GK+ zeaX%!N+o39jS@mkDP)~QAu%KwjG2&q3n7HwSxZdztYcrY3=tvA%!uqWmNCrI^?Lul zzwdQEzw4ae?>guD_xj6m9EUTn*K>K?ANR-Y9x5VNdGMj!}4@!d@fe^dqVW5~v%PQJz2`@T=zM;_`{l z0oQ{!^iiQ1iEVgV4L#?paw_@2C+VLn4li%f+A{-}K%J1EU~*I|CBu6O5|27lrJ)?H zdv{&>(s#77;!?rv6sY=S#t>ZgY;_pyS886V&u^?C+>>#tmL$BueO^C|)Og#{;NFu{ zAsp4sUjXmxOikZi#dmR}$MbKt9GsNX#+F!)(&j8=Q9hV>nhqT zR=gc#lFZzgpNSp5+hl&*5G+Y}XTL|Fy3n4JL})@bjMa{UQ_JD<3 zO<}%I8CNK2zEcP4=+l;N-XG2B8DwwYRc{RmDMRC+GUo`T5gqG!YE zq5K=;&Ir*7fyJ)e{DrnFQx#WhVir*Q5l_EZHD#%D$=Vv2vLT(2b0FgFMT-1tO#5kP z7Z8*Ak~Kg#oB<+{i|F$>v5 zQ(XbmO}JMBs9`@B)(tzg7H?3J>wMn?x53Tt{;ktTeLAHlQ87ug zPr`xyoyTe3-~F3AB)yJBKK(N|D?#!U{gb}iN18rSXvu4ljnQ3kAr1A=qu~B?AOH+0 zmUuhN%Z~IjJk^%RBj#5~3riH8``;IacpLlk+3J0wJrF17bGwl^Km5g{6mD;tYTa7x zba2x9jBj#p?x2d?t+~~V5RRFrOcz=9WAWf_Fc;z;@kQGbO7v&v=YN4e_Zmc|>@Pc5 zS_p}?+q~kl4dLQ3vo=M=7IGoKVX0?PogPSW+LgKx`_AA(!I7O+07@k8a=c@MHCiD%?5kAQbYVFEP%Q zmHRd|G-~%fNKA#yqt9qCompvDqcjnzIL60+fWAG+F#E$3X}MNmagEA@ju@wnBFUPg zt7mOo$i@o(FK`f@lWmvV)d6C~L+mS19X6FAf-s^)a4$FiW$Kdh4l3lX6EqisdfivX z@sF0TqlGoZ@?JZ=(CO#}3PYhm-Qw})L9PU@TV>~rI#j=qWihM02teUPj_Z1EZV;V% z7)oDV40n2L?i&=ws&h6iTso*U-M+u|u%VXoirz}{qx6lF9w)RVmkHj-$f?;0>zcYR z_4BOa&jWg4;*#458vGI2a%maAR>A0%MVinY4z>9kC?{UMKpN1CG@RxYZcrYD$;a)B z?a#$MXx43+g_O=I%_VhToG({DWekx- zdZaJ@sPgmMcR9I^*SS(O86+|>oPG7aiFdKnjHa76YZc}atI83C3~+JgEPGMa+f4+p z81STU%1KD6b&&?Op^&AJRvW)7^FdB}aP(I3WaZWe8Hl*iGEB^qIV{zPp587>yG-#W zir~wV_1#IACwfp4?Wz>{yRqFo7z3z4Lv;T7MZ@frS#J+ z3Xl{IJi8rZw@A206suBrBYvloYM|AtIeWt{_rgcz1VP6il~#!WD>solb!K68pi#}O z*rexaFd1Ed@!NSBxUpEL9HFkvitr#s$M%54fYXPVi7=k1)NoM+@lu>Rh-<-F(^nl# ziHU!HhdcJ2i2O6$q7>3OmjWYXZwV4(38`Cvb&z#vqFo5yFxMNJl1BnrqWFHUZG7yN z`m6$%?`_!j%L)s#J$pfg>(OL-F%KaXSOY0P8GR_8b~QK^KZPMF^lG8R4#HZ(MeY;N z$L08@q~rS>vQCNjY22K;_sdo}TU(o5TSj^WHxI@YMga#TI|WVbCam`R%6I3In|%?M z7oxRdg1GIwkE83PonLQ$olv$t`?xN_Ei)5?qVQrDIcat%e#R6-j|2z8YW18)iYO+} z74`acgBl_(lsCVf{ETkz{8-$~Uz^MrlpVH{PzSA#hUgqlf2Eu!dM;)6fTWRwg$?ZS z2zl?yBl`e^M$G7UT$R3qMrCAuh69vhw`&J}{8VBo>^0cMkNP+#*5*gNC8=Ak8?m2R|w-nF!WZ4VkOU`+bh4V~mM7aIsB#HwH z-qJpj%;J!22n!-FFp$j+jGb2WCVuvw4tVsbcRa* zXItIH!m2{|`tqiG3rsG+2qXcJ{&u=Bth6g*?NU%Hs(pXOpINEVwD2qJohY|Ra!6HG z)-Tveh{8~lsl1P5x>i8KGK1f%@N_#rO*O-URKK`o&sWp_b*fbN8u3XlS<^FdxY>J> zzxth;Q2%M|O=NWx9@OFECI+Eg#P&HwUp@(JTceh#X! zPbvZk%l9eWOUP`h5qyZy*h#m<-KZ=d!uyI?HP)rx&FirviA8ne@`bNmt-Xl3G*|bI z3*g!S)gOSd$YcVDh2}`&-I^dO(p-p5o>PePA*F)^_<`Pr&rKLX*X|P*7u4V* zU$C;`QXQRv0N9@I}1gHszEzF(XIuT-r3TH#BJ&u?HPML&jHjI2j7BQ9D{J_7G4mbg$W z*5o1p5t(l&ece#4(~1! zv0xyw(HEzOnd!G_B|@W-TD<{FzD}WLL3oFZZu<|~X+_fyU+t(lYs%fUe`?mQwjbP! z0f-f?#Io+f2w`#M&NhV(sBpV7r4QTn#cM%6>>ppx(yEAZs9d@FmfiaG-}|FxdGb7q z%ArRFy8qSfw|A$b$Oh?2(IM z^{;a@<+CLV+vX!2LG{zUsM&K?TI7qA2-01gXEWqL$^nWlO;5kmhVNd@5ur-ii-3&=XbqWYCv2%Lx5zhVN zn*s_v%>0L2nrVqYG!XUVoV>#5uZY2qz5w90a25~uGr?@Ck*;I04Jlz^YpJox^&?;J z7isaalH3ObpVdS^H~sdm{0yOxXt_}sCw+xp2K?S42yu-PgonlkFQ`%Katq0t_1qtv zEJbMN+65cbZjAk!-|3!HmAo~SY549XA|Ei|&WK|+=azU=3(iwUh`|Kz=^j+vg6tH{ z1TPjO_dV~{_*2KeMYBk4(RGV8KgZE2+uw7~+d|IOpqAc&8m1zMdVn1uvABgIMk?xU z1fOXQ2q&DX+N#`lP$V>dt#P^Zp(!lclwC7M&!A|Ki{a)h>DT`ULlb7fBP(#InPX5S z`1HPyK^TF(zXDA$ZdLiLT8Ng>M&RCRI zqv(`)K{>a;o<1(}xFQprBFu7X6XB{cB2AK|2KH8%`Lf95(KS^8-mt1!RMHrGA;N$% zhXY4J>q^T=T1$uBJF{wCg}klqvg_>z6s0t{pNoj|53w}IU6#P0pa4l%m$nal8uxE{ zTQuL-&<+1wxx1Ii6leF*a@zsn35uvxUY?{W*7WFQpAnc}BVV~6;$FVa3&4BpykY8jO=o@P197eo z+HRz4KDv)Euajv=F81%JPJJ=nT8;*)ss~$oJ%JQnyctav!;RF1$IQZ{ol8r~Y5G4} zB~GS4rm{6`=#`KzH2Q?lKLB^LNN}yMsDOZ#%ar37{2lEc1w$l|BPnXch6`B|u;?kR z1(ED~KNfWI^|xLK{|I`(I#ywlDDgAC$INReV;>yTE=GajvEB1d&h&iDV`veB{FpcSQ9Qkw1c0sb)0wjs)Rx?W;B2geazGT|ZNGTPy9^e@qTQ;IlCuqOre$G2J* zAz&0gg!1L?GOtBJjZ_v0Pl@Ka4DZD5e0h?g3?B%W0DmZ#hw1bIxFS2NrkhU{d z2($LtH6uglz#yn6jL!?o%}}6l_Q&>xW~FfjPm8bQd~_Lt1%K+BzIv<1*#C=mDwmN% z<#($NN#9QNR0W*7?oLMIve}$$l3N>_|#i{QfdM_2OGLdGGvb zX}23YmSP=%9X3UEwdzypUH4p~{=>4McgK;k9f zyFQsezsF?Upt9eO#3UmXyM8>;zi~DQ4slw~Pj}jBX`iC;V%MoZ&a_nSEL;lSPEFJ} zkVXK3Ys1e}Mj-(M1Am#$wRYRasPYa?>tM?d$g2jZ%LfMDImz9E8BI;JYmA`Zvmf=K z1sDQ!%t2_~B40kpYu1`xdFF#9_VL#Ai*Q2+d2!#;iTZX&RNCr9ulRs9J39vYdqSR; z@Mk|LF0KDCER4G3zPCEnG*?je2b}-h@!_XWd{@oI^r?U=Dg44Y-;YpNU%M9hZ=V?} z$7F*`lTQSmwLF=sFUYo=R~{_LE;;At(!`=rh;4KQpC|WQ>{to@Cjh>9jvf zC$6ew+8~Q}eSRV|7+=C6j8_UcwK61%RCf6DC@Ik9O~K&T+HG*e7+YJNHX(G6B5jzn z$^MrKx&e;s7Z3ls)h@$M<3=4H?8bJ`fzdg>Nb}o+DQ_h&E+MtiAKT9kSQmO-8x-Nc zbjX_0`W#zU=4_wXxj&1Y!_e9i!{#uH6$bBO>FeRJZ`q%Iqlmn%z$=ti#?Xuc$?#Hc z6B%IA{m%X)p#RsfNWX>J2J9u!9u!SLR|dRP9&lDJmcn~V)_|9G;Wm|dZh8vSc+u$o zYaXWC3RJbrf0+V-TgNou!=@Xv&_KDg>;t-UdcsBYt*!s&FaH09y?8<|uIJ03*m+}R zxgaJ!_{JKvCz~eBj;}oIgi$VMcY%^}Zx(JDa(~|{i+K1PQfET1MG4h#HA{WU7Vx`r zkM@eE2t1capIT~UkCm;we%*%v@34X?nR2%yeIPJ{_DtH`@z&G9hS7rnLp`L}>< zi|NBRY)@5HzolOi_>}(5`t)lE0kerFWiLUyVE`EDS1f@&k*0&&x_gJxlBcK4H(NOv zt`7I<>PI}5koaMTAtpf^`@A2_FZK@iYChTN}nR3w<@V9cq4AR-t#kD4{-OEJ9n0Q zrSbr&%dkGHrn<~01iF3f){>R50MM1(+Q1<7{A!p+1Fe*pYb$dZA4?~AP2^>tzK zwpY9BQ=w7L>mMbbSqkUqKQ=W>Wcs!d!`=uAqv-*4Lnwlq0`JK;0E`#*c}*?pZTltsJK~wquElCPuRrf=0G^29Fg(K;2Lbauz4;RT3(^ya`wAmv4c>S_U5DNBT;rHZKPy+d!cSW@8$+o8<0`#>s>ej8oCfsIiiRn zSxd3*zgy>B=M!$C?G3c0@&9Y&ppuC~G$7fq`{-@?+vcJx-aWt!qtu z>ho;N+%VteU;6wVyaQ2e?#cHaq%~Gq!KV{3O9d!qLpcO;k^!dOApS-t)^C}0uXb;^ zeG2ItT^L?Jg`yV0k=}zc4(5Y>uUUm7^;r|jtu z29nRNw$>y?U`vp`$nixXBs;=_-imryN&$#Fr#D2#;F}JB9`F#mne6LHPsNACzPnIU z&kcGz?tXgpO8YxxHD*(a3VQVjAgGAmczMYtDC(=H3~J!+q-XisRO{kx;F-Y~Bd=)> z0W)?+mo(G3zC4sUmgZX&t>f4+wiR7r@3ZLNd zpKDpii1c6BpmS#867bf>&QtmZrB80x z*N?0u5YO+Vud`fv4F8=vv7;>8G=lU*2m}AWB=Q@SwOt+#!B58qK}DV*Rfs8HH0Bg4 zZ+YjPpG17bsX1%3wL&t^bbs6>r@WIkXQCI?vpUYPOkU#vLjlC1hnH-McK^-s+n!bY z*OM403XRwh+*DiSca)?dH^PeIf>VxxvT1P1QvZZ!HI#t*^H0GxYecr9eyuV~UE z@y=v#NskYwQoh*h>ckM{;`_T%|C~8CWI8<}DuMmRgq0$O4EzawzO^+3&+cG|kOCbF z`PPKRE4+8(mqZ#!zc`kev;7@}s*umrlJ*jqXo~dQdgzQQIL=kzJL43iw_U#JjZ0a~ zL2AOSO0uHrK(8X+MHJR*QAsv_D|__3|KWG~u*Y;5a0T*BWSEsnpsHqpNa%1MUv ze=2vs>ae64fzrX!G)wA50LGE8UO3Bw!^SYgo&f9!{j<0cPJq*|nAGU|$#r8wLP5;E zCPCqC*RbODPEKoY>h>Vk2UP#I8j&w>aIj%L z7wipCOrAWByuy|{;+~=M>*m|cYp*4v=^EqUrOd_Z(ddewPZ}_%o?p9=ADT_h!Uk1Z z`3R+@4^(i?7RT2^SDC6*wfDi#dXmWk<`*XW-!ST!5Wm9x&V=^*|xIUv>OISg(%wQoV15{*w~TN7`LYn4CU zjoTP};34kjaN&(PTdRP{lWqN@Y_(2hfvcTellH(^a$$tBp^EpScq>|=R~Hro#_`3> zpMClBFOxg>wF4F2H|{c8v(D-7S?92hOf=wX7zuYka7-bb@DPdn2-{SAVPA6CEx`@$ zDo2gjg#CPV7sozeceuZ%Yxu@up)1Kzy96=7^ekUrjUb1C8SV@>9 zXm4+;PfzUBYjqprs9dHVwYOP@4c*@HDD3~nEWL$R*ZBs)M7n&>@oG+9)H!b{PI&7 z1`|~pidTKX@*2g*3j?-kPwW}Sm@5}?B^$Ssf>fi(`=0I$z_(d;KJy~Z`-tDmdO9&< zd7-vsU32_n!Oh=SKCr}LrndDU%ko{Ah(#`a9bi{DK`{YDk(q3}c2XUNvktQbOF;>2 z%Q40#gUs1hfT&V&dQUt(>#S${_ic{vz7OA8R3jMBxAnkl0Z3CXa!8iAb_{Xa=0}i$ z@=X#?dafdUDvM+m+TiuzL3L|fr^D<#3x8C79;<7jk5TM%a3@^stIg-P z&N3z^@q*^zmGfep8o%kiJrfptsz+Y7r*E)YbX^UR0Tp0~4r1ufNDd08Rbxx0I=~mC z-KieaNLD2b3G0@;YxbD<_6azHj8@J#7pG|rb&6FLb%Thvcsbz4cu*wrGEJINMv})V z$Eq(+)GMNJuvn%28H5DhK3reSBPYno>h4|J$5~fz*iUW5nGFrjlXtchsHzlM@_Qm6 zXto*}f(li@m&l5RC~e1Qa3V&nSkAH|g(M0jh7P-ug61ax?jt_*1(gm(~<8_mh8; zcl%Eico^MJZY^Jx3Pc1~{9=rQi8jg+h8#rq`t4ZYG7~z~2TV`(c3sJu3Z}G=n`WOi zdE_uGz7;(>UYORoIB7rcSh&eeRB)xt@b+^D-|%(n&C+d}s#|GGcTn66lJ%RycsIn3 z@?ZIIO6ufYKT*;}s?pS4QAVloGHVa)s%1|&Jn3Jt% z@u)b)IbW^rW~F2|O*EeSLC1Qc&^34ax1Eyd1H0cIL~xku<+CuoPg{SQFmT78-CCC? za26;eCE$A91%?1-0S6IWtA66Bi9Rt0of$3pP;e$or?p8)_4kc5S${Jbo*W@&N&r=5 zc7ZVg+&Z=5P%zBw2`~NgL_+sYKpA6r7M!qR>65SAeMk4(?C&0l<>=O-CJhR6d~u-A zH!k;d!w~v=u$KV@2icMhCxIEK_JS*DK2xp!*;*JeLkYMcEAaF$-8-cio3N}k?j3A; zBNuBi(Gf|{lX>{F$`$jlT^H49h~z>b+KFbg^No(bTqTGG=|9&PrzRdHV7r4~$5#Qv zH`Xc4+%wqO{F@0u1GWZ%h3^f8k)tS9#%Zr905ju5+__Ht5nZgn5z}?brOP>rJzwNXW+{+(~ML#$E{t=aqxx&S>!|t)A zGu+-f-AQ!+AkNC0?T

i3IVy@GRkZK4{3Y9=dkzZ4~3CSQ_SyCA> zD-j2ae!<{PQRer$v)1e~TT!SbTO3GgJK}t9sB^CUMIuaIc^1qu**6CL-r%l>yiV@~%da?gnBMaeG`Z8*n{0l0FWpw?)! z)VG0Hy&{<2H9crUz}^GTsKHI}XU#vz*dzO0E8uLA)T233{Rr(jfi#+7psP}t5HT_Q zLc?BwcECn%a=|p_*Yv?+V-T$q*~taMr(N#+5o9m%CLmQ^I{ZlI7-S6n-d>laf1dM< zO!&J%z)N%ilMDOFe4S?T+8spFdi|G)usCtFf+;l!X59D#-uQ+3Qw#mZP*b>Tjv8Y{ zsUwtv&1mB9-M>uV)7C=uPJClteaOh%bQNbExC&J3 zYQ6Bj#KQ@CFQa}72{e11faJfwOcoTx-hA?*FSul0vxPWVJSsxrrJzbM;KjV?pI-Tk4xw=YGxoV$-|dt^VxgyuXFo1HwD$ zC%sId6qNEGHq-HKdzVeELxRFYndp|q_r7S`QDZ9^vN9eX`(k3?YeuK4;e_pXDXo(3M>a2+<*le8R_VygRmt-$-{fUNeR)JL&#p_b+k7l-t&+Om{d3NVQg zGOjo4#+uog{&u*pnzk+~z`NwdJb4f7_0mE{t?PoUf9xr@1g4x?1+UU}y<_Aa|LT@=7uz%o*kOeIfNWW*MbL}f z(*4=z6psv}4&N0%Y5rXG&v@pK7~3~7HU`&NBMoC-3UNs;XCwvQ5aOvYCXefk#MKmBv` znr%*I_3{xb;T_RT#-{GF?QMbbvh(a_p!uiE1byPQfeoAG&{Wr=vP!0?v8ctUxv7_e z50l^Xh`irofGd^QrROhsy+2t~;qE`G1NxS4A|?&|A4!p_Av=Aj%(Eu$>CD>wIO`&r zAno@jL!PKaBuSDsl7a`y#$Sg@jvH}b=IFomjH91@_U*&LhoZ^z{%n>eOr{TQgtQkM z|1k(VcCYT$zQ}YDXXdv^IR3sIFtA!CTUesVboJcJ?pjGgqN&TdwKIy$dBAGh3Km6YKxFWoc?w`Tu!jK%*V#ngRHu7T$MkzbgiuH|Jibf{+XO7`zjhB~pht4Hz$hW$o$; z6C&mo|7C(V?*2yoNa5qKzLnej1oFd&W$zf% zW4CkMKQZKynxKKnbr|e8J#4DCM0&S6O(O5Ch)<67eQ;RZY#7yVXniXjp`?~3#;-7V z=pXnc>9v|`WmP$+f5&T6$Wzp1IzNSCx=G)+v+K7>0yyo*;PH2tqAQZ`B;gH%%pHLR{qLv7f(PqB{DzC@2gbBv_yFK2+Lfl;M9bsf@@eN|qCUae9XEr|X!< zS)fneQ@PFoy2{0~t!pYTdNi~Ge?wI_wXBEh zq$%bSwq(0?akY^EUGc6#>r~cZ!|rtp(>~3Z6+?@phE1*q(^HSXYPMkhtZ9?D6V zt+FJAhk8o2Rdlv<{blN(M_1pXA`++C7V&(;4GX(w6YVac()lMtwlZeFemyJy?%OeS zW6uopI_ztF$%o&GHQV2(r-AX)cvPocPa$#N^7?&0@QHH@lLUr>D7+t3)>wk_t4_aT zJ{quyK0xCElG^!mAj;+Qc_%oaFNw>0FHty^*tzKh#=PubCOeD;V`#|c{uRCY!djQ( zl3#S=(>vFt10Y1>KOYiybpN}%%-ewiLXlxwORhX>1i`vvR8OtLN#HE^F5Jw!@~*J9 z>;PR2DA8;PY($85f0Fgi)3x0HVlM6#&$u(f{{yD@ZzeL*h4=r?ME+;-KQobmmcxGl z6aUvtWEn8748goEPgq=T8u`yiWJ93Ym$mwTjzl*5UlNirRmfj$z;;kvbf`s|1Zwra zOcVYJ3$1Ch!z9_kbLE%Qws`uVMG94OzVp~MvtTHqmg=EW81F(}#)7m6C5V`fFHA4Qpv+bU-xRmb}{rSMSz=O06_$Tbi6!x#gIqs;t8_q>R6CndBljO47Ucl_(W zi4138!N8@>6Fsxq-2lLI!|~9#MZtqSQQ9@UuW^s6lexl^yGC7la3t2}G3#Tt24_{4 z6~U}tII2f^4niIy`k@JC8DtewYk?rK^4CWg{;ShX>KR6cc-IiZm{M#xT;r?VcSY{Z}8I9q2o&}~kQFGnOdHi&uGs}KK zJ~b3ME9}^py(HyBHX;eYxA!b!ZLD~=#2qF*>XO2fcQQqKuOzZg2dGx3WwuVsdWrVO zGlozCm?zMg;4Z_nM6Fn`J0`Ii8>^&Ik+;Y>yKV!20omlf3_pXZd8mN*!p;B+1=peH z1y63Zhqv6!*oaQOm~K)1<~{+=O^90VpKXb=KOBxbx-*7M=9tT#iF_$B?@(ewN1 zt39`MfgsSnS^Un~QoT~ykuSBf`4B*qs045Ns5WGvuv)7?9K+dcRNfemqZ1%-m8149j zl)~^|3>T(DcIwUV3ye%s?bi=Gp#SQM*-I{AJs4rbGaDeb?dS2y#aZnt>0Ru(nkgb8 z3W_OZsM8(dZepqym2D8wPH=jG1icV=9I#k$9Z)GM0}3H%i<@ZQxgbFqBpBlrRr{fn zL8%5f#^Css?7;i?oqJo5xj!!$zjHtL(y%m=-WP?W+px|JRfzde|x`$e;|0( z51_p&d}<@YXhO&d^Y8T|mgJ8Vm7=x7QLB>P-s!lzR+HI>?~S=uO&|6ztFp8z$?C60 z^)Y}+1Qw(^&_Vn$h4KQW{EN31p^O4*(JaMUgt+n#eBzh0R@{Tht}fV?;^fDB+H6q* zy6z`l8Uh7i4>}^pO7G4V-pg~!+XA)RyyvZD z*ZcDIw!ePuBs>@}{ml`e9lnqy`AsU(-JFQ&g9vOO#i9=u#VJ)}RE^T-M%@oqzUs}7 zwsZR)@m&b;yT=unOM+4^^#gsK1SCJD42PcO#G!i&74S;vB2*U!2p?Nmxj9gHr>(td z^3mi!Ce=!fK4peQd}+!8;Iq6EsbF3}X{;5ALY`hE6&4dC8m5eu2`` zsrBNSkMidQAH%aL_6|Y!AHC`k*!|AbeiL<-M`e)d7;uSC)6Q@@(BC7JNdyxBU}woK z-!)}y`hx*jKz-JYEvC$K!`gnM>ZR>On-bH)#OPD(m^o-o|5hy37-35Ppl?hRA;fmW zPQp$9GTkB?k)CR`3%9g?t>5JV(q8jSwHzz=;*G!UF1=skeY;ym9Q8r{taEml1(-#% zyJA-qV^w zrGjak^a7+@H}MP2GXKMm);pi9BsDosZ1{fV_YILBB8xV6{9@^SHb!Fsp8wlnk~+8% z)19*wEb81|6tw#7MYdi8#rN(oyA+d41$}cBuROO1SkIWLMu2PBgd~qYi~zc*u~|^g z`uOK_mX;heWfmXD(vkb;D_dh$mg~}u%hlubfA;k(FF)8-ab%3Bb+i4XoTs|X@KvY> z7BBGk^@9{AU#{=$Hfx^j#SUYX)Qmuu2wM_li%M0bEH0T$_oU9EyTL+;ON5>Hs}!Gm zL`G_eT+PsfS?e@ZikA;exl$xC8%zM6YEaxhmUUal?}usagMq>QawJzf=Oh|Vr<#w~{|JRXY8 z_B*k~y4-M$?oUrv5{CjgER%bWlCe?GCw3!V2VNaKJ_Cdtf4Zn2u1CKp#SVj6@ET7O zH;nkyNgwLcRr~qG_T(N^OtYs4Bp{xdsj??e-Eez7@a(g$0B-=$h0g`7)0h|lxnX#T zCP+$+Lh{0A45i_JnQSL+NK2D$^!7N~5?dNGw(f>#HrDv=3f%7-3gJ-F?v#6us?Cl> z$^uAZW#V2J;BSS3Qd&{(y)51Fg=hL#+?^*)<|kuFx3Z>^qgcQ6?p+h%5AU3O83Y!t zho1QZfUps6;Y)GnzLJ8n;sXQUU-JhI<}wt~k~SeXbnH!nj&AD6vho0`Eak%CRt_5Z z1I$Zv@+RpS*Ar2FsF?PPUx~q~t@46{?bzEX3ljTZBXyLNik-CTT5I)6-e7WZTiTiIVJ9VSrZSLSKHjRf0*Q(q{jASvn_YI%OgOf(IE8!y_wc08bX< zt9$}+rj7`UJ>Y#c?lkuV@X{EoOfu$6)&v)@Wnx=huKnz98oxWrc>6B@9?(GpuT11! zKv`GZjv&bc2TQkCHy*piT&daceZDyM-uG4=b-NRDkR~OrJJ=QP0IP-{xx7jydW~6V zK)v*WMoWX}?=ef|P=2HcrDkDaS1OR~*<3#c$+8-W-gMQASrJZuY4 ztuZB}^sM*sVvkj@t?<~I2=;1<6nOCKi~dVRy+yb7UB&ubOY>V`(2T=Hv}Y`xjM8WYq%rD&nr_ou;ep~MsOOw(QV zv5pLAtba3tcxNcIZFdBrOgL;}8)6PaoEAqZgW2RI*D-iiYLaZF{q*e|b zHl`H~$ln)s_kCcb$Qs{pXY^+Oq&;*TN(xrcR99b{2#=EUEryX z&HM(XIc3E#u`JK%g}^q#9U;Hq7}3wll#BM8ivW7=E*VA|O~g0V(N(F&-#w-giujgD z_v~)n-ZyEM(q+EdcDpNAKQ}aaWLh|1<@_26F2wxg?O_1*SY#}63|QN(dZd*N^LAqD zW=S#18@Tpm+bp`{v*SJo>9t9N_(dO|^{jW%a!4OI6Q;FSN4#;~K8y)v0R`M`@xW-; z-jaPJEr3KYt0rQ5c+YrmRIIz6snHT6u3KGMgSiE)4*4ULfi$@K4k8Z1pfx>jQdYRx_TP`FPa&IjO5<}ys zs=)=jLbEa|)<)#xTD^fK_#oE`Tlg&TOg7mT2!wIA!{$^Li>4jPGa()kCCDUtF;a># z*$m#SqY=c0@}6 zI2<{Jsb`M^>;RR5MpOD4M0a3)NGKgp`@h7R6Hosc3G(QgT_J3Vc;26^8<>)Gdh^X< zr1=IxOqSFS%oOFgQv;EU(wvkE($q03%2k$fk-HJNy}ewx5bxXSQu(VcTUE-;7LLb! zFG;$9E_d>sIC!-pbWSbyA@i{VH3T_#2rS1RvE67EibPk#BEO*l!gUIT1A2)|Ey9^a zVe!7OstMcHyFs28Yi~?!nvpFbpbefb04+;0aTN)ygoYR^{Sgj$>*hA0t)N*c`GfP#qZI`4S{=%ql>u;sZl-Q^;JhjgdLAT=oypWw|3 zPB1QtPX~BLU?e0Y$cNmMGiN)g{^ymCfVmi~uKd^gaOC-aqXc2-SqTDm>n;hjWZ#$i z%95C-udviZdYoUnp#+I9jj@74CSGX`dVgAXgg;O!e!QEIJn;3YVBr_kQa;46h02c{ zZH}pa>gVT&1a(a$zOOacA77t+cjs5d&HFKYX5!0dmZX0Wc{>__tY1zQs%g%HMhCMl zjk<(GrAJNI@NELOFW5d#c9u*s6K4_%9yJ0xVZ1Pjp@p5i(MSbK)ISsjXG&Cs*Q|FQ zgqRweHFF?ky*e5#tZgzsQf#Jhw$D$NTE^anyiSt@)@&wY63ho&pckVS+1lk8qmE^r zAU1^2gmr~?0N%4vykz*oj!iwQnS0fGS|Is4>IZKVv?chgUvz7Rsjy=I=JnD8DU2~xf z>3(TFiZT|G7hrij*F>8J+V6S6Jq>`{Q_xcsOSNQNz?AyBGfcGE-8ud;jrkm<10Q7Q zRj{DTDIMKfke{8q+VP2Pn5l&&Ka$ zrCVt&!oh}g>?kzp8!9}hFtfRUeF10*9Wd&Td`K=gvj{TbWU=GaQD724HPbWKFv+$h zR1mwCT7q<>x!BY=zc$1nSPY%x68GR9t=8RpE0<;p@%Q&-)aPQ4s_?zv4?&UQhkY_haL{ zudBCJN(|Xm9W~vp3Dw*T_vF3Q9xPUFsxJ6x`FpFfxyFmb7D&bT+rm?T$>q(09>o*C z!%4FOVsT_CAY67Agms=f%(jjC^!tYHV7>PX7w=1w;;&qvZ~Ma zSv5RwS1Kpe^@*z#vArfMOC-BXxo(K7LD)WEByhRd=WFHEM77X^6YQZ>#33)2(!DH! zd@-BA)i+=EY?3}%j5G!ZDGE>RG;I?8*xc77R$KE}2b~vF(0SIWc`&s;w_X0wvF6Y7c__0GfX%TC^VOq(H zdQ5V7FKg7&6JGG@r2T7zgp6~M`}@1=;YW=2%=PxGX^ywcWZ2{OCSswJ?*eyHeq=Md zI$M5d`*H`9U&1+Wuvjp)F*l9F9ws!eRqa@oVz4_;>2kojG5NqtxqL-d5)`2lFelr0 zCkTM*WYU}a8I{OP+6^FXa27%dm6WUErClt4>6!x~<`{2>qadM-c|0Hi=WImeGJ1y` z;f{(=GS#bE)__CH6@8E-m+=p4{_GvhH!#s`+~mw3sei+D{wrX|j3)eEIftk$t3!&P z-$_<;U}OS7MOXE_D=2I;%8@F_;JocLLJ^602k+UE#*nT|ng5%=<4L_RB+I_JM)x_I z=s_Pzp^x`5XbQ-%Z227&7)xPWJC+?+#!M}s+QI45XvVi2sBUWt%OtkHaWF7z`FMj{AF5(W&dURX$78S zM7dKdfbC%B$!z#I&@oc8mDRBYZ=p6z|0i6L{$DsnIr<1K2;77Dzi4~!pr-$BT{H@U zNK?8}qEe+QMFkX64Jo}jAvI|)yP%;;d6_o`qw`<4_sAtA3GH@!&%3l9Y3bCw~J(OvU|VM z#8c-a1MJnHop{nKh-}Tsx^Mi=mGk3h+6S+|=$;X|2=7NB!%wz+CIv^q6Zx|#^$a6K zFHWpUknTjSoN~h>q+?CK;|zJNd(x^igO_}y8V*(Z&Rx(oI7i&7wxG*Lcf6+Sc#1P8!_Q)If&EDqj1B*5o9YV)v*gU_BSDuuB_Xgy0={mSmvS;EVrrA@tvJ0y$ zKnaVEZg8V~ZClF$qIapcVs&ym(Z#TaA?}39$`=)XUantp^qatD_@8dvodZC$wmE+Q z1bv1agUyQmmnI* z7A3ZFK>9UGf72(%O#oXYo(6QW3@M?99x}Or*#mTOCZ^;!T z0mc$qEygD1xZ7dj$YV|FY~r*m3ta_-nrfJ*n^Jb8N6%D&*M!susy?2srA*fz$hhjO zldWkQcZM}%jQIhTKp$b6)X>WTS*SdnOvVGCku&u5=y)hSj7-g2rOK4TopY~>#d>Mi z^8-y=tZl+lFGI9!Oak)wH?klENpk|)V$0ls=uSX&tAPpFy-C@&F~Gdhcv_!g>-2W= zOQk6-GWu!H&EC4n%%!}GnAZ)!{+W{qI|M(@`V1Ere-pnH$JV2l8u;I)249u(AFVzH zcA1mUcDws|gljkjfn z@^N`VM*=zb_==qY)4Q$qb%f)*Xt%^J?pG?{Kg6_Pm>>v=%)1nDRDpLYHZc7b;}+Fm{d2)&_Z(CV zt`SShDj(!zoE##Jtoi2Dn3wTD259vcxwzM9f;HyweugHy9eSL3fLiML1}w^!dq^*8 zv*-Jn-U{a3{$LvQ`77O%ar60#R5SxJ{>w;1-7`waT2fQ+uAwxPDZ)!TMNeW_F9W*$ zR~u2#W7-x`v}q7En;O#RGZrRLa@tid3N2lnC#}41e0w+X*2~+Ar?y^kFPT6cJ?UrG z%3Hy-V#=$c&1LFXZrsF!9~b|Ee3hdp1uw$iqR(|+@`u(?vnLBhB!>BX=eN6-j_*@p z>A=nfy4ufx1#HYE6XPMg?BO7M8uf&}q;&&|@X~3R!$*u%f*Wtj1N^#q4^_NF zZ-<`ES2RH8%2i2in6UQU1*T;-Sh1Ft3sbZS$5sOP7I zN6hE~mh*@jM*K=s#m^hIGsaGo<^EHUN1BoC4=0i$p?Bi|O~R)Q`o#@0E%pgzh~(cK zue_*&7IK4VR8Y_7x=BVTyMpXpb9z3(Qr9dMCCv^7J;nJ0%zq^Z6zwrxA@Qs)A1 zG_=j;5N1NhjArGsHmlje{pn=9boEBE$P?3E2kDi&B<>FI5g=CwNrJ`L^E9FEQqXbQ zErq}R2Y5LBA4JW$AB70k*hRDm2oU0V{Jm zp^%e6^QSev@Cnp9oOQ-8ungEy$h~MyFpwT=nME50?fLo?{eik?AlHw2keE56( zd{((y9@<6j{S(fE*7EO8zPAQ1`d!M{GL+UX&VJIYY5YA2aLa91Dbz%cBO$q+zCyxyJ$9}6ds`izI_qQcs`0p`Y{!S@BGY|i@u#S|W~X2B22 zk0WQ9U*820$4faUWYGi`C6!*q%JPMh8FM7D6;hndRo{G?yhx2(1AL&dM^FWEsY zsuMdj+^&FEn9>JK(=x8JbE%+8niE)KezH=qA!u9Vj^s@3< zC>jnM65*ddzXx>#biDhSfLZ|M38Yg^0rRKn1b*iG%-X3^Km-pZty$dtiF3mU077so ze^dNX7BOMNSErsm^vS=SGxm#%*EMT1>5qGd7n@qC&}K>GSU#Hv3|JFrj$ea&&F~?s z8Z5QTdInbz3d1@pm%avjImkHtNONx&n|~Ma0^}g8NK@&A@-YulIbC_l7DtvLa(3MJ z?A(m)Zi#(x-7jpBFQ1->e8qb<+*+1%-L4ezXv-FInu3XjTa#E=be8jf8X(G*q6BY;(TQQxFA4(fl0fs*>cDh0|;jTv@^)P6qEa$eL&V~ z$eD4^h1V@D?>2~1({)Wk7UC9h`3yTA}Y10<$R^b zDkkMZ-1Id$ps(oP%zf2%t-FlSU7xle+y*&DAx7w5%A=dL?i*yqkosjNR)d@p9ZOTH z{ifae=Ub(x4YOVVWm>Cj5}!8Z_`?u&i-Hiv(!eH-UE0OygM;!#!4O>_*HPr6%HCYPgLugHP0kg;WWvU^UT zSDyfc3D$S0Q^{!{>v(MZN3R*Zf|SVvba*0kU8iBk`nvk~*u9EO+Xt12Z_?x^K$l+X zvZw=yBTRM5ZhRH=%{Vk%+g4C=6_9{s1Mu5aL zO6MV=h3%F{fiNKBIPH(>9{05;ws{qZn{H@D2~2yAu^vlT9kT3t*SU17nU z`(_K-G^RZ(1#uAWRaf0W&!ymiX{x?DdX+Nu+}kd<$Ivklj)V048NJfvQ1RqbtztE3 zntq>gi78H3bk)JM5~3ke6i0s(p?TK_TgMd)Z$}1hDAs1gbwqj4)E2r3 zi^+Q?a>R9FJe-3BelsmK43{ON1eUWDDAOees)x1X?;O*q>N}L&=U(;x-l4=>zjKVp z8xA`2U;(6~=U5v_<~uNux1l0{Q7NySx@TI8S6U!MsIFt(QNP&n`h~aQ82f)nU%FUZ*E8>^7Xs!6rJvaSvO`QuWmA86R z0Mw1?Kt|xIaH~Tk0$qiB?GRPVnx0ECmn2PprgUqsiFdrbTB=j}?HlgRwr%qDC;2!0 zb!y(tc7Uf8WpD%0?XcG{0f@RQpe9Tr@oRL9pT#rHb}s8;$#T!!^FuR)GlcC=Kp&%i zt%l^FLS+v1lkUEpHp;DgT7Q1Sb4_|O&^7chSSefZax9t98bP5j?dqu25$+WBB7V)3 ztdweb!mCpA#e;EL3B)XJX|)F5bR7u=g8`rsJVtECQSM>Jg69zNROF2jCjV^6LGugt z#8GL(<)4o-ufl5Dur$Y)e8#?l%nC*BLEKl}CSImm4dn#$YWg#Y=mn zN^tS8p>PasV-H94AOun6S>aHJ%1NtP?y$@Lnp%M;5}z*krA+$@HLsDwxvN0I7OjLh zr~pKh9;k0jA$9~IW3^qm9x%+s4;~FxVwgA9Q(GAMrCMn_B0mP+UEWBWg-A2c(ETat zMzL)(0`N0=0cO`D{W|hppUxV_X8r=ZWcllF^(NUzgaN5VLH|M+{u62Ve|pOSTSvF; zy#?|J`+z0oI1B)s4jhMfn?_ZI>pW^Z}-PqS9kSpQH-*xOKz!aZl2!87* zgC39n3;Iml-YZsuh{u@hA^P&AbvIeH>||y-Ijid1L>|YcEBF|C9Nul`l2X(o0H-`(7gar#lnL3o z`+lQy_rV0|?bJQ%UIu=v7quO0LEoJZYeP68hHw%|v{2TUCPlc_ckKBztG88uK~*-^ z-zKXmwqw?=R3-WOAM=&gT+&Ssj_c0h+!0_z2egd4eMS&Jhl&ZZSOZ92%_qDsSj>6G z=;gmowRqdye8s}kEq~^Ze{qR+>~U+=%&4V3uLGEN&v8kBpj4;#Y=_p61A>@ z&?8bMf!^kA7&lY&h-kMiU6|L02nFy-{No^C(h-JDr170bT}F-K$~4it%4Yi`8k^Aj zEuF(p*U_4>F#3_J1z+B7CuIK*u@nbIkxm zg`WY?CICOKTF}+5L?|}*p&QA<8GfCq03wE7fl0%)B?eza=hpc${|lj{;Vk~_)BZc| zCCB30zeTz@vrqUm1A1BLZpROmuKyXmH2e?r(gKeI(91mm;I(}paRxY7dhA2~f-q_X zFtG1UYyyDKC)xn9d7nS-$vq@+;g+XWqhBGQeIq99KP)88=0ENccL)=yIneE-|B0_E z8v6J$C|pkl9dCK=lH5euyFK&FPEfNVmTdJ$U;WBPi~d^I64s*BfS$*xdi1qb-1os4TZ)U|eQS>^n1$MFEFYS1Mw^CT`#22TVi z^k^GUk}PN`;xgfd^UM$>PwW_L^^jsj))H26&zmLHuxZ%6y`6nHOTOnj_epn7hp>zU zJzsJ@BIusE)O)^T*tm0w8yral11+M&{QaJrWL;Szg}nds(9@(dMyp3kx`bvIGh?d%BH_72?^(D|`=w0bCnq;kWOYiT>41-?tXCDP|FL0w5l2(SsLqQyVW)~ij zA#LBm(Rw z0*E_AEKN+LT@JY9ZNOjv9UxMYNCZ5w3abFRq49H7}CiAzAkhC>VKXA-i)6?k$7??C7rH;eSQRjHm|1A<;L}4(OhY<8uUW z%`mqBP0W9KBK?24J!m*jqyoVFp_3AH6eDB_MNc8@qxcTMeqH?^YeNE41jW8#ytWH~ z&to`rluB=ZW`Qx&ZOdX?pV^J9$RBs3xG@Qf5p15h%k(_&iF=Qc8-;ANF9NP5&I-zB zhr#-|nZh{sjUoWozmMD2f(>((07hKH(4AHEJ_rNA^-{Q4LH$}>BOv7x%rNbp4spk( zDnl~%T zPd>OQ{pq-IyV|YQ>!0Bs=9*?3^y{Tr5?5d~8iB_i_H^;EduwF-82Q*GbPR%t-T*Jb zjF=}`^-MR4N(-3sh8|41&b7+3HA10|>8Y+Or%nu7P|C_OTxhc*n&pL5K8@ zCwvb5c4TCwP-IG;YvGX2rJnS8fAbR24M_vF;77Dg>!Ey`%o{PLSClPd8*?>$jNJ?{ z6^%553$*VnG{A+9i@r<$G)sstXAe&cR6CEjKcm1ejP3?K$e6vBvLBf|jUsh{dtJ{b zW3eB?{}_37;*>lq)!f>!L_O#m?esag$JdwO!yT^Y5US&fGfSis|* z_Y^>tJU5(v8;JbXcs&iSIabl_?U=S~=AiJ@Ih?2J%x9mf?_wr)F9VN*P(K9YVB)?& za5zpFZc0^YS8;bMqG#PTMM#Uqwx%6IZ zD9deH|I9977f~XhMA5w_3-#p#{Q?0LP%`n;bS+z!2PbdD2L$wpBK$FUu2&PuL=sB=Ov@ZhL7<1*( z0O^v5S^FWlW4aCPBhsR*Wp1j8WNsW%L#%*{pI9;U^*>mVmM~Xrl)f|yd;M3X1Oq#| zW>bom>^i-DNqQ@H1-i6vg@^GAyh@i{@yJA;U;w}IuUg9SPc7a1k6Mb%YWVk3`k$M; zf9mA8|DXSgMa!|c`fqg-5HI`_eRdZrGXo@_{ac~5{tJrApm67-2`sA5+kdN+c0i>x zK>e?k619#Y0t=2J*Ii>Wv;W?|-$g!R)dpSt3oo;jR=$yG2gn2y7TcMGnLnI6CaaJ z+MO)4cP5DY(eKvc310b3l|5-z1zY4dP69WJg;hj{HCz_)PrJ5%WoE_iPC-6$p zo{cY!9xGNyRu(5aIt2^)9(LaqlG{q|>~A0rjEiN;q<(Qt^R2mFU0(TiMD}n&p3;Xe z;e{f6N+1w7I1_%I%1bl+>8mk*0O?|JC?E_Do_pAE+@G{&Y&w){77ROT*Bj{VFY~QP zuB=x(snan^ld7j==F)QKsw2k&dZL1T?bR17qo1LY?{``N+Y|`>IO92@#}6!~@0~-()BNcX z#cp~DpXVBVIv{6;tBTN)e|{=o_DIwD7TS`hlyLE6hU{gD8Jq$xz2Cq1N?d6gbtI`*3u}%7QP$4pf-4sb6zq`{d^W3ic$%jvlAz80)A!nY+>dD`T`Z)HU#vK z5zOPWHQTia(EuUEp4i@pzOhpm9;R(7)YM_0_~@+Ks!K(x9(%{JvcUo1yG96TfJSGQ zFg=E0%sf{?@mw9DdOV&Mr21v4&o@zCX_@Pv9|)j24y%*G1FwHbI*@1E7p~H)|8Dv* zY{FB3-gbqm9xqV)^?CJ)>2z(&o7nMdUxH!k&#X|3FU~zJ%@3P-cUXrxLwBHkpkSkR zHWG&5CUjkD>YVuM3iVR|h+vq$sWT~ZK%WEt-8H3?U-WB3SxtnonYliW5a{ivq5e!6 z{smOVP@F{k6o-IQ$-pwEx7`L4!{&Sxkl|W~T3x0(^qrrUsZe^~4-K3DNs98ncglhL zA<;uz({5JkdZ^%USu(zZr%3<~rtY@a^XvM?qPTyX7vZ0sV)bqE#=3|d_v_`IWn~Lj zU)RN-#wo#p)B6zMA*Am>nh-oT2G^h?bH-rfjgy{5g2zP9#m1bx!=Lq8gWDPNh+$QW z0A`G%O&U|ce}}dmW^>UK_J{CUhivVvI`(;&m-Bwx#6CZ2p;jFK&FxrEX*jWudv&vP zjO$@W0n>?MM`%a#23TFE%mcGJN-D_rt1Ifs3%0P!>=SRD(W%Y$wuNbK&9lHdd?@}Z z`-lqT+QWy9Oe8;B`gyE7Csy2##yj^c^J$E93~KGdfI@)8Bwlw7_8D2WyLO;}Jp|>@ zm!`Vi9&m?41LrR3%GVCL_cfVUwfp-&n7_J6@LB+2yJu9Sl76V{jgK&mslb?LktKEh$gb<_lY75J)~1qR#mGv*wnoJ{Ov?s1 zK-Q)zY7TEt8U4z+tZI77y=v;)`Hz8MM9r|mU7%A0c7MKVS zDge!*jG6j~WjCbh7oh+XjdMdyq&Lr$Ck1%k7=dfd!K4E!I>i+LVuvUt=d4+wy~V<$ ziJWJ(nfr_Cd7HlU_|^*_LHa&0&oJDf?maFEsmEEh7nk&bA=T~eC?JP2aBe^08RLr* zbge6`br$?p$ue4;a!PBRfhWyrXf0e`)FOpfEOc>8-R54vx6RO1oWn{HaskdH0v`67 zt%03%qa%F@()c`u#DZzFMQy((kEDXfS{L~e;}q>jXjW^c3>aLO^+BKK z85t?vfh;VoMaq04g@|8yL-;>WL#tyMSXt;tFvXgSLX0D` zG7*R98s^xMCN+Bf+)k29ko1SM>Afm0mGO718b5s=Qx)qOP0F*e-1;twc!D0|1xE*o zRgh*l9@5Ee`w(nFfX9SI!_ZPq*t+|gjtQWFE3f>RF@Gbm;mxg42}MxGV~)+m2{x}J z$(*A-O%w-g<3o|65YYgYj{RXhqcF0d(B&_AnXXb4?Sbbf`ceS>Q-=#dB+(-Q#C9;s z4OGD0Z7`+=H%}j>yMF`H24w1=IL{hfGCr}(lskAw_1TbZ@4ZCn54BG3ugAeZPI|-A zpp=IiLUchBRsz#~*5K;RfgMj7rllG5V1VM4A@`xRRGYowRbNLlZ57d0&0oJJA@r%U z$3kBS33fs$mV$J1gfyacrVKFEBo$2AO+1GQ`L6LcL$rU6{CWg=O{>eQdzRe2hZiegL$6S40#MGwQ&rW6#s72{hG17G z`Wynkdq~9BE(23D1wV(0V5`%?ZRlf6{^BvWECK4RPcq+f{)~DML@3>|?V!oR<|6#* z?@9s-2DO+YK&1`ff&o@X5k;gmF({BJF!{Uu_j~J8mDqCIQw8bWW49+I6N740%dtJp z4vh$HWQz_PLXV#HX9};-a)!6*b5vn=f76-ak_Jgn*`CpgWlN-EkDB@0hsFBL4FMQQ`HSA5y_w%QmAz3R9B0rlkVC_5n{)+G-+ zM*_69_(M2e=xjbt2ImGof^5}bXu$=C7U_+j@d{{a`G!59JvfaxNR`u+rg>GRl={k^ zsSY_8+A+6QudCZ-C^*xZrNg<-&F#VMvxeI0Km^mdY27$#{#rlPjCL>8aOmkk!-3iB zRPCUhvRb{I{m(>gmewlvv>6~1(HQf*iK`ED6jEj~UaEB9o}edCbEyCw3oMLu!i;M) z*IBd?;=nxb0Ay(_Z*@6K>AR+DFDo}cq>x6NML{h$wa$hjNSo>3EuxuR#}t`jR3l(!H!R?-HyjRc?z%XtNRoA=~?EI4r4qYCKo4;4tah$iFGwvu-aBC2- z^8CMvasNO489%ZPDY&RY2N0+5cj_B4Q0y;7FjegX!nsY96^5IFMsJD!!%#L313-}^ zMhNvU=>|(gOdpQ=s5UXc)w)eo9GsmCa56n5>(}r$v?9_#^KRpXt8{l=>jw`S2#te|o}h;koBD=yFBR>uwbl!v$EM zAt#tHfVSqT){hUXv~9Fy_&q$`S~rIG&siQNcrPBkZ!?QY&Z1+wSt5|UVRNP$Q$D+&J@Vu|v?2Fu%7a7EmOtmcBkX4wTF~}-)c7Tm4zcy_oFwe{BYJrM ze9g~}J#5zHrQ&_391Z_#!mEXwwaSGON}7HcU*MYhc$LJ#aD>}f@T-i?WWF3}Fn8hO z0ssew# zR-tUgdjN#bHvjtV*G`{BFdNse-&ixVuil!eYBK&ga5-MI_b}0eJn2i@!30q<9(U=o z+F@}nV?T;{~vWhNvm zZI6li(6?AIgJB7}=41^FcNr`aA?W>$~73)&S$A?H3_^i-T&pKBlC^lpvmq&x};8 zW9V{@F9DwjDckfUih`;G-H`f$``gO?4rXocRJFED1a1`f21l23_=;?~J$tHLeDrym-o6Gw103PY|H%6*Gaz3W5=t;MA-CWej!fxBtwFCv*nf?e-5$pbBh}Zv}7+5ti8W+K#|O z|7IE`_%G9_82T^ zjLid=r(TG8v5gbNa|V@Wq(oiRP}_-aFg-Tk7#Y1z3-z0i%HyWVKY8VO%IqUiZ0kDz zNb%dt)~8ZLd|%9m*vhehzNNqH_%Us z;bN*T)%VA%m9FrscUfQQ<~-TVl_`~O8>rsC-qHa=9ALf#=!!=JtnSj2vL7M(k&+X4 zhMb0z8200vfYf67t2KK#oq5eiJY8W`A{yMvElQ1U1*G!5skrjK-8wW7bQt}R9{$R$ z^E>sXmf;Jr**`BkM2U%FwNf76xYtb>vi!5m{1T)YK<(_<80TpF+5r_rh#>mL^V>gA z6Vf56`Sg6#vO$}f>-Dvd^8i9YM!oT>nh_VdkFXo zfTi+~65WCv^E9DHe}St~9kWCmsfSo~_0zYi#q>ho>8!H#HkF1l{J6^Bs#3mov0DQ#m)1rW{z7o6x_3_t%!B8Ct#0`qY;y0o% zpapei#$FOz)BL`jHZB_ZWS#iy{X;NwuVRAK^9Sl6vL3>UDSG2t zaLhX5K#bEnl}>Sdl<*7C9?t|0K}w7W%c@#JS72Lb#z{!eOrJRmyv5<)$6TXHcU_~{iyoPAb>Yk9`XSZq3h3Rm+DfFF~enyM6> zdxAV)qtU0zdn?y$j~lA`aWAB3r|3yK)wHo)os%KZhK``E1C;I?>hT?{Ibq=Js%{4`nN}PVWVyys!KYrk#9z#{C4Oz$qdF0E5E}pDSkbOUO zt{;TRg}YFjK2f$viU|!qrF~_tP13+19~h`|^vlVCbYE3Ghh+Zc$@zp#w>{vnjtJEG z*kCy~Ga=mGUDHtlf7;L#SuSvY8!31?@M)pb7VeYSknolDO`)-SUpK2 zUfd^oEYAC#4*O>(R=<4Ip#UVMJG&x5~D47FwCuo<)DFG>be48ddRWY1Od7r5gP zL?eLPh7vN0o2wdmfvXZaq-8knf#N;8S?bgq_`ORjgFk;T6bj&z)4Q?>WRUMS*Ejxr zYbpc~c^mJ!X@EB5OOli*I|7m`n+0*pvAEpge!_ic2}hsTRYzv81bKWd{5>R5WvilV zxvu8KD=AWQ?RJ;9s`KI!pRR#QQDdce%9i1v{!v94GENa#Xs9N&9?E9f!D z+9`##n$D|F@Vf!~`zHX{uAfy2Kh990UvBsgBp9X7@qlkqG(xw#*R1Xnb!xwQ^Vi{h z4T=$uymNL1W?W(qa!GdYSxni2x@~8=xMNM6Y!%xOLM8~% zacyD*chV<)1BKLimqGnhq4S^JrFSx0)2FajM>$S+gOg9~`9BU75T~7HC0E5m+tZ@g zEE#tK&{qM`x;UA}9vG@zX}mG+Nw+$o({^pZrB3?=rP#lKavU8zMbc0>c=w(7x&Rwsa zf*1o+%wez5)m4O0*c|k&zFm$jTsK^pM1OVsMQiEjBX-xYNx73+atQ-=eUe_!x;Zrf z4;pjUhbFFoHd+cD=gn|NjMhcHS&_%n2+f`myJ@fa^Wl*4r|%~IV7>JF@b=H#Z>jRG z^m>-tm&yvgtie$p!33X={snQd55t`XZS0J1(zU1-x2R%R{&1cT%jmJ)I`2$7ahaR$ z->%gPfW{5`{Vb6!B5d$jjC?s@$a`_T-i*K9&(hw7_ldhV@3kVGx_G6o(w{~rmD}-U z>4zoxVFjKP*%m+;TLa+^D4zsOX37Er=s^x8h5dmcJnwouJo=tq?C+#?xwN8^1O6y+ zgi*!ufMz)exjIt7bc}6hOQyG;W$N2K*~n?+I-@rKJ)zA(`TNI7Ui@aCo6K1)>}O01 zZp^b~eEJix+{$8=Lr(t*dNS~Z-cj^iMvF51MCOva!;QdggL7bEv83L>S9^h6`X9FS zS#7`^#(9H&j-P4czR@WdtA}b+@`bHAP?{E3j3DB5x^k{qlN6qySFU_|LykVr2@n|<@Cd@ANhR< zy~iBDHd`_BeGBk-fF&y5D8)FpjP71&RxsXLPzJ+|?@$xl#N=MEHC64BT?>XGL z7PT7iqhFJaSvbAZ-7GO2SuWK< zbdEM~xnyV~{iwkdASuZzx)^*HO(4BDqYTvwX% zI=t_Yfwp=7^^R7YUo&JXaChU*|0`PH|BgQRpL9V(D4^`yw1j$M7+mJe1|2}-OvwS* zLRHLgKn5+VLgl7y`3#b8Wm*kC4)9D`>j)T|Vi%M!SGKH}kgeFWerQ{Am&Sw!?FEm$!_p8f@uvIGelap0Ha z-!p?aHHb=v>8ne^CjmI24E7BtPE@(b`LZ;u%yNy6Ry~zGhvQO-8xf6&jt^tz7jHGSV>0f>|y4lG$&8au6yPQKnX-h z6>XzK1J(T*(Fz4Ic_=G$_Me}TOJwnub^%F7gU)9y@*gUE`nDcfZTn5`(sjjt)zGLP zKB3UO8ueGpK69Bi-@iq+DSvP=dNec$?S#u8>T59KdQyYbTZ`AAN($lS8r zfApwq3+VAAP_2k)j=Z+agP2)O^dV}`Qk;>=^wlNQdezat%-SA ze=L-L@|+SA7yDV3Y`q(|c9H|8#GZm3gR4+sv|@TDd4uEmcY1W24GrVwoxgH4^;uJl zc8DgAGuO2@@6}Iz;`vph>~)T~=JWQW-d+&x;oB;^Gn>~R#C<^P#hc2F-{|pqKfP<0 zJW~~4Ttv!n9WJr#gnxVRsN5~({K5kpdzSw#R%)b6?aZ<9!ssV)>fTr{>8(jHK5J)v zTA0zs=3u5YC|>i_Gzslisgxp@u9D*fJ-Ae60Q+vC!?N5mjn)66!^KlYM|2gxHq9s_ z)eH7i{o6MNO`-2i(ZCK4kR=pp6e5*(E#Bh(tTn=am96`yQ8vQgJP5v^7}|AiCN~({ zq11cbWzt=<{Z=r4d&Ke%cs?-W@LoXqhpe{`&B6YFuV_)?Ur>YYE7!4IzX-^0;P^4I zDFo1k&lmSBn%|gmNTKOBv)6ttfng;`ci@9mc+){L_!VX@mCd8*c@FsXG@HqzHA%CN zOk#dmFdv+g%F=`9H@PuvqFHx*vMz;Klpx)nXayHQjF?2<%B(OHZKx73Ykhy-r~di- z#+t4l=Y9mu5C0L_YT4Ua1x{8(CSV#H^B+xRm5zz>%Lef3dV?7*d! z9rAegQMuqalf>|ekEg>L$dc&^UlT$K@fVjc3KuHv(`X`YFTgyT?-kjg z(KYe((Ls=1mrBb0N$%_y90{||`}bB0HJJyYvFL>%YKOM;jDN4(OFiK2G4M7#XmoI^ zHfBDmGsr>eF%sRHaSU0H>P2^@MOo-pe|eXak$Ni(dGwYktwX=-?VqTtH3CLj=u&QO zfPiZ#sDzs$EZ`TfpNbdIY!om;zTPGm0RHAlZ!UQ~jF<`qlgzn62ixOb&a_($$z(P& z3l0{KKEQHzjZzvuwYbVYc;Vloi?<1B%*-tUpO*OS#9-STn)GH~6Yx4WOcT|7eKS(d zISEgH>wjGOvVp|zO0%?Hu+RQajwo*e%wh{5<7C_!;&^%Qp!YD9I8+7A2nJMJkANa+ zBpsVP?sX{x0ry4`_Zx&Pq|Xs&puH5pkNc!dW#w%SKaKo9$j#3Dx#B1YezS~ zNQkF}yt99sqo9zq8{ZKZK$ck9`4Q5neE(#Y1XhAZ#$C>8tIg&f6y=;p4gb7#u0^)# z9G<%b^!PvqBWoLU2sAG^JUA#7>ZKWcBu+c}OQ}|OH%kwR9zJNJeEucZ39QHgd69dLW%7RC z!7~#0EyD8J4(R<`moACM==QU%jQwaJL3eR900Zs zz+1R-d+@YZKMDhw`QGOEU9c;ivvhHTrHe;2I2>Qn{#@r%ld*ZOd1no8@go#`{p``h zcg7&mQs~}&#@#3{O`i=zzz6^Ts9E{XX5<=fHb)6bGGNtxM=tchmxwab%NYkP>8SQEV~pwcq(kGU>XHwt063#qZLbv}yUN5_!GbJK;?_SLY1>>nMKIhBQuc-0fVd(n_HJuPiJp1ds80uOmq&K zH4goT8+mMq>;r7(cTjzV`gS_28<}LaFw=qD!9sUNVe=#V(bO07KLiai`%(P}d-#LJ z#osdNljJ{K$WNHonx_I6zdf(~V8u2Uf!|WJ?(hXHEMS|EQ^Y3LKYYv^3~`zVy^Qjb z*p_C|W@kpzNhjmmgV$UhEwfMmoUQQ>)v0K~K1|ta*R%U#@=zD*9H)+tdoOSu9yxc& zQa}4ZxUK9c&IG`!1jlzf!H3u>Ix;yW z+~(ZrCfV9270`1nm~IVe_$8`UEBb_+JqL9(-v2Wl)kfqZ+%Bs*7V~29pe~>0i}%;3 zs&CInc{V^i+|~l#Hl5ErZkZsUU2x;MW!epf+=izy0&B3^>U%ft`M|xehhKi{P`6f@ zUW40WznYHuu6!fWnY9j*5su#!^_NciG%qDgTK$|p$_BUXTM>YLGhbq$IWBqfWq?CO z!c-aTYQSZy1@aJa9Cva~Wz*muwtgd5V+^@E;nAH$EU5#Bn!~#0y zpKeoZB_zHI!=NJdC>s&VUxM!g;%M)UFgB9q)HpO30CucWj58C2j7cHUEB02L<`9L} z4fdKld_fXnT#~3VdV7W6_BZR-8nB_*sb2bR%oMtbq6a#qCZH)_pAy(&yh*TeedwR0 z-@rvW*9%x_O8`kvjB9+rl@uu|d>~`ZlYSFeg6sra(a>V>DnhQnjj4e*swu8d9z(77mq2GVyV}wEfBl@EDpXdueKslyUn$O(+U!pJm={iJ?L%1AS z8xk4hYVAg6;E=Kbq{Z2tNsb0;Lm2Y^oQ`2d#QygZq{>?I95C&|s4B~&sV{8HO7#{yiJch%Oum4k8a;A}_{b&1vv zbXcuS0aiRj1@#&LuJv{2%%(h6%!BFTrfvrquWJJIiQ-n?j^gnjUT}YJUc|xH%V>fA zj`0m^D{%?QOh9|pau;!!z%WI&*fnu6PuxyHPozC-jGSDr)%=~faw2&1T5eNR>_)NM_J)OVvz}rBuvQ<7c^6`nyqQENR-glkb?tTYy_9a( zX`&!S2A@wQe?cS@IN@G`A<;;l| zZKO-`(+NI;`nsPxUt2ldpCot$uKa(I_om@cK7QZuh*DXm?E6%Rl9cS*B-s)wg=|w% zXe?zNj2T;&Y$1dgg-{q}Uo!S3*|)NdnaP%I#xjOke)svi?)(2d*Y)N(j{EiVq8E;X z98Kr>T|VE>LgtMlV0qm$^rXl=(YrYz+97cH1hsx|__5W+X>~R37LNo=o%g&wJ2pg% zNV)0VNv&Ye>lN7TVd|WD3_Z@}Qq_`nJ}^$lvINHbhQCX>-}k9ug7lgnLVbIb;dxp-N}kbn`T*QF2=2sG z+9U(~#T;~!+B}#U#xSHnTsyN`rjWIVmxl67NB!FUOzqaX)as81%x>r_9k$+#c>{tL zW1{flNMTA^8yT_RF1&^Vta{T882jD@>j&tR;XGD)^9o#n91%ph3slSD!2F*N3)+qLt3%Fl&J5Rv3mRn z7zr?t)LRj=D4-liJ{ebXI=&lv>+mnNhpzU6{cOG3j(u!YYs5zvYd7-;gV?ynWk zudZr6$f~38G-pmHq*Rx}xn@^}+g;p->yH^aaBucv+t3`WaSYkgOlhWxh)&$IJVvua zM+Z8wJ3=i|{2YS{9B;4Lg|=P&{m!7gw~p_~04Ux~TC=s{3$>N`3H<~TzmKWHhvQra zxQy#4p2=NnqJLT(A@|0r*hc*YPgjrH+iyPFEc7wrbdqO3?2aJF)tVBY^vSr(c)i>) zJ{M!e5pc0Ji3ZJ-CZR=EVPM9E&xpE*3WCw;?y8G_Hrjgxq%y^a-T)i&dvt$-=8E(!y zC1V*{qE_%|-mnA|os!S@{ut*Y?@o3+&J~|5CB^)Jl5z(aM`t;rIA1kX^gOkmg`FgoT_@#^Ho}XKaOXh>ruVw^16q{T+adpl%cxuJC zQ+w!?Ekda?I40V!M3w1-zg-`yZBT#d-1ss9)k}AYB8c+mLjit#aue&}C z3H>yO3?aJ+A3g&v_;x0Zy?L|7XX5&enHdk$UCweDNd`)|mIq98ZotXTM7hXjI1v}g zx&TCL%SBZ8sq&Z^k}>c7g>3U?O;4UL;z@uj_h=0O2Fa}^GH(ewVaqNd~8PwJu4)9>W%c>N`I&^t7{Tky&u;4%KD?5 zG4De%J0P!3Dbj@K9Ll*NW8B!s3BN2=d&hGJcfAYlJU!K>T$l*KD0i2=a=B1@v!DGv zTA8H*INSb(OuJrZzA0uF@8&wUOI;=}(6Y+mYIQ=T0eR;gtvye=jMkDbD&jGk-Rj0Qm6p|*9nbr(jns#{1pA%f|oK&dCsd>XT4s3D^hdf zG%@9A_}lJn@H{ApLumukj*UI^?(#lD^X+LgEE>qjA$n>_-ul7SA^Kkb%A=+P3rgxO zNTW&5ceE0k{8DMXoM5ipPsrR5Ep!jCw&ABmOtt5o**+^jR+kg>CA85Pw1td4JG(K= zmcTu=t4|l^!+%?DbHC3AC2)-mii`H-yf}7sMr2|67m(Kb;I5&u^hdUa+SC`tE$dRb zXeaq2x07M$j>&i5y4zk?p0yB{Ook8@02JE|os8mRX153+a)|h3=azc%{;Ty@iwrxz zC-!~k)KA%m^Su$oAXiLSD>Arro6` zd|2F}_{5yQ>(J|2(vDF}^yK8mC{gr7;%{rNUdYcdGvTcxn{(+fX9_w_1$u%8SThO7 zZk-+TQ@r?lb5CI{^`=lp!JnX_i=q*wk5dKXWM1}R`{H@{sVK_798F=?B>o$=Q`}Ml z(a|w~6k|x~2}4O`CbFa*)T$&qa-Z-xjSlh$w1?RHUJ@1x>v26buX%}$suT;*m+-&A z?RA;8z+JlsecZjy4&H1t3GfUyJTIOSm38wgwNgm=@l{e2tAt^l{6ZJFdZCcKG!k;qMH3Q)*_kXw%F4uX1I6^dzXyah?e>Ugx1$0=CLPfP!E}4E@NOo_dnKeG}YU z;Co)WN@I0_)%cWiWrGL;dG*5(S9EfYp3T0g638ElUL^&QdY}TqJ%-aZr@KX}+HgKN}FY zUf;^-JJ(glWv*^F7M&wE!;zr4rLu~g5nxHgfS5hZG~B{RC_5C6KSk^5!0{@1DQ-2( zeeNsf^0X4#*Lr`osDU+HY0O29H%djzD?jZoK-P0i&3{D1_P)^H8XD8RmJIkHuAs)= zu)6KW^*8|R7E3oqC@t&*vsR;r4^8^Ph-Xa`oCoz|qQago{_t+T6F{7FOWh_~KTWhh z2gS<^uA{%XGKAy^bzPDM9e8d%%?@z;&}*Pc)6f=^O67Z}2{^I&Zb#M#q2Uy4_Fcuj z3o^$JJadp%FABV_Lv2|KtPx4v9hT=&#uZ8qVkQ5jJivt zpT-XIis?F!o;6z$up0+-C5A1fZ2lN6gkpT1qF4W>X6Dx~{V#=aZizL;?%pbE@JC@} zUwLVI%>jTk9z97>@0f#7mzmjnxHu`E23iV19Lt&V31K0V@zBeAuo!E`{NQ=g!A|<- zm4Q22<%$rAI`{+nC002C-j5VX6TcO8UM~BG!-G@pYiDq4awRVZ`)#`TKBmVNp z1M$>oZP4YX7RWAo4CW5FmSITuL-k3Wwr^0yB+j3zF?+G}_w4y1&DSO~{r*tbv8DbC zM*~V_0z}%%EyJO;!mY4r<+aYMNNGxUIx)wRJ8ZgZ?!DcsGBt&F!hYM&tpnT>W(^}= z#SH3F4&;E3YlG%Y=bmQ`&Pz?PWLD-m8Yt=WMI}yu;Y9e_@^|yw&((+5Lo-|Mz%Dow zlCEvOgq7MUy}~`iNz%-^ahxovzbNp?rzsVn=XfwUMvc)tX=a|aPlG1LTXt)&BPS*P zBqV|KvL`UN?mT7x0KYySqsoW7p$^Vd$iTEGUIkMHjJ7H?(-+5>U~ zHI@x|%`hVztnUtpcd*W7>PGuc-`2H)_T!EC2bN`U8G22Q-pgBtIZk zx(axBLr*pfk^uddFkZCfqRATsiU)dcY}1<|o3A?2F}tQ-d9zIDSVsG1C!1u)W`pIN z^O9)GF)|jQT(#@*R}O!iU@4c+y{c&re*eYmjM;!o=QoGhlDe7%mEw@gOLv-#Zx3_s zHL~Q%;~uYBXGR1IJ!5%GpYEt}J$iz^ZmA(i!ixb)1ItK!)$*&4l%6iI=qd_A9+=yw zKCLKL`DW><3tX?JAW!4Uhz;(LKBG4t_v$(GK-gugwmblRia^llMAt|?F?|Xp6FyJ7oq1ykOPn7@TV;e}~<2 z?CDR{6uG#^MKlBtiG@Ao#8+JWHg76pWF8|uvD$7}Av)?cJb zbtl&K7jIC`=N8X{!WYtg&sy9$OkhlrIjP>v%4T5__!vOXP~Y(UOvASYo0q3|Er*|{ zpQ&oDh2Q()Ev*#eDXXFU3Dj)0osbp*CqbqoZ-n4!@5f|^#!fqNu@IJbB zL1xlt&AGc=Bu~Y@UyXaRT#KFhY9SA#!nWTv#1M3)Ua)`Z52wW6n8?2O*EZQk8y>2DhWw#OO#9lTZ9wwE@^N+dAe)o7Ju4(z!npgytWXgtQu@xAWriW6uI?yiZH3J6U;@6yh9_Aefk$F3Y<${RgG1@dmKG; zuTG76ka8V29w?W+&Anyyt{X9k`c=naAo65AU>oM^T4^eJYXQh3_zr~ZvM@Damaj-XnsxW|@|>W#=*xfv#PY;VAgzS1l?6FC zWwR#;T)KO)U}i9u6%OlTT^V2WI|%(#P!JkAjWXK?AYxB)5%W2TWxVX&RsG0S=8-3g zG#h+41;E6Q35oCptn$~vQ0!31b8)_2BMm9#N~v&AxYZWy);{j6wb%1Mtm(S&vU%X+43r* zTEEHT`SFtlT%+~nmNfKGkv`}BL(CHV@7H%foi3(5ps31;cROG+elR}IIL&#@F{rwB z6JM+oimb~%EAl0jyHs0S*OUiLm998R+(|uiQutCaJ6Uo&#AZbA|KP25oQEr{FO0Pn z*l{$Nz*N}TJR4XXBHI%Gv`BjSYQSipp|67Mw;4Ea`sLpOXmlsdyDB(gm%u1(n?{CZ z+(7xqb(!iBkj646bPNzBTp-_5_PVr_ydaHr2vxxA$(Pz|^PPaSBuf?U^jPSt?lAZt z^u)>c$+z4rRj5rb9|)j-Dxc}y+m`ci0QlI4vWJ*JAi>ZDLf&2rI+9yTRu<{|2joF( zSLU4JklJGw8)6iFS7+8>U3Cn;Ypi=_oLI&M4YZ|jSMn&X`W=hNRG^1Qr)Ah1Ho;g& z)^?y)tw7NU0{&`LBtSIZ8*Kd_MDzc@2;J3OPj_!#KWp?Ni1Uq>&GuKi2_o5;yvz{( zLJgVN>*2GVMWE@IkNE&PTTe5)_x5KsU~>IDa!|dU#9u_*=OFY%ohYdY%>yCZ;{<6r z1S~@U-ii^>k(K=Mx8>xp;vQT(cB5im<5RL@W3(y98>7}H`ym-bpN7YVqyalU;H|C0 zs`cdl>Fa(o^>&)~LKkHsTwXj*==OOS`RLE9Y5S#PI6;Oh*_#m4HblFCX;oArBuDsd zaoW<7+k6n2PVwgO4^Kb;wX9gP_HH)!f2r{Ny!=NW>^FRG!W)h-o7sOrU-28}1kAKO z6$ONwluOw*ee~G!==}rg_Y6j4S@UN7|b$$BioU3B?sPD zq{+r;eL+WHwHuiUEIm{onj7GXt`T}RTr1iXX8TEVFw>CS5(ib0Is=bMvPSdF zN~?H_T6dAcjEpvGQivN0)RPpiWMVFeH3M+#&w4%^TUV0YJvlkj$t`?D(3Wv--4tPb zr-Ng$3ZT{!SpASw%?2dwuZ~tIUvtP8@wt#rG|!$V+^!|}&b2w&d2I11JNa=Dz_hjD zXr07v7m5Sbim&4YP=v6&6SVJyWGMzO1>0#>ZcMB#_|o&4hBg@9@{p?*5}G7rknUzE zWa!J>B$$XgkTkJ%ewNdK$Q9gyrpXB>>RcdObxED0rTLOE9jVAGRpXQGHyhD>c$j|4 zxwjb|mdcPy_>p@fH|_+=L<{c202f{=B4+`&o=z@qXI&ce!0I@MeyGI~Lk27@Yce)N z==8Ojs>JSa&F|%{g8spkIAi29$-MkCm@ej*dZT%Jw?le z4evVY4Q@cc`GcI5S1ZQW`%|xpKrfq;Ft`@T%Awc4yO<&Ecj^3!&`X-v6oG%%QFiHV zjOmyTR=~Y%Kjq+sa&?~FL5Q>RR~wDnF3EuI8p!<_#U0Vp0smemY_-n$T2R!JF0+)U zo3o7~KUyB`ihAL$RediAdpXmsKsE2Gz!0F$s=!l$8QTD%vWeHv;gB2acS3*CZ2&x;;kWt-~9W$EnKo=K+Fr1KN2`j*6vOsik=bOoKazxO!PKG=F0M z%SwHZslzVux5%pJ;X;@0D0z6J8$Fw>Epq)uEVAW1(U4fCvta}x3Q+DU=kw31` zP}UDU5Hs;9d4EHk(1&x!@zLV-X+;%9-A$#47K8_|y;@w_l^=ZPQZrJP;&r_8H3?*r z=g_$|*GhpLMc8*h$QF?4_)aGw$PwgY`YpJW9L@J~LD_Q_@3%3py{qMB#6m@1o$S5M zv}dkD!#&5R3)dC8XR{BLsH$Y|cJCQWYBaN032=BHlsZR_iLrl>v|~ZG?Ce!s_byje zszL;xE&2ChtS$4H*7tIWX(T>v3Or!95Y~<*0N*;BKFhaBS=dtXJ zwDPXDU32ZMtL?F@%iYMl+RgZqU989P0(+!xt4G8wwrDKSRiYtmEhkumhnI`N9nfPdtfM0s|M?=x zsJbnFt&8j`(c7Ebx1$n;7d<@L696|=ar6RF*XYL>RdvK~Z7mk{`wN2A&2y1EMSZ$C zf=+ZQLYDji0%u+xj?l%VmDW4RPc!wcgYP^h7~i}gj@BlkBQsJhoI3K-d-MI9T0B`;#i z`1`PqebK`;E!F9x^1pzv-PqEPfg$@X=R#egR8k1dE@Aht&*4HM4)TrMb3eqaznRl{&gj@? z6@k6+&(33EzCoXFnS;-s(eamgP>sk(v=}%U14N7IopW-p3+~ht82K}vV^(=*KXYKI z%Ukk$W7WCL)Q7{Dm+y}M@qr(ZXb&lMq@gx&JWG>_MQI?pDVO8P>eQ$t?{i#mMB$X4 zuzmF;r7-&kj}c-Zry_YNy~gu~<6(l!8HaVy7E5p%HHN9hy#_p8y}96!>Q7%6@G(H% zSg|jebc;NhtCqs==!PZCfgkpBZAcLqA6|x>gWwgs^uvsJUf?D41W}{F5@rDaEX9Ts zC<)d?V^R?T&-pwfz|JPhHODynd`;u{n*nd-88`T4+xfviJuft+mwE(=En=g%$IobS zfD?=P)J6;m$K$|wOj$#WZe1sr=XMG;8uWc!ZG8B322C(>mUq;3ksHWD`O*hLA^^<+ zOy{7@vy{r($(a*1JS2n5d?OwLxypk*jM-y z08yPCfHeeK6d9T$s55JNN6ER^1Qq)^Bh5b{x+V|nvTNNkt%n=@8|%Gy-{G0gzU?H6 zjR~{#9Boz!=?}agtP?MZuvnBt%eZSLrc3NWZ9;8!L(Q{T$#01H-dM_!H`ep#e}Dkn zyC)1`$`Aq94f}2UkdoYyYrWhl1^!Bw?alt843J!;eG`sqKdfxa}&YsN7j7iGXP~ZP{3|0r5u`uNWaoyCr zqnXcho|aBxXP-fLT@4Tcyozb^vM{pn2`0AWG2>9@k$lWb#$5m^ zjAm&tQ_J)OX@C-8(mBSAycMVT*d7R5nB@w&5LTCIKDl?qmEAtz7(8prkgwECv>B>piu!`R~8B#1Peggy-edQRI#mTYmC_lyFSPMNC%)S!fiItHY( zB|Sy7JT6;*N!~fXbvUC}_9f_#ogNYv*Rm_(X`Ya~hmjya9fHg#pY_k@1O`Ok@Or|b&ED~7S9J@2w0w!i z1b!nwZH(d+DtA{(f~G(iBlqZ!qXs`t((kiIM;SLpXP9NkUv(35Y$*>=H?)>2&}|kh z`RBpvkw|qQdh*(s%Os$Q9oaTq7&yn8{|RJD2{+_e<=Mx_BM5Ov1_~UMws`SP+l%!e z#yUB3-I;t7_?#{BOj%Elvla|bJ1ME&pa?$>tK$ekKXj|JGEyB;GKNl|S$m@&k_6`IEG6dNd zs9BULKn9k`5`_m()({4itW);kD2QX`3n8F8=X+e!xn}h4q{(~%w!VA|IG~YldH)8c ze48j$tR$_88ciGfM1yy{2>r;~!1^@^b!sN5H5kU5r5ZimX3ez*D9irVO z4gM?LHB_z#4vCt~sDlV|h#_&jM_2NQWQ2D&pU9qXKo`2IxhOq6k}qrWbZ*nD&_&=e zik(yfq8&S^fm15jlX%+D-BFf`o#U#W(3ts$hDk}CtDn35s?BEN-R9n@qG^{I;pTK*KUj?GQ=F()9+8Z;J-3$+pJUw+(ZyrsCgGLi;R>$DiEtM z`1HW*YTf&(wfzl>vnN31&tWFBxGf7U10NH>0&?AuQ-8q{<<3Pb8HYBp47D+f;X3Wh zg|Tm^H+I;o|J>mOdQc}g7F`WE8rif52QxG%-s@w;cQ{uOKP-cxs0_vJJ7%J$yRwwr4-6KYQ!#*>6MyvdoSKrIPh!HYLb2L+NK97tiB zuv|>DW7&$Du#@9_v2grSs^SHnfud4(Q(U#&A6TXw`dCXEGpXe?@*cAkQ0}3p*QhDv zuBDg{h#tcxa&epGtNr0lG=ZONI8Jdg{@gE-tYQCYJi74M%xai@`p-wZpfx>q)DWaP zCcYXP1LL=+j#04jI$VL0(KRm#tAlT*oj#mwrHjFj- z`|HA#_ARO^ZRP{*HGN?}PF1@f_q(4MevPO390t$UDUIy z%bD5=3Cx_9OGvFrs2hHIhS~u{+bYVnW4M$STL3-uRQL2^#TSXd!<)5@mvP$q5^8C; z+q^73@iR}~Zw}S?!@h+6y-ana3~ijCD)F(g=9@-!Xzkd31ib<_jP5XNmM z;7aTaF~+@ z*&o}J{Cj(=Bh^id+C)CoP9cKXR(BlxKF)%#WCPB8bb#1v+zn;Zkpr-m#)-M5IP#h$8a&`ysxRB}<pUK*XMbki!Z387SEsN|F0$WQE^SHzb)QY~|I&(vJ1xq21J(;f9=-RV;5XKd zWvlykFwe+C7Vm@6*I-k>MLB{KY_ z|Dq53v1Zx1?HfHQ`g~(ZQp*c_=(h`f2e5K)Mll?IcZ)xG5^B7q?Hi_7K*9bExgTd4 z{X|rQeuV)t=m7@ly?;Ooz-A^iM+wQB{sLy8p_91J-z`~&CFr$Z=#^u*REE=}K*+WN zbNn!Lxsw#SCo4Aq2Y?jmonjIvqwWxRr$2}>nrLNBi}lMQDZN`GCT*)sXgTU55};#e z0OTlW7PcS%`q4q14q5`!J4Mm~?#h7mya{dAtm>|vTnN^}<^aQo2xm9b=JP2-el}qa?l%Jwmml6`nrq;rxL7o_j%Rt ziC%;b>ED?FWThQ1X*&a!B}|}3>wXru1da$tAU|3@7LzfBXq6Bv(|ZAbKe7Ui(fQiq zZ|^jSIt8()(8GT~MWMnAU5Xdaib(LU&FpR*?qc(hYWcIJ(0N#^mjtV3H#4F@D%f9h zCV|4l-=cY)VexOF+lQIfEaM?(wFB%hlbxotZTal_O5aoiJ76F>gPZ)mMJze@qpcTu zi4vhLg^OJ-88k$o+p48Piy_@v)C1$iWG{eCtQe-UcKK>f2|d^s)2}l%@WOYI_+G`+ z?MJds!N?H&aGK?Gdw2T+)SEN(X(1+*pH=M?Gm9ph)B};J_e6eU;OO?Ws0uCMp+4b( z^qolxo}#9oKCdEIDzU$O9v6?H$=r`cFBDL|Rh~ieNAV8=-xyVUE!m05sSlgJ1o@k` z$}~s6`~%rm_yA}LM{N!uWsh)?`>3myJ02OY<*|>}|KrgN1%7m)hOHSNj?frNuoznf zWZVNTVgNPx6Z)ISHy?oRIN8z-6wh(Ne^pH+DL~a!^&eH!;D1z2gMjkBcLP0D#6-6p z_(5fW@$^7M$#O(0CX`jX!=x}WzN4x4h8QZ#VY)gj^j7?Pn>R3Yc{32Yh?d2|m~X>? zc{Z2KS%OJMr!K2*OI!tvl#T(z#@z` zo`dYt|M3;8f$p;#!%9t_#rIe-%>DtHw_11eZszVdiIcp4k1fP>mOu2zNicNDTd!3` zSoiBE6oF`yK5cfHl-kjswvq2s%|p_-BXpk&WT>T(ti=oHe_z%E>X-!Xq<=s^$Jl@B zumPqPnD(|H;jm^%A??=S0c*t9wa8f6e%4{bmNndWX*BuT55st_np=NE5~-R%<}ML{iC@nHg(cV`w}G4y~4){K1TvY7>IRf zIw&Z8svQz7_C5lb>W|Q#x%&h*xd;4k`15t{tAMMV?p4}u1)g2+lnY=j-At}j+D>C; zotDF)e7p71|7zdwsXr^T6FyaOb^IkhxxYp`pfxP9_5BfT35nEe|A78N-+^{$EkX?4 z>d`-=9*nd3_P-9ksAPOyrO1G_Nc=j_L8*80K(CR|2SaFw$h0W^c24e#M7C zl;4_FXh5{6m)Ja1%XUV*An0Y|O&OPhuMRs2#QJjW1$4VPzmqq=I}D7}e2P`+MsL`b zy>%$-^u6S<*_%9-e*J~X9`@nmebkoRlAPu$Lng3yYdS(O?7&oHA;AZF#f@EP`NoJ6 zK%y}#*ZW~lx9z;+_`TZlrNHB?2xINDDZ(;u1j9J z$gyDvN#FhSZrmlpW=bLf?0lWid5Qz5iiZyjihqUXJ2In(=RU97W2y|O>Gw=8R;}*{ z`k1!9P&Qq&HuwUvS>hl`*G*{@>AYcU8$|P>nXo^@oMJ5}$UnvAhGLyZJX|KRHvRQY zA&5V@V?MRqsmIRxyf$&*P9UjzjkPzbVM^wn2Q>QUeqUVMYRnVi6O|ztt%_YKViA*k zhh1Lq_>sIw30}IPKv6q;=W3frV&QwgwIR|>@pI6Q+0d&NE*vegm|^*yyq7o?znd#f z=Qo?26Pf$9y0igM|0FcTQ6thVlSU3rA*W~mYWaWq>Z%xB>`^Bac>JCl{;Z?@=gND< z?otO!)%TC~J|Gx@B!L(IwMvQjfJnjrut%T_4*}$rbCC)Hbh*HSl)#IN8Br_WoS- zr5hLYin00SSH%`wcr_hdlXMUEO65VonT_p=rih5HkrvbaI(yg^-J=3`j)pXug{--I zUjCRA5;QJT`6FJ&>qF$@lyGZN&_ZPIu_`6M#Pds??tuVNVLrF6pMGg_*xW-{v?ulN z9q^-;?`uz^7tio1YbCgZUh7%DtAH#|T{mLm6m4tW-3OXUm`*k0G|kL!OLZu$S=C>< z*39&ieW^ou`Vm$Agg@IS_PaP2EZgvha(Y5TrDM)IhYP7-ot>`W%GQJHGN+qLwz{>F z0MKu5^@Z?zG^TTy^I{7(5PBKuODL&!)Y3h=z`CyUkQ;zlE&AK7Y9Jj=qK*5U9K@S`Q*$$phO!{0xk*@oUnsEMoqw_dmce;YD{^U_1p{9MoTYx@F!5hyY<(`YK#_K zMR>k9@Vo~Z*J_gH!>(RZ;gK`1Z^*Hztf@)ev{WOSA?}^eyCD#iD!~5J=QEotP$J|& z4-^gBP~sD+@sh#&x2hY)t2=TO8azpgj+*GB4J_S#q*`a z!qsfaD6wbBPX*IoedCCp6r7Pbe&XzD{R1OYImw~#o~OqtV-maFZXmX_q~OC4fCQq+ zkY|sc1aj{!J5kajIuE|M3uS8U$A|}{qg5{M{Em)()}1e?s8J#~!gWGGU+dFIJcyT$d{JwF@iYGdi(?9y(yUXU#u64hkRlll*#! z@$L@O9Pn;$R_k78vb3{1Lh7V-F8P8b5_mB2@ zEZ7+Mx^fJwD0c~r+_J<-q!h-TJ6#=PbktVrTK(y6ZR>DRk_=csnLKpRbL;T|LgdVQ zP;lBI>MdI*BBV8smz+Mgu^)p}FLvku>*cg*&*fjoe--p}gty)2b)Y9__;eE@TKbMI zw6qAMaJorT;sOg@92{_6L+-sRAc6guZJ!WkuM65bU|tC1z1AH9Sz874n&*Sbh(q5^ z&3q|kBaJ>F;!06q!XI@%@|Fef71M46@N+r!FEMZPddju_PgX`jIgrsogN!lU*^iiT zri=&QIWl6Xn&W;qB2eNbP#E($@0wVgOyEyTNSftOGy5MVnLOT&=%HYi0dAoPzpcrd z$5Bt_9MHUQ*z=-RyF-fu=(8~Ly(uf=*394|l+~xfcbiv`YQ8M!I0bgL+v2w$@^1&A zhXkgoo*|ix6X*A+ag6i|^c5)CJReH%l7cQ>!XHYat%EWD+rzRF(8=+jFJQ>#!_Kru z#2i5H2#}D%XsjKu1etcv3#5diEeh5b9{JN_@?Iz$D0OAzidjFWBo-~AjIi6dVk_cH zu@3W4{HWYAxU1T8UxGjUOrJEg3-%5tm1o*dlw&>uSvi;rc?jr&_+vc+() zS={d`cH9T?2KI6~<=9VA_DT5Uo_beWHLeqfT|>7XIJm;-el+J!T`v@F+$Q-i8faWC z=V>%M10J-zVPkiPAFc)3YD^Ra%$P+G$i)ei=APL9sY}#&$V|C4t|3jNHf)E93RdB zYnxamAl#o0O$0XNSB4Ps-Cx}r(Bo%@oLKTvnDHrU3iz<_5V8cPeF3I?=v)@SkzSKe z2-D;15Q|KBkI3fF1K-dMp=7fro`^f&U4fQjH&FlsH01##9Y+>n)j{q0p>qZE`v*Qf zd$`>`zzX4zefa-bFOEQg?E*Oz9wZ8E7h6d8?4jL%-5Du`znxj3V~10}Fl4)kdGFR{ zZc?|L8&F5$o_#Y1VEQrWZ?%UaP*=b*;lC{xtp|JmZMlg1@5_bse~Ax2p>$gS@!|hm zE~)@?G0g?AB@{y^E(`vT<)R5#E<&XKzm^NpAEm%m1%U8W|F>b|e;gX+{@+vxnWpC2 zUoTo+_&55P3X8ZnbS-#&gzODO>%}u}(Y_L39JJIW=?mW`mYPlHPj%g)5I3b~9UUmA?wywuqxANL%cRf3$ z%GFaZFH%7|`sJ)FY|-b6_7!L)CJH0f!pC^=HO-dhNYYjB&Q^VkaZY_&*1X@68QWPY1=dpgT=WF=sCtiKnu3;0r=nCidBI{84LMvMEX8+qF2ApBykj9S-|DrC8 zqrX*~**7#7k9#*~ZR+(Ozi43kXQuxQM{@d!A#B0TzvRM{E9sI3(hm!F=ub}IYW`#fJ-fp- z&lWaI8CnSJ89-CLD8}m(M4S{uni<_9PYeFBWorLjn3$Iq;-RSWi{G}cDrAukODIau zGnLN9$Dgoq!TCvD=iCE!U9K6z<;9_7q-HhQw^Tn&cQ6>G*|%E+04x-8;wGy-Pm_F5 z&v!gs_)ctQz@4`H{<``i``AB9q+hhVPw6TQ9UOUR9Y%3&Z;@|m9>Z0m&d%WZqBDQ;#(R1mni2}R0$5DO3(Pv6NZ0180>XhZT zdhP-j6GG>|DnMi7+3nC%80VdBO{#JvDJ& z;A!KBHOw>>93O&CG>)9^=s|;@0W%z3Pl-pb_+#e81LL^ISzdZ`?w)P|j&Ha7Izw6> zAIi~>w$bg8GPEqY2vzX!CwRh8+mHy{^Yin%mkx85o&+w}Fe9qnoqEdo56bAm>vbFs zg(P)lFN!7RZwYEx3aX|oCnO;me?KSw6`@R+| zMiw6WgnFU5x9rc&Bx6%Yo0AV*jHJ^d^L$}>usZLJ&{71iB9t0RgSgFWGmG@DTtm3F zISG&257v-93t1j%w5IN#vURCEYBJxxS6Z&G#Z?&2QnPBWX1-jucvXzRDnA}Uw*>-&-I~pa=GRRnrZUaN+O*=`xP^MpnQ$IT1l=%o1<|G3=9S3H zNl4vdL|uFK_u9IA_!aS7=-sL}ecrx&{W6y*S5MxGJD0+HU-E~MmUYFKQu|dk8?oH+ za>=4XMV(IPsM~)4c`xVUYQjl|DD$135-pi^Vupgg%Y1`&GwYAl10yi7j5R8PmW6a- z^;HdTs-+NsJb3Zc(`c!$KDd>peSx39wBh@A{duH2gAL3isM+0VB{K);mDITM<>;pz zYP%kit&!35(vVNM3}HnLrv-jYRO&#fj&Dd){H*yLKf-9)<~JVdO$4>v`%G+~pT2U|#o=6T zLmza~c-{SY4KrJhmuG#{k|3$Nc**Q)z(W&`_i5WC+;>puEiLZ7t^aZvdC5|x3`Off z5Z+c4oA{b|bAE$SPu2vw#IbMDz>_ma!pXT-WTEez(n%DVh(*|R*+CLjB$YbnC2)~e zBmVJ^1B+oZ8mU}gd^RI&-?KHC6;xdRdpw`_RaNyu*vmHEqc0%jZ%6^8z3+nGRP_$A zUS(^;bT|Bp`peaBf+uRy`AoR*wCIz;^$cjhx0Y)R1IP(I5!w{dd@!p7o-u!Vc+>x( zO6Y|rA@uEXi4%g2KPcTMsZUcnOfZec7k#e6dV3P}fb%un!4WmChN}T)vv4$UluU3s zD1kJ*#uIWgJH&^0^b29Mo~~(dkuG-i*snZ4LfrCjk zA>@Ld#Iq#t^FQWKef^PginFgi^*Z!ZQ|1YYQJwH}N#_H-H#x+ATWQM+F^STIVf~wc|Dr+Om87)AJx?4r^00K(G#LLZq^jj(Dqg1 z{$L(Fpa_BoSg&tf&MgVL>L6~P-}~X|w%qGGr{8&|_ynE9WouS@)8tcGGmW1xPbts^ z4kv|iJxK@0RduoG4@By4PYgKLGV@rz=f@z{c&7XSD?#nCRjRT=Ju@tmN<=5)#V&8@ z$s)zan-$$DIqMK1yQQ7Z=$*UcP=d&4kvsBY=}AEUHdiKXfGRqRD1#(Dh3_WxVu^}ii1g>@9q;wz@ zB|>6dyZ1Dj82C!ZoPs5~NJ)$~^Lx!pdP;TN(vn@=9bM6{){Y;D@Kg)^L|wA{ex^I% z33wXQ)^ePg!;oEwM<>mnq0Ire+(=^P>qlgKj7qN5TE~kuA@z-qLnXZ*Ovrl;Q8kIf z5S_#jckUwfr~YOV{<@Z!JstF3>6xirc!T(Q<2)vCze=!Xv3gN0 zv9mI9Mg;VJS#piGmESs#p_#i6EMI%=G-{UY%diqy1 z2_`CYMl9K^v*TxDpFL{2zZm%F4fwY@t)JXJ|L0d9$(+qg3|)0Ry1;8xl*0OQav-zE zs~~%Nlo3kMg>}~DogVX#xBnh!@l7moF3RT_!ejb=)Gf*ex3qF^JjPm(k+BNpg1Tx$ zf6KMw1RhT=SSHC4*&P9yX3yup-W5TB1ikLcyPx+Q#I41Bt~`kItUee!N*4KGqB)dIGH4j{+8v4( zYLp#!zB9R4gK@OY(3f_~d~I-au`lrA%otllST!3{3d2uBeAUOU%#hCXekpypjM@LR z7$nK-+vSYtCqkkaM+xvP5n7sk_Wj%2w8FGi?Ylp~yN3 z*W0y+7aUzVCBgf5ZKQb8H7^yxz~( z^Z9&idQR)24zGTf8aOa7M>_iI7OLpR_)9XgmBHC{|6t zk9je!171i0_VcLJ$V=m`vU#k?n z2e!@zjdOH#^!47(U4OW>Xq)W>e;;oz_4Vs=6P~s3@1N_M<(zDlD(V)qfh8{>Fdvo| zE87c`a+-T4Cl=$fvP@i-C=8`nWSy>|QwS5I#;JoH0WXS#UxSUxufZ7;Dc7Yt<5fe4 z`7A8n8d{ zbfF4LC=LwUddfz%-DJ{4z@0QzCjOg!d z{FCKTv6jnx9|^Ptj8CJ>s#aZ9aQ6l6!n~}v?z|>uOyHmWux_Z0kKgM`Kh4$U4pQ9J zO*k_xzI*E4gu{AUfkC(#2Id*3?!hggdzDWW%CUj|I|XPzhmx5>Ub+4$G;{icQNU|U z@_HPuJDQ{V=di5>-@Qn}Gvxy~W{p>_zz^={+?-ctU9G?=k7qkvm!x^d>DsO++Mn%} zSN&)oCd@ANJzOg6>r5fK%-hxus1?g~Ep^QTb53TXpT`r$7EGmobZyDypiaIMKA569=r6Tc36r>Qv9Zi5{J(wQmRrB7cbapy^e{z)c?Ut+qkfsEix}tn zDsuWXy@(7?L+Xd%8G#GHZ3Nj8{5J21@Y%Djsi_+a7K*pBReP$UmWyJ$d9zo#dFb+; zKO>!N*Fj_P9k>)2A6OmXF#D^Bk8WN{O}hOv+**I*k~_^HPTW2Ax45<9kw;4Ua`bLt z-8#R^Z2$R0rFYVRddc>iDck2DW&rFLbW@91AZbbEDEr8$V(QVgTXl4j{D6JLv{#T< z_ON7sj*8%Vw9vSJQtxc(v#GmHOV3!u(6fAh&3JD8Dl}{&+rFkb(tW5^AE`kWg6m9v z@-jMpYttV)Dw}XcTSjzr#jO$XoGY-5B|o5zW1}b~S^)tf3}R-Hp2PyB8<@XN(q`-W zf%m0E?sY1r8*>;$PNHIfq$fe5M{`agCt3B)_u1P7r@!+hn|xAd^U+p^F@F&2N+btR z?2OLVe|eM_7#{$PGNN(9z=~9i2ftMb-eGfbu4{b`{xlU~fb+}Q44heji_CPT7wwli z7jLg-_EP!UTKNEtHU8@Y7Z7);u&~fh!gl4}n_y~CA(lTIT88N=2u$WQm)nI-Cw0do z?HR|#EgSO{OWn(NSU_Xk9`cfJ-wY}BEbsq=Qf*YfSNfeb4KvUtuS%}GadTRfTY1d! zJuu*&a{i8q0%uG+tVJ?J<>jaWcd%l>{u%)%9W6GEbR}7$3C_@N2i!}|{&yv=Xhgu) zA|P}9SRfZU8=2&hAghrv;;Fluevz#Ppk@9_J3$9n$rX!G5o(d?7!p!?y?Vi=KZ-T( z^vuIg0+8%c@aXO*_e;8uu1?mZb??M+;j^f*I?1)KNR19$KebLZ!jx|h#%$G@`*of; zeK+)v@6k2tGQQ{}Vr65os10{|16S*rY@kJ8Tth@dUkA(~tQXrc5=(m7IbTe@9IncI z8s!r8Mum9P`icAV`02zl`E}Zru$+#FCQ4y&W=B7v0;LCMW4zd$}*-BqtHi{%zEZ@1Wr$_HA z_#N@`!NCr2p8+7RCciIOW#pR-QA1uuYso6!Vj$kzU(DYYq#6#&*)0cr?~o!?8`4;t z1YZ|ak~z^T?5A@#$+9(jo-PQAuXRi+uq1r+2v;d-ylhKUxG>P8o*$4L^%^)fQjGj) zX{sUxMm0o#t_dRg1>K!e_IuhKKy0d*C|teuv#zcw>UTID8%C zrKCk{XPNWhQ+4q~Y--Vr2JjH_>__nth=>Ta7gaui$QvV@Xd40$-E_LyBC-Tp%8{{P zZ(wqHO;+J&<^ZZd5}|K7!;qtHkT&p`4!(p1UJR8$+UWu^>39^5=aBx2-)4MX}+8zrFGe!kz^vT(Rr&*#qFCgGq0Jq3g_FivK zX3Cp8o4i7W&#vw`X3+sUMZ12h@IR&&;|o)k?nSk!q~^k>4?9HMI>DS+--(7{dQim? zMK7JK_4SW?15Nbrw!h|1fr`SOpX8zbl`RHK*FZ!@xFLY082%qbDIjwTw_UX4d&Ca( zNtri?h^X0qU9sw-F%G8D_f^u66>O?*yw#$)I=`S|7A>y^Tn}at{3nr58^3Uvx00AN z*~llM>(ye-TH4#Eu5qxv&|Q+jiFc1K^^*oGFB2bJY|+dx^Jio=t|Q)WI<}XEl#DL? z*~TsJ?Ll&|R7IYTu*4c&F*$p;F-TVfWR;Dh6m>uM-B9dfmyWqde1|BR@Lwo3ED5TA zs+O8~yP99!S6#_6;C=L?Fqx}w+WzU;9k6}Z6loQz56eG0`Jo7vo_6D4E1%DYX-u~L zCVlOf`9hvqKVsU*w;tngp%P>6M%<{dsbRtZJOlD;?aEl1Cq1C3U-&45?2NH^rVq~~ z5wMJqV@GnPZMDs_*5Eq<3SfAk-f=`~_yPKN8lViQk6qzZe!AkxeM{i(Mq~_WQY{g8o7lIWMuTZs_+2 zhCa$@>F9~-HHW}_UEz$0a{UT@2c5MkxCWN%WqH4(mO@6O=^AM#IJ>lhoO9!V`9S(D zsugD4S+=(}Wf)bFTjXeaA*KC7MBvYjziwlsow3~UvAP7E^H129qz>s+F_~ss>cO^c z>9w(%q-kGIKZ$Mh^4@`=-rMy^?Uww!>b25|M&Pn)Evx#+ubZo0oJ{$5hlPSJr3vKlRz) zCocXD)bmU&h(JLrBg(c|eNX%&>-APYcKyk#E6KUUnreBKzJYIeg4GdpUF-J#Eu2~3 zIKu}w+>fd?G-yrEG7Iw8w$S&D%G{a;H!)TLsYAQeB9LjXZO>;#`g(OXjavTLAVfdv zwIYy$RjU|$C(7hN3!vGxOKv&9W*4nm1b!G%-K1_rzA!NL|0@;H9k~xYO5<`YvN2a! z#Dp1(?Gv7MmrE5zg)$rtO~WZIfcNf!@>ieF!kU$yz7YAkqpE!?qbTpS|9?41KIa)Z zv0&YK?qaVrbl?g^kLdvMN(kPeq7*mxL+o8M{`g6mRY6ngF*j)s?_C{3Woztai^2Pk za|>p!6-?@6z2!E}3#*dn)j9i9d`SM8p)WN9sB>7PeIxY(aN*ceEyyhssDHXUIvi^29F*)*YvLKgwn%HQr_57Es?)?7b#vMSTpPQA9fr@n`$1VjgNzj2j z2w)@Zf^?>uFZ#mx5KxK>wSuf~mQ^$`iBvGS_O$qc{Z%Xt>hdEeNL_6{Az2TA2icga z2)jj&4#*j#^>7H6?tVegjk*bc_ZP0}(+#(z%5}1xD$sA8(2Cx@;%(^jlUV!vi1$Noo zORaK1zK9A!$O|RyVF>rbs)zNdvpcVAtbiK+!! zF6s6FPft&QTe3dw84qgHo*}*61=JUcWn(VUov4+cfHuZxLJOL3jxIdWdJge8O5;u~ z`LCNwYK7*C(TinG)Ygr+sSCz_rESuDve+WE*Ah8hs5n^FA`(Kj+o~i*T9N+5Kbmo* z+F4-8x|8#z6R970-*YW%3(9oH4(M%wqCA7^#E9sHV(_S> zMuq3Pk-hysRuBK0-RGHh9}6wOoN0jy@G9-HQAQb=%~~YdBHt@u#)O|*!?9?koupqK zlXrx^I$qv+{Ul@4$Z%=!S@v<%aH{>Znhg8i&XtGUF(-0aj?mVlt+L2asyKkAwHIDy zV8DD{(bA)u8|E7-GP|?Ctky4S+WdYT71mEme)E<65oCCY=M6|~-dVKEf~yC@-yB8L z@QK!ApqVETI~}M~;0q3jGKaDKU*sTVi3`~H1Hbc%wg9u#XF4n|kNTLUuO0s0M4=T$ z&7%d;&rv_()u_g;AZCv!AdkL5OnUxd11DD3&oxGXv)VT?|HEQ4hN{t;r7@a_4;S}p zXNNNWWs02>W~4I`us{$n?<@fv58x?4(z^k`k*@?#o+nm%>}cIN04kjuKngL6RCqUF zQowpfWjv+GfrY$7RX*4S)J}J)=3nWCmeou-BJ?3d4J2*WOW z-^iA|nfjB(?j79=fKg3oR}kElMn(}N!D+eO&n z%JXyl7m@*=W*LayLD4K>9}b96YSlL5X|f1`$x26R1Vv5v^HJiE$ zf4jLF^?|3HOXag|`U<@Dl=cz8+$X0ISGV3T!e14g1bhOtkTAf|6&{Nfcr*n#hiY`+ z>O4ygXcjO17uFcwl(MN+QSnC5$J)UU=Pn89xb}h1B(Gb-vmHylgiUO=A#R28fXvBL zn`^u^3ZGzoA`uyg)IX-O4c>PB9&hcn+0Erdvm$u@?gJ%6aq2bG0EvLlC@$tC(Y=X= zY=oR&5P^khB=_+4)kXy`nB-l$jx!v2yR@60Q|Lx@Ug)c4C17NEX z0I^n+1bhkj0K5h`l{G1oMFeabPO#Pp<0#&&#vc25j6T_v>OJfu(VFv&X;iE_{A}8~ zj&>I1J;Y51lhrn@NNNa)HMjs{h)ZoQ<*BaMMLKl)kHVJ9w?sYKqY+^$;$KvYpIrj%@)JJP$UX{bpJ z1#CcHnm<%l@sCqxj!|-ugUbbVy+{^DJ-}ry!`6OIB7y`E9C%#2NEHc_=s`*?7oJ7* zeQ3Bc(-X1f?mF@AC~x^X_qXyNI;=71W-J^%3+lwx;5rJzI$Wg3&y&@9pb1xWVq3)k z6z84udXSG!JF=%a^%SR&5?tnk|UloZA%040J`^ci)nr;*Zd zE+lls@xf?F8ZSC=ste2=6cXTtad>(-Qy>b!-xqR%^7~Rb+9Fm{YSGCc zk}WiL6d`G!WqG01a2iV>IEu{F9fr= z=d7}_aICoH^D(iD*Fn#*qyTo2B2rhV08UQyR!(mKayA(;}_Z zd7`PGCB0{$H9k1+pJRZoY^URj6PNwgc7T}T1xR>uCCUO-weT1F1oAT8Co8i(*V`yf zpFeEi>RwQCS~NDDV|={F_Nkv=i9mESzoj(Cu=OxU96g*Wj0g0E1WW=`9KoC<`f+qk zf*xTby|tZ6j1gt?{0kLL!Iuu7i^(sxJf#a#fjp<@Sng&?Qc-&c9^u(G1EqjdF@CLh zhornD^g_L*?6qScd~|2xAZLhSWIN`$(NO#j&-}#C z>O7A@;;E}Qgce2B>DA~#`Z>07y33n6glCs_^&|Ni)z9DT`KOo$E3R4T>YygNnQ;U+ z5qM15`-xu;ie5aMX7*nqe(^(w6HS#3tR${OQ)`N;U$rqxs!c4;i9-SZVUeNuCS8sD z!-jGx;@67exK1)j)JhZd(LT0}cl(3`7neJ8TD0T~+#KdCJu9CnWbcuTOz4phfb}W7 zFGfm}I#51{Gzx{9S-L(GTK07TX*k?0UN&w1!3!f=+fi`y$TPT z`%K?kg;uR*aeQOEH?Z~(-kENq+^5>0*Z#vI{+%vJycB&7XtZ(`xi~+JHR}_m-KI3h zG)GO{N)#!13kz#s13War7DMNB=KnQA2!ozZQ~+u^DF%=zvXBflrDU{!tr zd8dCr<~*Em095qdC-0=t)4N_=^p|@)Hh8*AG2@ve5ju^%Plj$?#sU!NU*u(KdrJM5iM!eQUo^IoFSpE8Vmif?q_OySh2U9sAmb#`%%CP*j72)r0}$ zJ~6t*=(lE2rBANM_UmFgBue96?v5#0D0HT@iIH)YXVa=iS)*E{(Y4-5ra9UrdJtEK zc^%E~jpJ@s^d^AB{~|bk6^Uy3!V(GR?FcxYtLADbpGUT| z(Yx+(Tu{xUDLNo(x(>l-hp|3sLOu(^;|inzY7$|Rgq(t&5gJ$cF(%)fdu^~NPvgqo z7P5`Te9GzqP_}`nD2FK~cx{236cjyz<7^HmJ}bPG(rnEXOzi0rG*XmEb8W|_Nw@&(a7KR2#iKl@tsli zGyd{?I*h=BP1bgL02!Tv^5vsmDvNIwN7%v09G!4Z`U%q3AST|x*G=lumpN+2@Ir56 zcd{D3r8azK)+&2qCi416_St)9OTy9JYOg^8%>kq!%0n!*lI*`|0=y{bnJ1{z3v^d( z!knc3X#;Vh`QL5y$-?&`5`#Y%ldHYsAO+V?U~i%PqD+1*umEXxtm{QwtpYwM!H@#a z&cnekj)>^VnM)}Lort)JyQ70)pSwc4WR*SM7JP71^b0?gNs{a-E zQ6y_I4-N9FFT9eyZ5{^uVe)h%NV<4E=hMo=!pQ4>G1m6R*ZaG+VR?q(|0oaP&PD$e1&QMxW16q|X_;lE!+SW;6%`43u zDdIzFkcuqxI{QnITBP_ymAVQ){Vp_bsVnwZgjNSM6xsCJZCu3)uJ-7uTvZ(`Yva(@ z5{UD?f;>qLA{P_W+P9S27bISGW#_cYk{N%JBF2o$!zV>@@2ds#%n!Wz>^3&>?7Vz; z^I6O55Y}KnCCXjY-=X#}$|%(znri2w9{Fra-Dk1O%16$rDZNw5%Q$Bx{Em~z%_h@+ z&$2me{SxGa7A|Fheb}Axjwz3{rT+X(2anYi*ukiN0iPYI*-=8jDdy5Ul(K~2Ceg9j z%{O;W+*%DMYGnKrUXqA!ON)&F(FJ!1=+qU`Y8)VJe!L!i-omHTr6Gf$C0XtQA?OrhjsF9|1ldUSzJ&;-kHs)JhQY2bnp8A5;aqEuub|1T6 zITu7kGXA+o#{5I$*y@UQ;e*vJpqO4Ioq9tP_bHSiN!Nl;Wgzy^JP2d+(83Xs4NJ08 zyPGe2kQu<5HOX3ik^xonLgm-99<5T!P>H@Ilb$yl0JqdxpIA_QAqIY`qbeLz!KnO< z`t%toUKTT$oILjhPoJ}Eik?>z3N8BaRD*u6hI&DL$SoM@SJCZJUF|W7G%LAVYvP1d4!p`5 zv&99rmFh-Snm_z|6>fcQIVDoG>!LDJ7)aMb38+B4Pe=1?$S6>a00M#aSnZ(t%Hh4n z1fTK<>r;81dJ~_ni2O4?y5|b$JBYYCP~!H%&`Yp-9RS%d^T>g}cc-AWFWam;Y!~ga zRU9qe2XE;Pz0jOU3(IN{ZHe)-mD~l)dFj^FA=+bv34Wo5h~h<_pqhS0ICQpa*Vnsf zkBeHt#irM2es0b-e%pkO(qS^dUnd3VSGH#C}_R-F9O{;f6ID=&_c>dDm$wf z+iVqnVYF*f??LBaNHFH1C^V^|a_ToGbLG!)8|cH*^Qkq9N9rY+>47XgiX3Sh4=NSS zzl`2ST@@D|Y3NkCc)3S9kTW}0pCW1hc$g!e9$h!01!M7vc+#?X6fNQ4 zV(bRgknBqzUidl*2M+fcwhTDN-0HkN59LUX=BHn61>j7`)1uTdDrixrn4GA>M+s@f zMm@nloP(CEzoGu_&}47T^YH#D=bnBgrRUEn(_RoTCDieC!hq365G?FFiISN7z1Xhj zq~UXDg&Pcldlh-6$(vsImOjAVzz=veU6VPWwv!S&@hLeS*p#AryUw)djo4RAi9WnH zQ_4q%YgovDd6`tbF%z}aNNoLBKCj=cI)2H$<<(|A(con|pFT(cAiio4P@4pD9FFpx z3`quh?$-;NK!HQ??#Pk`8j;#}k7tr>?-=NF<7KCBOp*rUrzN1e|RetK+&V_$~Vfzd%RW?Pq$K|6s!4)6u;+ zZj=Wk1|-9jMnYPq=$r&U>)9@yg$tyCN`q`YkBL8J?74*{xv}fA$`UE~+RRPL@=MlN zN;%~y_PNn<*0cVC(>UX3$f9#MhyCpuN|^ddloa zda?1<6VJsV8Z1#Z6$nAVt>Fz`Ep`SNEzo34`v_9zWrtCvBDq=gP}E^A-N)gT`oe(g z$V$u|v%a72Za2A!iFKot57cHrl;o*aO{6g+e+TOgkC8JT2{unr>WaSFJf^kgS6k`e zT{-D|$zzPJzPiQf86@3%g5&NLg`37o2Q`!gQdl<-ji^R-1jbL>NkHK_3#=ECG<_}f zyI_L;s)bY5q^Z~oqIQ9buijhyDxmm3;lVA;WY=2Vo%eMM$e# z=-t~#s!;U0=q3)BQYf^pRx$E+IeCh}m&z1vCPr>R*k(4;J1g2;-wMx%e|oqBYg|11 zsw2BvsI+?-nuKCS`GZ6O@(>>qG7NF&V|ARL1@W&l!SVNNf!2lXo9eMl>P$#?OL0Qp zKw{KhkIyZK-`tC@;A={Fcsy8;u0ll-{+o5lRhM!a5!p$(x<;XB=f2g^v12h&E@c|=t4HkGe#f($((N@tyH4_T9n$a{}5}hX~|)pT@eis z6dEw7vo5~4P#QV)9tnoN#*s_`&W8*C59+i?CfTT%I>YuQ|m2Y8j+Q z*j5_grYAePZk3dLaPS`et(c*f|NVFm%flQ6orQP;Js5VYn20)ww8d-01U_R6|2YVrwaYuQJbMl0kTMtTOW?(wM42(g$wg zt!B6H>qP?RN8*6lrPBlDNs%`Amz!OFf~f!3V}RTd+IfyI<7*qJ0tJHj7x3W~6Zebw z7Lezud5hQT3NEWyX~E8P7MqvekQ!)LUe?JWS*l9kb2=e+X<6?DE%h>p`cUQ)qYmYn zmY|K}&FjXo)LGa;^DIY0oSij$4;&>QtE~R~3BI{L{Mvy(;LKxf(3RgYrl7!SFA3+C z%4ET{&>Vqj@A_=M5W`xHC;AAt1QvI0W3v|sY18#f7ze+UxcG0g4iT)KEZj6deo)zs!(7N#>;hU&`{?un)Qkq+@`sSs}qo9m>)nlIim=O$~bNZsb) z^n04iO+P2#MK-mL_1TWzlXJpp{k!&%Piat*^ZlPJt{L}MHENg76 zCmqr+5B+%Pd`nWpS2PX9<-(aQx6ChCD}SZS@hAFYs~`f@Em4cBEm+7QVTicI{Z%sM z8@vwA3W=MXnc@HR@JCIn#GhjStt%ep8GfJO^TMqC?smz{p56Jr1m8rL6XFyaqNuMHz?; zwbzA;)bh{B>l6DGkTX#7iP@VLCgzEf6&J4DJwBCFrg{*`m2M_}N}gRS5;($K1Y8=@ zhE#^{3S)vvuntsgwBT59im%P&L4(%pyD_KKKZx5rGDT(Ue_XXQfQ^;b&!g&~USZ%& zr5&i81BOyni04JSdo^_v-w*PxkmlzAnK~!?6jz0hThhbnPc)P4JV*Z1y|!|?r?8e- zU+7u7duDqp|FD4)5fmHEO9D2YPWb&Tpn9_omH+4k zun_E0n*pzyQOofyqLyitYEss|1SmoIAO(7tr24+X#7C26{vc*u@AU*-QFxcoVU!of zrVqCK+ys#Zlr&RY{03kV>LA(Ch5C`EiGZ2ck0K56J8`ygEARxHt}3=L;&e#mZ>%sAgtWkps{yX} zl@5JsqWC#9D#Yt}`}I;(27Zk)Jl*l!|GSp%?ZeUNr4QCIpf5NQbms*aLUsMN7JZJY zU%r3{qdXy}d|lvs*$TeOCyL;`q4+lY*eOEY{Bbr~_-jA2=!-LS#pRC1k5A~$x$7@j z#dIORvF9Q9{6bl3K!Dp<>R0GY;p!sh7gT!gHPWICrzukOS2V+ftv<=^@r+0uac0_& zaPIWys7+jpM<05sB1~NsNIPVLR%c?AM~sA6tp4{KtJH4^MO&5^x*Q)@uy27?twNGc ze5+>kNTFDZ%~Pycvv}BdFLjGo&X!F5dA;#=pPn=BBFFAS@!huCx(1%dHTk-)yHF^Q z+LYY!6RZlL;VY`riG%pS;y50@w5cV9b-_;cgp?y9yR45AG6(ASx`SyesY>s`J+gBH z1lI)f)y;mtUUWVYS^eEJr$f`d<+}{F{uQWGq1bZ(Q&&*y>sj9U-gQ!u^P=;D`l@Z+ zcBS7K&QA+6)I+f&%SUrji$6iBr(r=e*psd5RDjS7Omz|)p}DwD!$`X*?cEhF z9&?K-6`?+?-vnQR)6~;?@Sc@h(u^u|vAe!anR3gn1MCyN~o*TS2((hx@K5v`XJZq}-FJphKcmIp~ zHJ`vVEyauT&P}k-jTe~a%WhvM!MX~inx&hb_qv|%sp;yk!7r$>^_+GwVKOd^MN-e!) z>nez$8K$9a#(+9@W~824b>$K>HSo09(2X7tN;+bXPIkIO8vN6}7u*8vXIo{nXu_lL z;GnTGb}7$9j341|yrS6zLwD>uDS~n~;Lk;Lw#TTqvZQy(?2_|jo;MBylP!Ju@j`#U z@2s;ybLEK44gt9PLysf756@P4T<(e!V+n&3~flBBy;wRsm0Mg7DZ^7^b1yN|-zstZcP-1ZWv#?^DXx~Wbdm4)p~ zZ$8M6t~oW4SH3we=j*I=BY4+9@r`ZAV5E4YnK$i^<@TVX<$W|3-o^v~TKw}XPDl$z z1@jzrq21;125IU=rb**C(Uzv{&@dT1i{3m8d*BK*E}k9b&ha~*iOiAW?qBV_Jsf28 z0F0^t+7u?OQiQ1d#Z;S4y?hsCa%zhQZ}}ot?B;8X%alBqq>CI^q}|`_5r^5BNwQSu zZxla6_jRao#Xrb)I;t zNVF#36%IF2S(TF%QvP%KhWW>l!z&sqV@b9~21+`@85cQRf%*kjjRyG`F7g z^v$E?#Z6*{G`5ttme;>GWYTH6}Cpv@g;1slw&tmy-))OB)6V1_68lzL?sa&I{t3Q+CxuuEBB+|xPIFk2 z4bnG?mkSL7CpU6SG_N)KTIl)Nk74^`y&70`Ay1*HNQe#wa^C1VfKaN-;i*Ji z8c|7S>_9*C2Albl?TlI(zw#H(ad#$$cGx_rRlmZi{} z;5TfpKWw}hXN$q^C5dK`OWv;(^!okGctWsiU`K9{9GRRgG)?4bAixf30}OCMpQ%YU zFA$ofA>~P?Ok%i*iG{dlq1%a_Ju$WhHqF9f(1$#Zat5U1?M0jMy&eeA&;SxEdtiEE zFp2< zC7Th?JQ+RVc`K8c9jmq66JNYrJDX>oSK;j%kNo2{AbO?TdhGX|bBs;it1u6%>mWtu z5Vopp6QH@MwU&pI!rB_cVh=3*(l_*KdBOrv<+m%&9El1n%sao~k3@LLswwXz2fYZ| zPpx^RRiH{U_g=0w-|$u`j0|iI_C42*=#RVC7To&_&i^LSzkMAAB>`>nkQl1{49Xpw zP^dN8dLDtXC1+O=E5n>67D~(K3$*ROODRV_{rgTk=@HpPvA05DS^TNH9q{OP?y!2{ zc<90e+zA9~&_c4)A=u$b(F*$>c!=|f1;Kc3dFV#6-BT8}6WRY`+@Lh%dve_RMu1R| z(Ya0&S*4FsQfF_lyL+BWihQVI<%UUloN`J)ll6h<$aOq-#Bvo-dlID{K$!E9oxPE&`G&YUHvUoA#~wE0ynRTlc0IOB05T!S# zi@I!Rs9@bApFdYSpdL<$jg%H*-NSXkrke<;E)*xAk(5Y{_bZ)7baego(TULHw|D8` z&)^K=9!{8z$0Wx-wkz)r|IS5u%t&N%MgP|G$NG|HO>=0_Utw{3UCeXi&MOlklUF)9 za*#~7n{Lx^|Eoe)naiqBC&o)-6DRVouWXo*33*y}A zawk3}_9>X3>InMI3cVuH_eS?FX^7+`NoF(!36RQORx3v zKi9LDzpB4|?f#p)N}L1L0n|3=bPoad3+Q5y?o>EI49Q32cVoOC0Enb&e9*W=ZpSR?3*?`t&TGiKU*B@32)OGP^ zdosorFW#y$h$sw4zoJ8dhh&jzNR}jaSCLJK+gRdI;*a&PiV$CXny`~&S!``W=5P1v zsw7hj@K)ud*TCYIs6gwHl!#IcB*ny{8kyDB3=QsZrC^nXcvx;>S!_H z2k??$W)_OSBs6m{g43pOX=Xum<(WCBL0QQA&yFUsAuwhBu#tU}D!9n-Y@2HQ>N9z)*hz*nVhKv7a?U4ZrdJRPciz44o7c~zjp|C--69K8 zI2lFJ%E*WH)SqOvPV+@W+s;laD$IOT%X+PJEjwVIQTe1^Tf<@Q{Y!Y7!0)wcBDE zjS!oa>unV};_Wi8$#xV8sv5Z(M*!!ML8h5qOc2%FKu5y(W%SplT2Ee#Quh!cWyqE^T~zj`$q}}C0?w5f{uCiWW=~-u&bIHU!MhnU&LI4 zvQfD@qghCWFY!g(Yd_WsrAT%8p%NkmygLdV3x-?u=e9S)OqHwo<*v=k@z2VHlWQo? zsE$BuX*8yZN@zUU`gKNS-yt=pTe(dV_{wa|3?wt*4HcAn-)@*64sZ+EWkKM;=e>eDrdfm{v|VqGp0j|R}8MR+G% zn0|$Pdn)u8loY%?2KQGs<7z?}O|)LU`MpXg_E!3&7QbqL0h4CwIP&GRoafttuc{{RPQSZi;NK&>J^MfZ@|2h3%p$^d z5Es&q5!)(>^Z}AU{=>rNj7a-c$OpVX$-j227N`0sarWL=hs&igwS_gs2JP4#8K+0@X z{WDbUzgOx1`+Kh-Z5V(%peHE#^Cfx|3DfD&{n7jBWNv8LlGBXy8c9Q67^5v}n_#&n z?>AbKk|TGmO@@=?#^iiY1Jh4Z-}{kG2vjmp_Q$f{huRKuxPEXZd#U}6JFA8ErmVe^ z1@VST`=RN{Z~DY^uk{$n@FAlBcfe>3W*_QAt0EQ3Ji|wGr(2TYue6k)Cy*xjPE=S| z#48aCp8c0h{x9@|6TSGJ>h92^54OLH@}yC2D#06)Dmaf?3`X@9B$GQ}`_YN*^FrMC zP+nkXdEdKI@+h@<%3$Tw&$`6*nMiw9ey7`{ zzEs{mp$gUmc4o+vL>4kG9>)#_A=Q3N?GJJO8-D1B$a_%aI4OMV$&PhHt)sZA``lKJ zcCTCy*Ym@mK}1h7#1%VD>ybG*+A8I{AP@)V^v+$RKCk^!@)HAWq^%GuDW%Oc!vR%7 z#^cj=5031Tm!_sQ?{Tc2Dh3K!Gb5`GmC!0deg2hdu`||wcy9p_)RAfN%s{`?;)I^@ zBxPi-O6 zzHxAN)aXi4x-w_(&SJ{KdJrYVx?)9FA%zsZgwszJuW3gp#;IxcX(sH?YfqkjTvPql zwK%r^J*+}%P(Ec(D)UzW_7ubu6<@^GDz%2!RO*!QK)@i61e;_zW*UXR*7K)x*@X0ZFGT9_{%3zH8_&k}-%O%n^Mfe|#TsBeyRQ zs@|9PoSHi>LXTGGOO+y;BAd~JFkcrO8m}l5rRLu>?T>+|;TeFj>0{Tj4X_jdL-~4# zcKdIhd$S}aUsIJ9p#Sbgpy5|+64n*+O5)fgD2%kzIT)CS2KZEb8;G4)!d@rd7&lfc!nZSoiZz!>`eL0k|w&kFo}P*YD?J9b*h zme`G03#rTTuVC`QiW;E)Re&dL(?hGya( zQjJ8oulrZCm$b^E|MO3HH*3!cY`6!BQE@{1d^hS<@Yd{i2*bE$DE6s4;mx(x?L&Iw z!S~+LXJ#`-_H`cLC$X^I&ESirqpNtp-p7*fMJWc=@rCp!yoYWsx<_M#ABTMTFQFNZ z6it@KaT#cxSIKXE*Bp@itj&$Sk6tiE%01$nvupreTudvonK~L9`!9n{->&QsSR4yJ z*mVe3Q&W=%Kcv8>=sE|X(Yz44U_8F7*pq%4%iWXN=X^D2|L7gLX!QsRunFSv%?LRH zG>%u#)BJ9^$P ztI$VDfC=HuN3n~V4V3b&)WC=^h|>yiu<(F82^ZeUh}7BXo5A8*+S9;I>FYsTZTlXw zA{8I!Up^-tR2#CETA1Z$p!9_0Nbq@vcG^(c@FJ;ro0gMvgtB|QoCe=GhD}T#ohN|) zDiLBcicLwho5mI;f}d~Xrxw^r2aKW7w_HAk(3Aj!o1x7JRQ%?*`V9W)6bP;pPp2$BJKH)%u2XG7muSKY6= zzqbazhYf6QVH&?>mp{!ZAL526+4+4hZ=&omzYTFR3cqO$@jSF3RNgeno*BH^snxFF z*?wK;>^gRePN8Q#b@tFbeQ{G}+;=i}$_w2SQzalgj zul<`1u&Q?O_I0fI7hIB^5F%vdkoHu%5oCAO$y*y0M zOaitKk6IwNBcuKx}ygANd}yZuO6X zj?ExJC}}8JjiJLj-c5Lzy@-})Vd)NEOsf=#>fY)Bb~H+iGJs#LVuOb#GkFk!hsJTR z(@1-=(awwQkd?jLGohah48Qx3TWvj(Zwp$aagXRO;6VoIwoVzZ)KV=LJk+Kv{|( z55-nFq)`$PejHx99!@$(^p*Z1k*m(EOmU*Wu*G$))T7^U$g{)tCPAkYb$Q@lWM5 zyx6RPKu`9kc)UBFym7y{F{V%6s##6h@3plKaQ$??n6?HIC)d`ph(kjET)0S-3L;9- z=Zd$4NQw_!%fj*>K!dB}t*RY;H-Yo=wAw9}`q747JwQDjqMEg80^u&8_O!G@{-0!# zFHah&DxWv@e6~KV-#i_xzV8V1L(SS^jzZ2fj7IcbtJPH_mr_QF@-)x0H%FfDqi@Dy}f4 zbPP{L7oet>b?gy#l7olhQKAP*=ZT=RYoXo$>^IC=#*3NV_4a;f z|E%wKuM89hb)7c&{qvW*7mj3IphGa}7>d`!YBYq$tQW7^Z!{Xh`KZ9*BqVt$rZ7cLb0WvnZM(7-S2zDPS(q2a7kUd z#_^%zvQRSH@S`-#AZAn*YyiIH4KNY)N!oQl!%ulQ(X50<^mG;qk1^$sT2)C$bzWst z`>0J7B-ceZk!s}{$+Eg4?>D0T343o|9=qak)XVXkYeZnYqk0lTQWmNJ>3w&Oc_|_) zWY+W%reS<$(!2Oo^Ro^riwLJ03+3xcg+*V)zq5-zQ_c}NM$NeK zCsjn6oD-Mphg59n`5 z_y5DfqENMnd*xu)flFM_BI~?7mv)P+T0syRwy67D6H+=2Q+{k=#+>rLKYs3=h_yk@ zRJ2G8#Pw4{oCl9|mkqnVGchE1el1Jhlil;n>eP!PGk1SE7JmbPA${Fgw@;C+4y6yO z0b)XYf!zZOQ-)drrH+wci?9Sj)kMj}rh#!4HR8?|QteXQ$)62b2iI!2?J}DE-BR9N zYhw?lHFZbJGD;xcYMe-Y>Ih8*X-~@O#KyN?88+I#ikP%8*4=)%;9_)GyCK(3iJ^H_Rd+&d?+wgr@ ztF5Y4)E;R|siLa3B3*V#(N>93RIL)Ts7PvWsZnaCYE_L??HOBflhzhHF={7hq!PKG z_x<@k>-+ox&oA=I4GU9f+OV)Mx=*v0 zqR)PBv1V%ab!Kj`J=pI!40yq4J@oGHYJ+pc4 zEne=nc8$SiMymB`$*94^{YIiRV83!flyG-dZ(kL2e_r#bLnn_m0ZsV0z5F1+#}meT zv1KJ)uLY&6AbXZsP&6fN6nhK7L)SQImt#l)s#7wq9~&*MN_v%s&`b7`Md82B1 zd{=oj(f6PSwEvsW`=cUcyUVV}qe$6f*m~${}p` zaTCNkT{F9@8}{>$VgL{};rDy0ijpCUb6iri**Uyc9K z%?RqPIR8Cw;qtvT^%o--!cx7Jdf`#D2Z%{Y{nPfkGKNLa<;L&I5(O=b=y9@p_z5Ch9q;UShwE@Vj`=Y^px8LltZM4L@WiL!b zD)?QzIqPKj@<|~4<<*t&er9#?NSftATouse8zRK$w@%swx)JKstg4}9Br_ar!qP*T ztZ%sOHl?F&;_2lqhn^W$>cc-Ge+%kchnD#Ey)ydVJP$j;ukbtpJZRD#o#8s872UIE{BRpo z+Qiewu&Vq^O>w(?fwdQweiW(?n4vPY{71a+SgwJAt%|px)Yi9lK%M5AuGVQ~q$cz$Dn~5yQu~W%Y&xj_kw)8Z~Tgh&Cu_Y>JmD@ysB% z#$#k(ngAQzVaq-kUV?D}6hgZPKQp1weu=8D-%vd+ zybI>qXzsolgRzYbh-GvWq;h(=)O9R``e$6C45HcF1J9kDquwKLQTEB1vG}1rFbkZu zD9D$>(j?rM68OcYDoy>|^_?PzO4}P9M(1R!SLYQTw=sb&j&9eB2+H?sMfD@$>m<6t ztEWZy=ci*te2${IfWce-2Wns-%@X)j?|T6PN)E`9EvgF{jl}co@B_rrFPlHHv3Nj1 zI+^y!!53URGkLUj+re?4UT?QD)v$>qJq5pGuuQ2yV#F2GEXNsFsZx^ljLnjeKZs+&9e(YZ*o%SyVy2LY`JP!FG@e$hkvtqktlxN9cDA$WEmv|Y~ReVNu zhK*iC!peY5m2g#Dn;w5ZQGqJ`tk%y~eVTEQqe4vcVmw`>(4MXm64XBLqVQ2=k7{tU zm@#AUsji8`lfp+692C;}hq33n0CoY=KyP_|v&}m#T3V z*-t57SGR-yR1Flz)B!@uZ8a)Eoog z)K{ZsY1y@3QhY#wL}cdGn)~F2ap&Ms1*ZFSs#S0N#@sfXuzO-a?2bJRv{H zmt~AHW=2&W`1!DHP_Q#^BdlMuUgAM38_kNf!*`fhxz57@vQ&69{4KdOk$z=qkDq}P z7a*&3dj>MN7e%STIsJsadu^)E8oDeKJJo~WIa?|&$%Zj`H%|c$e+ZJ-8L=WovGj0c z-d&;gfGe}o5g!f(`*qqEli*w=zYb8nWlXCK@ngFTT%W{WNtnMs0-@>#a0u0PettgB z?e%z0WXqr`^1;i$*DDrpFQw%o4B(GXim*#S70arE5e*8lpoWpSELupffL4vwq}^AWu zND-0QNN1F1c}2eH45j3N&%4;EV>xU9hys2OUp6$$pIHe!lsv@!Ro)}43(f9r(XrNl z*(hSvQX(kPz6R612%_Rcj4Pdbgzwnd*jXvn)eRN@nQzECmHxNgCgFTa;Jdkf*{z9z zGx_V6eL6PcnSd_&f3q(?`@cQ65bJVETvY~dg9=bP7ga<^t^o5>@o!9O)%9WgZl9p=e1_Mm=zWB$RlNQPY3xGUDe-7Da-`@8J zJNZt%ausEIV{oPJ!_#-p>hnM2r?`_l9j*vBR=g5nGkvpK7KbtBVhIX?#Mc&p!pf}L z_}A*Tesx<-m0d9Wor^avwQa6^Gk#?IKN!0yGc(`8i<5Ey?C$c`k)=9;kGKJ3;=&gF z3VC)Jf(l?&MzaKs8Im~**{>2jJkB*9NW5!A4LfHqfYL&4kkc|)#5}T-1JWGb^)f_M z2g!Uc*T0N*6Gz90P?_VZMFiJ1C%4pJbSl-%wpz3L`t!s9D-Wz!%PI^u0&8Pp2-?;B7 zzvnv8un*m6YSPtTW71$zKioa%vq+a60u=5ZU5p##z<4-s@qk?RRK)6j-N$yD2RG$s zhqePfo0G`6p?tuGPs`+jZ!zom;}5~UqZZ~Ut;!HP!)qn&2M-6mBMVp!WhUN~ir2hp zONIV^Tg;d5<#F_T%3n_-3?j$_+cl~YUYM_#cW zIbqe#%S%Tqe#yC$aqrnlhKEGD>(eW;z5|9vrmUs@D-A|QTM($d@tm2vn|ht(9~0%t zs>S?>-=BO>{rOzsvZPPv|D=#bQjpLf$Ib60DOvpw<;YGsM-GVt$V4<=0HcErQw73u zgL-zvv1GYP!~cBiU1(B65-i&rCmw&tdN6;*Fvo{j3>BnPx{?cIq)D9JIF|UO@c#$h zNaHDZ2f1BRSvn6w9jKi6QC2s&P9#r;=>gy#?BU$m7Y3I_c>&$(6nfR3i7{_2-Lqra z3B*z_Al=*ZZOhi=%eG9*r8d zzFyQxmy5BZ(L|41igcfhbZ-KHUPy730 zFC(_?lt+u3#}q_NTH@DaGlGQ~ggY6kIGB`tCRc;EP7OeSy%1#4JDJaSl6wfA3F$#z z(z)!0yx?dH>&+IM%G8PQ3#c6Om476lBRRHn|6^4FTESV?3%y4?byw#u)yJ8@b#4=w z4*>64VOc6=dHB_l?ddV|Uw`tb^m@i5Ta` znYJ~t#QV&}rS_zZm@1)6-vyjpXO5v}7EvKKB(=ChjOeu0Wq?rR33g26NWC&&>r%}C0E$P?cU$|nIzgL(U&lp}LDbC-{ZkMyILs6P8sR&-5(=X3)x-OLp$RX<91FTVEP^6wf9}HiZVI|$)yaA2HY0n{ZYy7*T`x~b>IYc zuI{8~n_b49iZAQQXNImma4YTEe9Gr^WjA;AN9Tyg=3*SBT_Q~ns8_>mHWY_^me5qY zW-(mP$JhH)4cRt!98|>13vI%evg9QZEZhy|vdd6F5eELg8%6?C0w>yq;4&=}4AtKt zZuCiw3O^3w(Abn;|_ON_ueMg9dTJDd!EKg(w!+ALx;%SP+VLL4Z8~X>aEgaSzbc zvodrTUC4`$wrZW(sySa!(eKgJ5`^4Fby2-vClmjx6Dh+gF=zngW7e4cnufT`FuG2m z=Z>~CB?VX}B8Wzu&qtFsU&#azWwnh!ckYq?>}3+D#dSd)h` z2u--@Nj7#VHBd+O)`BZTMOY5+(hbalDj(|<5F9DL+{j9=! zxPv1fP`swV9<)_|cN9)&24(F5B~W+*CR1Df6gW`H27b zX~}i}A8!(!L+?=9sW_4Z0o{+BM(R0UnrYQQ#iwp&G^=DaG?}PVAM~=-&D7Ft zF!{f3oh&n7c2UPDoirm~?ah>SF4lp!xj+5RKTUFprmGQ<2}mchNgm)Pj>T(wlco|@ zp&^qVv=7`uJ3Htb!=wR%-gfhHGa|)vvlT6CeN$;e4wExs=jL#>KRnAS)L(f5P8p z-W{#|fdP$-9`aFr&J|L&u0PXFvZUP|f~B>bI5D-Z0r}6^2pu35kxYqNlIJ3TW8q?M zU)`pxkv)`bt3*wX$hT4Bm;lPlx~7!*)`aUH|0QAjoc%y(gJZt_}YD(JP&B zMm9M@+lNXd*q?>pBCmI$W)BEmjXXk&Ae5WIP z;t#rNuW3*PYUy9o_X_3q zzsIp!DHPXRG!5|pr|REazy$}J{03W%iEO{Zl@MqSzYgNt6$eK}?IaQI-^4t4Ak4-Q zmUX0lj#jX8VDG`005w25(UA*mz|k18wATd^a|MaDOAHlRQDdOBV}}jnf0FLK_Lq^K zQqGcU%GR)%d43IjUFUgju$C7lrFL%y-7BoW{ggCIl%MKV5HdW6B^T6%Kw|1Efze)< zU_FQYpT_vJ*&Xf(sG*3)d=TOlET<09$(YJ#rWTUMh-#fU{Gh_A-{4C+0`%*oRZ3bH~zaroRNdB-tLzm4nZ=MHf%vVABb!k;H7-< z=~XJPS~6hBBm0!hSCv-!T#XW1)l~A^QsIm7Rik+6C%*;@R19wn( zDg$pPM$U|Rt&HNNE1jT~`KFW?Ikx!UylD`>Oh(SGRHtqReU2kGhTq|E_wbf4vRH>E z$~=C4q6oapxY`8MlRPc)NF|4# zlmI-J2<#c`E0iewIay%mELm5+lFr6i>>^%(EQ)NOy!mY4+2k1%gvV%9=*P5%On#75 zyDRVxodA*#5Yg(Y@a`>VOaK{1V2Ng2?=Bt&_kd1m&*y!h#ChX$huQJYt@UWlArGni zncx!1Im14tetZPQ`lJN=8l7k-*m6?rDM#l8;_jt#3|jzLkWH$I(MLtCZhWh?E@`Y0 z8Hzj4_?dsJ{;1w(ZIH)lIpKJ3CyvHOEjK6Ihk;!oGY_6Lkfi&tOg1xvzn~Qvc>RRK z=|_|6kI3x&KmXZ#$+KjnGx*Kax$a%og=_p>|l0(j{YxQ?as6q&`6>=LFW zuw)kc86OnBG^8C#-qJjCt(d0Wsu;r!xoMOksiWyX39Pu64x<_pu+un#LX_aGE?|hzHQDkzMBUURn#orQ_ve1 z?KQqIJ2h9cD)-`=bSIHx0iU`%nJT8?%Kz zSm{}+BB(JT_TUg0Cu72nxrx-@i9gqEHf07*&gREjaAFLi6-#_lW|9H^Y&;U0fA9WAIC?eFqRd8V{gn*hs^<_Wd$Ac3+jdeMzi#N>| zf4kk*mEF`Fu5M60=$3qaEoFaGFzmgS8UaV=g)M_RaWj@?Ck3tY4LS>aRY6>b_6dyZKLZ5yJDl~% zsH7eFEV@gXLXy{!ho$tRY6G;n>gHs=%gm?P*;$2wmNiXqeP%q^1r}>B40hs>Ufx-M zr$QxC*HY_ZVmvSWNa13orpKu~NyUS1&ko->-D4XW4NfOYq*qfRq$wg0dQCDmrmlY4 z(2=i8pg4S=a1hSx{pZ;M7r);n}OTR|h9Fp3)0 zhb%zRn?SmvqJ{U8>rj{Mg3MG}AZ+maE%9UKV^6nNDqw@2s4j%wmNhOf4!((g+|Ky<3)4TN zTOMl=ubb(6Q{r+ek`CwOj6l6T>9l3>Jeme$r&XdS~9;Hpo%rSk<*?X^&^Mey>cuq;HP{n9>w$ODz zmWMPcqzicopvL52qwKlh`pXmOrT&EVn&g9S)1g952Q%Yiz$g-V&JQ>n@?90h@d+G!Jsw*28+OogoQ~MH&o$f@^Ax3Ca;3 zA)M(~)!Ho-(4z4#GajKJ=D+Bk1bfb)MM?_UG=+>_&g=+oy+`&HeA!4%b11W&bI;CY zaQuX^KSNGuOEoUOx>&WBdu5*YVD9faDgYC{cv=~QUtg-q`SRP2uML){2&MWh{}w>I zW(@c%aM;v!%Nc4e{gZj7W-cOSta_;y#T#^CiqM`yhV(CpHIf1G0F>-d8+ym^Vq0%j zsSnVHGu#Bs>YnF4?-8D_eDKfa!poQ3?>Lx3aZ6a93@Vv+Cupjdp+a>cgRM{T!V}a zJIwQia0RJR9E@RSs8z=skKHn+_Vr7*)C9gd#6weuRojF`%Q>n{7nYd!oNbFo5C<+B z9iUOPO=bk$32>MH{mf2^RE6=VbQP|U^(o$*yj@Y>Fgg4|;9@G@gj)F(o~t^m8n%rp zn5FfWUyx_NSQjFM_?p~bnBkt{V5>7;j;GOKNq{Z$~eu6fv16;F! z)>`(jEJk{sZ*h28{OHPO!a}I0osR2 zAaqnkV!0CZkTIG(3FE4Gd9#sdj>R@xhWsdr*D}INbvce$W6}1kh+zQuYQF{;!U66X z7G#CEJq0Z=>3gVO&)dcpH6E#U#Fg3S+Tmv{FZY)~#C}-D!VBL3xwJ!s{7jc3xG1?j zs}$Pl;Nr1lJniXJm*g~Bc_s4vDE|CUp*}!icVpgQ#1ONNp$NcG3{EO@C`&hJCa^O%L3NklBqMoN+nUmF>W|6`2&UV-#5SS9~cU2XN^jo?F@{v&S=cyH)u>L^##qNv;< zm<9O8uxNSu0QFo+*z!D&2ZpL&Ya2ur(7P&|(bzf;%=@b|xK#D_ccq#II2HHVxxO(} ze%sg0H|+4zB&nt}0!u^yDn5HX+R-GWZ$Z3y!g-XgQItMKU;I||v!ZKRBFTW}oV(Sm zhZAvS{<8&ejF-7CQkf}GDr^bI|C^s7IMu3BJmB!fjelP`!R3zq!rWiUy532x#&uoh zVK2p0h{};VrSFc@XuIS|Y!Rs#(;aHjWhYG5X&`2h^lGGI{=kH`P_K^wPg-`xP2PDU z&(HOdAJh={U)M*$8wF%re88Rx^P=m??X^ss|1W0u|CO-%e?OK7-*~`wr@q%FwO>%s z+%!nLky!6O;jlHW>GfIA>uGhfx3-Xc@r$6#Cre<_XDW;G!l`C!J~qEl`pRPf{L@_W zdG%!cFoaii{N*IY&EBJp?`U(fO%1FqUuR-Zi99KWRe1hkeL)Q&Ij-RC@rAc)C*jXj zjcHwc(@|q84?7kG6gjn#`W1v^wxEJoNA>Zmr}CMY0;T^>t(R`|Cj>8p;}};yvZ`>v z2xo^!3h!_&ibQ^^3ws!Y%6)Jq^p2;rv?L_ zNXyyXR7~>+{M2v!EH;g_wt1f@qvPxxY*kg7@E%{!;p8mS{4rCbSZYTYF7Qq z4uv=S?r((@=98<+bmEq61E19gIW>+QZY6xQBO+73>qVBO4V|T=^$#kg${qk`VGj(y zPLz(~?addPfD1Xl46s_N7f|>bcdh=x)pB_JR&Id}^MIJ_p%bAt4%CemgeaF>m-x9m zgA--?^m2uJoH!@mhrI^WgJD>nRx6UxRG+4PAC8x*WYX?w0VS=4m5QAG7InqrM>>LL z+>{Alz~|=BPplueA!PF?&KH%E!Ez{hE=@A9ue&*$lgd8~mONJg|&` zRtSF9_@r140Ee?nQCbg{x?wH^>dnKDa21zZTk>vxr@I;>>ESagh{%<-w!$9p8LX?q z#jSinvR?UDxQ^ydBqbAX8CyAP)sTJB)955yKSMo3U$HfrCH-Hfpwm9Ra2i1y(vMI$ zLBXYoU@?!W_A3)`KH`r*<-=YFLm{&Xt`G6BT!xKVlV;Ffzk_78%#des(dGy71H zSPm_gvUz=g;otFVItW9st1K~is#aWk9Q1R|_d>dSfYY11CbQMFpE}l5cIk0)P-|Ct zCHh%q)-u(x-8QByv1_82_UP5sBAsdtW6n$|q2fPre&gwmm1pIJ4uXQsdmN=T?eT_0 z*OlknSPsobXS)DCH^1Tj>8JLM&jRf0@;=P@&mQ`9{`Bw;vKs_*57umYc&v_WwVeGR z$bW{os{TXU8&UVy3Mk@$J4ywOm!*6L3Chzo0&i1ki0FW!grX!laCPnFho^-KFDFDO zcAoK_Va9F*_QGP{x^&`V02pDU)FHI&H$!NQKO`rFnPpZo%uYo2qDb7;*dN0;KzUt2 z918vYWGjRuVB1b+0oXr041f{E8&GcY2C@^fchN>CMXYv^@dyNKQeBV=w>c%;zyGlH z20d#4Q%TgMx<79XkNTc#>XiB9`9FGW>d7}Bmp#~2>}*a}enkt6N?B-Kn3?(Ru#VLjFOTox#%gmfPDG{zv?r3Si;IcKjh@*(HsZsKZF z-)$4epnjketj0zlc&Q46Df;!Mx)zx5;J1x-v2V?7jPZj!uk#T~9zIYZmYKz(6_+-j z1%7G)V5A!p@2TbIM3bXGp@#4sca}(1`{UW5Hz2o%F6rW8VUid3u0Bd%Fxd3?(Mu#9 zl4n$2d$EL*1Yb)zzw3L}<2+t-DRTi`C1lKC`pn2&oWXuj<1zc>ra`)O;t8{5l z=nIcZu!;C1^sTmE0S|5WRXPRVuLof!!3k(KWiZN)le8KWo3TG-5FPZVeEx=unRux? z#W!{vLr-1l5jI%6DkHG|@Ne}8@CPhQt1$`Mf#yMI1_8z}>{WDU&N!l3$&APypR0Jh zezBW*1^2n;27I`l!og`@Q^;lUU8SH_J-1W`pNpN4s~8q@u3N`r1s%rjV(uV*cqmy(cT(#PWh18#RTS*rZI*xWu^T@>OA61)>bi8Ua4#vpatSAj z7+d71`)guj(PP0HY9Kn=YHTz=M-1hsu2n6GVhw`CYxx<`3RYU z)H8(Z&plZ-o1_TN1y0^D^D33wRB&w~Of*?E@N;n(UF;?l7$x)a#o!R-!c^hLxL=qE zciy$KKe1z}rWQU3qG7+jr~MdOu+@Uh+l3I0Fe9-th3}l7s6Tma=BZdi0F7>0HP^0k(Xz*ul58s+oXD0hYYlGolxXQl9 zZ5uy&d@>v8qp825-DsQW8Sh_T3b#v#e~<-AHhm8a@0B&tFGiMLFa7QE$Gu-=$j#xa zqq`AbQ@K7T@kaWKvY~9?I-3vo>@^%T+T`~Ajx1uxQOjsXZq`7Z71hq({S|NhT&_NK zWO&8I+xgz*vnmF2%T@z|ApbvWSgk4`n9Lj~YcTHT?QX34$ljacP0t&@0YML&on(4m6Le$o%m*I`C?L zVt@O(Q#5z=EN%U#;$^PX$xoZYy-4{2kn>O1SJUD%`Co56D|?qLpxEQcC+Efabk%$?PbD$6ASQg(k}~>KyXezI z!TDiGhS#mkO`s06?>SzNkyB9+bVW{UZvWsILI2C7CCa0Utjc})h2sckv1KbAcjyX# z@Y(+N)Wg`louYTmJVFm6!np)5qyFJ!Q9)eXYVfOlhkpP5TyH;yrBF%ktR+fi)@EQ~HaK*%5ae*fH@(gxas}Nc zqd%XXHm`fwhR#@DZ`d9tnD{Lp_3Viwa0`;-uiD3Ip&1i&zCc9(aAxny7@s7tdV71T~VlZbAsV@!@0 zvwCX`27l`@v{8NR7Z!0WS{OE>F3NeTUqUNX$zHo_uu3@DGkv7693nI9Zq_xCoBLHW z;3QvaEX{M_cIEZmlUXZ#e`e}%@yEw&Y0138r14d?V5_b?{ZKS_TFZ23GA%NH~XnYymBFA5A0uAQ?05Z&}v8ldxn=IiJK z{1G^o(dEV{Jroh_0_GteK$3`xr_OIWGV5!~$pj#A+^+jyPcd8Q1_cA;hV(9sSjM>a zFH^~q%tsiQ$ie%1t%3cZ%MOlni-Xof~>Z4UFM< zt+A2c-*5lzXy@z~#;<%GY6~fo5A+T1N3|a`Kp=Yh5ZWtb2kL&r&sld2?MrK7XYb5BMZqALq9wuON+>mURSO z?QW3P!&+4egT{and}Krq{!I=?Rdu<-am(z-1Wb;&V^q8{!p^ua##@rzv1N{bq2iWO z*zPUu6GZ;_@%7WW3vLDzPa<*!)Vl&!h|;1?1)G|d>xDCzrlv|s(*V0R#>4Uo*T8?WI=(J41G9`e z5?3!859-rVOn68Zh-S!AzfbkxIg=@snyx$NqCUBu6PzTReat$#mKG-|^f7lm?3nMr z7dHDpU%g=`jK(*Q7(0NU|5^E`5s=ItEu)gJknI|lJzVK6o{oabJLbc3D{-DB7J?c3 zto2zEBA;2_FtLRFCrfyN<7JH7bL+6WH(T~_xOBW#=nh`ub=MK$9{_B@&*m3#Fi2C9_N)6sSywk}} zV%^RbyP^J}9&-Kc1ogv-w^9IvdW|LazfcS2c4=3vn3fL^o)pcHvkmOARzO`FTa7V~ z^WQNaFnKpx`{jbE&Wx9up`7e$|K$;%778Vvi4LE@E`8LIA*=Nv6Tz2CPYURMOCnsc z&8=^fvkK@sp|=&Cc&s+Oxr0w?KF>wxwk1X=4qg50^)7c^+yUSSQLpLu{%ybPx}Z(< zpgj)SQMpFyB<;`_R5dMz-9$E(Y;YkBJE(``C~<`D#8`ms5jYm1T1J0f19;ibMWhinXx-n0%{V{ zRg3ujPr13Huf~tC%-;Me^dr2QoHQG znDES-?r%B$$TZxyBdpywDgF-T_UX3#>J9>Y838N{6?ZmsN1k?Xpo}gtS|uM9(+xVK zRsFj;udS`t?A(ONlV6|PUU`d4KUes5A?h;oFrxSWv;XK#)G0txF-UbFi1FSX$GQNF z{$+H#JXKuq6UimHmACz@tD~EwFrq!PysQH6pNjMGxe`6R%h9dJ{P#sz4SAiSNo}D$ zV2mKyb@-?U{m04-Wm0~VOC=CQpvczkhRIQGOc+YkM&+#;2+xmDuh{uu?p9SZ;cT$GTfo+@yU@v^v&Et!ydxeGn!E=>dzt)&=&Ik}7fA z6Xd?JWxmNw-_b47rm@WTNl$i519{N)v0mxr(^m4Ue;?`JO7`ibWdmQX+LHvZ111)G zig68YN!wt`-DZFLVnHq;?sT2fzy7aMxM%q<2N}PwMx@PF+10t7hbM={jx;^ z=J;$x`*IWWMaz`(UEw)vbV6bE}cvDq)k^e%_K;Ofx5wF}E~}x}7&nRn zTLPk8Q}c+nU?A*BpeMU8j;Qvt_!~`qw9ez|&!jv0;g4y1SA~M_N@yj^9`O$7{LA#Q z@PB^(m*|7%rARlP6t%i7H)(~C!Mzkx(AE9C{hU~dL-hZwyiDSLy2#T&VfR$kG;w*aDDiWtFG zqoQ@r!9_@F@2dPUT-2D}eD;RsY=uj>GY11w#j^H))v8%DR0I^S;d_L3I`$tY6%oO2 ze%%Ensi8^6Z5DlR|8xF=8<44FwH;=Hwza3nG744oK$oklJWQRwju=-&^V|;%=84Y<-@~F%iF=3@&_-NbjY@Y5tnODjU}QBmMngMI$y*&cjZQ} zPs_J|nYPvsF}OSrisCrkmgWec+qH}Fa1-$Yhh-QuZW+nydQ+@;v`(YWa8Ix1HOIHO zm&psegtPK@_UHQa3{Kd9%SGxFu=S1UEI^}8R)bJej~jX*qIBJFBo?QF!_1Ae64t8d=#$^f^Z-|UpS31M<{xRISwbc>}$CRf1A8|e;7zylCq z&QO?iDTAR=8?(F! z4Da`n8W=p(hNV39=k7Y{WUXiq)TQ!-o{yW@m!o^!%=&LmC&)tWUL?FzTc;$)GyQ!) z`5$+B7p|xbakfb(Kw(K=}VM68_&FpS>!5eWcppUFLjHxo~+a{H{-3b#CGZN&mfGw{i2-(_zUF zq=SWwZ+Yzs=F;fI%z)JKcHUwtui=a~2Gm)S(YBDM4H<^bpv=IW2vxc-Rqe}%F;7e# zg^Y*$haf4>A zfWwFi=crqdCQa40<-CU`KEbb5abKo`EhT8G%dHI@u=Rgxc0?c_;{%;mb^dz1-tsto zM@jR>u2a%KcRMB7IlVP~Lp3nD%X3l+GRYFsJ$j(0-Oph16-{|YjYetZ)~2nqE0k~L zw%zlQR|%j0!rss;@}q2k3`v)>qB_EungR<;bN`ji2~#h6mC@7eBXgT_k9o8r11K4| zpR{_wex_N>(Hx-3Wc;G2hYw^o~&57`J@T{t%Q zBa~hP*%ZxR==RxtsQiJ4W*U215zpPvCKZ_4;2i1;-*a5ny9M7H!qs;oxj%k>7=O9^ zh+DutEWd-Jux#6KKn0hzA{6bMg%g5+EN`a!;+sNmOiM%ZgW$SvpKj`vHbgxse(NsN zCF45?ZF{EjecMjy7A7wGY6(}`Dd;I9f1BWS(>f!SuZCY!QrFgsK37x@F6_uC^n&@^ zm8cR+;vZ#Dfc7SrC$JXfQ0i)wfbHRwZfw5uF}j z|2;FQRfr_JwATw^56bgAR7gTxu!Qhc)sAFGW8Ro|ehyktbzO}+J6r*7xnbPpeTx+{ zKCG~88SW3`bCT(S6ICW*M^b9pgja zymOAk#r@07l8ka&DKgCEsGW zr@xWeua}s~a5|}yADaH$Hcn*sU)=>58t54ca)toZ4vIlQ*JVs#rOSwaY<9%ExBJb< zIc7Q<-~&@Z7lPgHlM|;F0f-B-j1r{lv`_|W5O}U z+(v%6ZKZO~&(d?#3!h3IWy<1bul*&GS)ik}xETwI`S>5`;`fbb3)0Yp@}bCcbI|hv zl*P+Q!uQ`fMVY^44;aDjc+zy?XQ)vmBVg?dT;%$=%B_R;4;Sv0T5odO=z2G(UXJw0 zIWNHxC2=RVFZlA*LUzOUENRbuaInFsJn9PR?06(k z&!J8q*CoUdmbLY1G17UC>SUTQeaSKo)|l;$1x%aJ2#;HT#)j*pgye|i*I&h=Y{m9$ z;l)eWFh0fzmW2)r^(hZ3?R=|OSY~%R)y|O8 zxOOA>h8Agzm zNRtlINrE6fp@d+{zH6Ux?mhqea3AgiW55`cWUDTPMojBd|=N_@)gogm$oFbJfO0mxOMMQm(N~ z*%DW-KnD{;`JWkYFQIl4vEYFHP3K>ecHrm^171dU<%GZJulW5I*wp2zn9$a}^f<-P zgzIk{WIwxz+NX^iHLw&L+vnsbecP0Ju&@eALafYJ2KA+5OY9XH_Kr898BdrsG?dg0!h= zQJBU}+E8=-w_p3$KdSs0DYmWh>vHnr?|yc^mE++UU$XAX=ROWxs3p8UNGst|`Dfh+ zBnY5{Y%?Lfy(9%^zA+oTfieNE(B9Q2n0#}C)_*~EAxtXM^a+l3nGbL_ z*IoucfJ!c1{OS4Qe~KvqTt4W{~|8j!%pH3rdf^{4c1Y{|T^BE7whhNCW=x zt$+~osS1!j{g*d&ryOV&U5ee0jEMB1hyU*{(e{kSP=kL#CPx1UMA!KE!o8qEzxTmU zb|AK7k+rXq2 zMy;mgkaye71c0!X!_A_H+UE+D}Fb+2?0QFU{;Akas`YR5b{@@{TD+W+N6P4e@_OrN6EN%^tyklW$lnXMm zu&P43Fr+6esJsm-if#v%E?&Te*>~7aQUW60Eri za@u#l^jrliXzb`kjsV^mKrG;$#;`9<%)!<(^w+8X<}#nU`$i77TXzk1Z9BHH z%B42{0-?TVS%f&AIJcXN8#o-gkam;nT(D%s8t~uGfPKV>pqKNOPC#AFg67(BVmjt7 zc$`S*m%@T?GlniDro4&oldj(x?{)Fxdg+rT`+VbixFb~&xB>MUZ<%@s;vEuuXR4^3 z=i-H_``JTJ>n0pyQUMbXkCy8CI_So*lmlQv4ML^gIMt`CYozxPf?n5mkq+{`mK(0+ zDoBEC?bGggqWPG5$PlUzfwih~q*Y?m)ofIGwdtiQ?E_pYyt#FR{84cWd*zkhKh9~x z3sy&zEMBF#W|LbdgF5}r##n<^UIuj#p<$^V5z4zy2TrZf8-}Gv){+;(D^pf6%`!3P zw>7+5Am*j0hsbA)uP8Yn6RnhFzI8;?HFNg^5RJjW-C6b?iyvc#_yJbopt#Px{*+PG zaZ|s%MR*ElZ=yqG=g*8 zJ%xzwX_}=D5GB8R`hF#CcHSTMCn%cUH@n4Ll-Gu;UE2EcHm}}#Xkp8w)^7E;|6dl8 z0AV;qPlGOt>DF3{09Xn!PcBTrU1&x2m*;!|JX88rM3ahjn(j3b1@y@d@tXI zf@d6Q5A7UDvIFtK$5Qia-M3eYL*6Z)(L3@WsA8y!u^&EDvpRB5PotevXT^-j(}_17 z#M-UzBQ8DJa1Jl1zgK5rA9luB~yf$;{)l26qo%0;52ls@KOsHf>Cx6UY2 zl)eT(gQF1wE6wiee0xr^%}Wr}1WFf(-V($s$4<3E>Lw0`T$cyYXWq=5Gi#r`IDN4q zWx%VRBS!S%lLVL_K4J)jO5)54&U}n)yKbEeMt4HWLt$4dcnZ`~zvs*x5FyhAC?V<% zCf5Ui@cb{xfxHumH=nVj966BKO7#*6rgk%>a9nj+Uc%x!vi3vQh_;=Wd~ry9wA`-q zT!WL_QTeZN$|m&#?G6z1aMW2EhL_ll^J+PT%_#Cbf4MHpLRjeOHV;tLg859e3T9tF zWm8tvUusYVp-MQ}2e~iH&-lOAUW2ZYu_|drJcEE6=MV~TlEjfLV6!(UKGT7H<+$eq z6q3kPhA}BFKHm*BPI$klk*nb)o%+OKy!E-437jj$qEY6|jZvf?qY@1VXez~>YOPlo zuaSnI5C-=%#W4Jc^v5;5RU}H93|MY1$dHhjShT`(fz<@AJaA20lNE$&WcON*@yb<` z)bF)oU4vrp2EWtu3VgzG@Ru2QtO~?Fu0{hvPwF&-KZ_U1=$9=}Kosqr-zqX%R;IL* zzHc#w8u|ijHeuGW8%8k^^|!vU^d^x!4`3Z=K@kp3AF)Y|r7>$2i;+@xU(G-6A z5$BcD`U6)|7XGk|x3t-L{H~mqzzwe(H0a;sJnzbKPI^uT+^Ja}jdKSs-o6DR3kWy! z9Ogr+k!3%Vifx-rfv}*vl{^-~4 z<%fa!@8P!J(kyIH{TMD+@>aI~bZuDi50!FWw;`!=N?EC&f=!K~`}V>-Ai?hVf#nlu z7C!?n%QST1(F)j}rvD`tXk1-OOPiMX?zzuG>19;3c#^~@K_sKp(tt2|Wicwzdh9@( zS7;;fGQ0?OTX}B#EY_FE7Rrp)Z-7alOnN31h<+ zlXQ8(35aJ)ZP+#4F0~J}+$zFX>;_?40ZBbC1#qB<@Jtz{{O+imAnzaS(}mzt`D!Uw zx90~EH>e*8t)`4Dw159Pr5Btm9$T|INd4X+SUsFPCoUvy*AyGI}BW{6R~WM@?oLYd8K-$U|}uT#O6 zSwKF;hobE=y1DdhK1wM2{?{#sC*b;vV}4GO|5#=43`-(@YB*Pa_O#~`G_DA2bxo=n~DiD%= z?xS?iZTq{YxBJ_iQ#5ETN2{$K)Dz@t7`2j=sr;%{f%LXFTG^$dX~OPyYv!zJ(fODE zj4BLxh9Eu-rsc=SgZX8B{bBD>XMpPX7Bztrs)a}llO9k23PB?B#&Wrs8QyhjCG2FN z#+SllLDD`~kP2L0n{&|Ku z?}Vi)@5*H`*Meu}UxnG+!l}Z?&Oi#?^wy$G^UX_Ei^!FSzsjSVK@q5h)8XHB(W16L zMtqhYjn;g(G=8aE^lNre#fi$>by;aVz4yEtST1+1)N=%=fRO3BaJQ4!sjp=<GLzaAy?g&>JM+FY>Y2VK3G-I zq(R+9qzZtt>9K-ozhO{pSi-i0r+8vhg z9~<~A!gku8>Q2HwreeN{rC0r#HJWKTOdQDIht8^UpW?E)M7Z_BNCXfXr{!+V>0FkI zQ|Aw{C<&Q|x%+(ZV-a~U|1j$JP0kAs(egunKLvxgmU%v|;x43a57X>!@(!Ro1&}7+ zvuA}GvJDt9RimI2uPe!SA#WsREaoL^0h$9Zbh#KJf>QQ;qB4RKNBw~C(+x|I*Kk_k zaA zr;L)ZTcd>XmrdAI!gW9-7a&Kn`) zes4QL;4#^>jvu#vzrkBi_X?GB97A7l4EFywKz^@o@G5q|?v&pQ>|FK8r z{Y5)taf#9X<_z}2yAkNV@?k*Cnn5WhqX9w)BM?`FJ89ssV$p&5YyKPe1|97=he%Y>+iN z@#R;;t*f*#d!Gx`9yR~HwfgV=trsV~4`8P~MStex&wKn$!C;5{TnlVe?rbofWeQp( z14g?W`nYC8qzgY(51e6j&?p`MLV7T1Dcbuib#6IlvL}uZcJh~+=&ajcREc8#T2!Tn8tv7K z1}`?5q=@hdiF;QKBxxb*(~#8TAyyyx*8?eGftsx0hRB<=lSpr}Pw(&yFdp&gIKMV3 z-8|zkpd-|CtFlDv?cuje*Q*8g+VbgZ7jY*j8_t!-=k!IyUP+fBR&p7PKLRFy4sD^0 z3n-~qZ4gApufGqX@O~MG>r6DygQEN~k74K1JxK7v!f~_R-iVXZmP?9u5)Wj&R@;r3 zf-#^U7ko)iNT{-XLTL_NUpQ87;K)AfpRVn<{S&cv!2D<#BLCWeZ*zQg^W$12(8+I3 zOjrjv%B6+Y^J5pnl z>ZGbFsMnt}zLGqxT2}V`_|A)xy$R$q7J`={$~ z(o>IjEk~`=&6+)C;opNy6n98&(3Y}tXBE{%EclXgsq0Ime7TKT{`HGKB)pc2G8U0pAxiiqF%Z~MGq;jDiTd!PNv2(ZLC}6qFEoxt zLd)_kyB-#IyuG<;YIwr?d+ZfBtG8D!qgyZGk{Gx0^YRD4F;NGTZaiiK?f&||L0fJhr@GO`Ot$ms8yyGh5-BP z%_7$xzvp}PI+|}C_G4+gmxMK^P6BVZnR<#ud;BQuoXWe}0&9oS`iyTLqo-W1v`1dL z$Cb8Ji?*-6(&AlaY^f<-1d3+JAImbT^yJ(CoEr#)Y{XQrC8!qvDuFwX3*0fe*dN02 zH>F%7*LAMq))4KJfzYCnh$etd#T2^N3E2jSJ`^Gw!r|_`S(yp^*DG(w??on^6rRJC zB00%MloO2CfQoq9nv71akq|W(^bCgkh4}lIY^c=>3_3m1j0rS;#_n-q@AfyfzpJg( zaUTKenwplBhkHuP39Iv;wezEldmtKDg0E>D_Llh{uhR_B?HWiqnjzvZ6yk{!L0zKW z+4w0NJ8nW6no1SQE*jhu4s5E-c9L}t6hXr0<}KKNtJBX~Q{)*RP=Yh4VN1H|2);sV zxH}9D-s~8+PhT;`H>$vGf9JoIos{bkJBSZ<6pw`EH31A)b&5o)d36h#dwuU`U1pa0 z6&(`|TEMt%m!vFgyB4%+MwNtJJ{(>drk{l{Ict}FqS8^rS=p}LyNRgFSv^o8N7DB} z+wH{^AQe6Ji^uemdyNpqqG|AA#FZ?CPnu%pY`9woyHsgt=VdSWjtt|oo>qQ6)M@f! z670{3XKAHU78Pq|z3H45ED|$otb_=?ULdv-%7Ge&R6)9S!AKeAC@D8F4{>YPVthwI zDRam4@@)z^F3P>IH7M=T9OOoFJ}C^X#ymfTb2S%5bxt#7$;#U%WO-ZaXgK~oKd`XO zx_qu~;_xjQI`q8j563l$D1~LlXXr%g$7L3VG<9uRxd(EJ4jOAcjpUv}DibsvtC&Bh z4$TvJi~k7>Bu^=E5I3eW~yyIlhyVYle|54 zpD@JV+k16}ssyY3>0H;?F!AXlcRZ?p$2egXsJe6)oRM!eyO}(C4ym7V7OcQr$tHq5F_Nw4=}BoaTIH zHYr8q-6Yi|5&H8nx;n6=Te_TjuVOSVS~c-l0QyTPMapi|M&P=G$^Nx-e>^vQ<_! zVefks&-~`%rA9`&U3)Ie7~j*W!%K9hz!JpQx33jTt897|Ec5s6*$i!-y}tWi=Dbu6 zNxKbNhuIchHfN{9TB(3V3iS#h3{QY?)F*7|_?)hH7meS_uuP;~*+WPxi~f{ei2b$s zMtp>QeR{k4lbi5TRcbGRi_Q+CKFzOt^~?swTAjJ!{ku|jZ|$?- zzV#LsNZ&Rk0(a#2vRYD8VF79Bu@AewJy?{gG;R{zCGd7i^>b!pYju;D%}GwT=!k1+ z-EPd8rJAKq0QkmKL6ez6bXRHy-T_L?ZCsXial$L}7?#{)%1!(Wsy)w`dYKi9+zWYj z$-!vu`ODSCd#D{0jPVLM3qI$Hk;=CKL}e5IynvKJIQUJ2Tjg` zjqlXx+7WSGVmR?cPJQ7@+wj;WqZsAHnxb*zrn`3wIVE(ksjpwiCsQw9?D-5Fo(7D< z9jURwGwv|%9hK&R6|QOiddg~Yz|#h$okUK4dFHm@(YC~m*Wi9-_CM&;tvrZLLT@Bp zj0Qs@9h{N+Er|rSJksKHr*IG@d$1|6w)64)a2eIs^W(+5$FJW9(y)@bSehJClElQ3 ztBFcjnjUjRPm3By;Pt_mGS+%ZSqNQLLbI){r$2iF�^6C%5dt_wVyl@9OWj7#+P( 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 literal 48680 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 11 UTF-8 - 1.0.19 + 1.0.20 smartai.examples.nlp.translation.TranslationDemo diff --git a/pom.xml b/pom.xml index 6df95c2..a78b7ba 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ SmartJavaAI cn.smartjavaai smartjavaai-parent - 1.0.19 + 1.0.20 pom SmartJavaAI diff --git a/smartjavaai-all/pom.xml b/smartjavaai-all/pom.xml index 3bd7b2d..e66d60c 100644 --- a/smartjavaai-all/pom.xml +++ b/smartjavaai-all/pom.xml @@ -6,11 +6,11 @@ cn.smartjavaai smartjavaai-parent - 1.0.19 + 1.0.20 smartjavaai-all - 1.0.19 + 1.0.20 ${project.artifactId} SmartJavaAI https://github.com/geekwenjie/SmartJavaAI diff --git a/smartjavaai-bom/pom.xml b/smartjavaai-bom/pom.xml index 22c1a01..d841897 100644 --- a/smartjavaai-bom/pom.xml +++ b/smartjavaai-bom/pom.xml @@ -6,10 +6,10 @@ cn.smartjavaai smartjavaai-parent - 1.0.19 + 1.0.20 - 1.0.19 + 1.0.20 smartjavaai-bom smartjavaai-bom 统一版本管理的 BOM 包,同时支持 import 和全量依赖 diff --git a/smartjavaai-common/pom.xml b/smartjavaai-common/pom.xml index 8767b3d..1314870 100644 --- a/smartjavaai-common/pom.xml +++ b/smartjavaai-common/pom.xml @@ -6,7 +6,7 @@ cn.smartjavaai smartjavaai-parent - 1.0.19 + 1.0.20 smartjavaai-common diff --git a/smartjavaai-common/src/main/java/cn/smartjavaai/common/config/ModelConfig.java b/smartjavaai-common/src/main/java/cn/smartjavaai/common/config/ModelConfig.java new file mode 100644 index 0000000..fdbdfe7 --- /dev/null +++ b/smartjavaai-common/src/main/java/cn/smartjavaai/common/config/ModelConfig.java @@ -0,0 +1,56 @@ +package cn.smartjavaai.common.config; + +import cn.smartjavaai.common.enums.DeviceEnum; +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 模型配置 + * @author dwj + */ +@Data +public class ModelConfig { + + /** + * 设备类型 + */ + private DeviceEnum device; + + /** + * gpu设备ID 当device为GPU时生效 + */ + private int gpuId = 0; + + /** + * 批量数据打包方式:stack,padding + */ + private String batchifier; + + /** + * 个性化配置(按模型类型动态解析) + */ + private ConcurrentHashMap customParams = new ConcurrentHashMap<>(); + + public T getCustomParam(String key, Class clazz) { + Object value = customParams.get(key); + if (value == null) return null; + return clazz.cast(value); + } + + /** + * 添加个性化配置项 + */ + public void putCustomParam(String key, Object value) { + if (customParams == null) { + customParams = new ConcurrentHashMap<>(); + } + customParams.put(key, value); + } + + + + +} diff --git a/smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/DetectionRectangle.java b/smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/DetectionRectangle.java index a4bc898..06fb5c5 100644 --- a/smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/DetectionRectangle.java +++ b/smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/DetectionRectangle.java @@ -23,4 +23,5 @@ public class DetectionRectangle { this.width = width; this.height = height; } + } diff --git a/smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/ocr/TableStructure.java b/smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/ocr/TableStructure.java new file mode 100644 index 0000000..4b7c214 --- /dev/null +++ b/smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/ocr/TableStructure.java @@ -0,0 +1,8 @@ +package cn.smartjavaai.common.entity.ocr; + +/** + * 表格结构 + * @author dwj + */ +public class TableStructure { +} diff --git a/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java b/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java index e30fafb..1a3c75c 100644 --- a/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java +++ b/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java @@ -370,6 +370,104 @@ public class ImageUtils { g.drawString(text, x + padding, y + ascent); } + /** + * 计算左上角,右下角坐标 (x0,y0,x1,y1) + * Get absolute coordinations + * + * @param rect + * @param width + * @param height + * @return + */ + public static int[] rectXYXY(ai.djl.modality.cv.output.Rectangle rect, int width, int height) { + int left = Math.max((int) (width * rect.getX()), 0); + int top = Math.max((int) (height * rect.getY()), 0); + int right = Math.min((int) (width * (rect.getX() + rect.getWidth())), width - 1); + int bottom = Math.min((int) (height * (rect.getY() + rect.getHeight())), height - 1); + return new int[] {left, top, right, bottom}; + } + + /** + * 列出文件夹下的所有图片文件 + * List all image files under the folder + * + * @param folderPath + * @return + */ + public static List listImageFiles(String folderPath) { + File folder = new File(folderPath); + List imageFiles = new ArrayList<>(); + if (folder.exists() && folder.isDirectory()) { + File[] files = folder.listFiles(); + if (files == null) { + return imageFiles; + } + for (File file : files) { + if (file.isFile()) { + String name = file.getName().toLowerCase(); + if (name.endsWith(".jpg") || name.endsWith(".jpeg") || + name.endsWith(".png") || name.endsWith(".bmp") || + name.endsWith(".gif") || name.endsWith(".tiff") || + name.endsWith(".webp")) { + imageFiles.add(file); + } + } + } + } + return imageFiles; + } + + /** + * 读取指定目录下所有图片,返回 List(DJL 格式) + * + * @param folderPath 图片文件夹路径 + * @return List + * @throws IOException + */ + public static List readImagesFromFolder(String folderPath) throws IOException { + File folder = new File(folderPath); + List imageList = new ArrayList<>(); + if (folder.exists() && folder.isDirectory()) { + File[] files = folder.listFiles(); + if (files == null) { + return imageList; + } + for (File file : files) { + if (file.isFile()) { + String name = file.getName().toLowerCase(); + if (name.endsWith(".jpg") || name.endsWith(".jpeg") || + name.endsWith(".png") || name.endsWith(".bmp") || + name.endsWith(".gif") || name.endsWith(".tiff") || + name.endsWith(".webp")) { + + Image img = ImageFactory.getInstance().fromInputStream(Files.newInputStream(file.toPath())); + imageList.add(img); + } + } + } + } + return imageList; + } + + /** + * 判断所有图片尺寸是否一致 + * + * @param images 图片列表 + */ + public static boolean isAllImageSizeEqual(List images) { + if (images == null || images.isEmpty()) { + return true; // 空集合视为一致 + } + int width = images.get(0).getWidth(); + int height = images.get(0).getHeight(); + for (Image img : images) { + if (img.getWidth() != width || img.getHeight() != height) { + return false; + } + } + return true; + } + } diff --git a/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/VideoUtils.java b/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/VideoUtils.java new file mode 100644 index 0000000..d43ddda --- /dev/null +++ b/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/VideoUtils.java @@ -0,0 +1,77 @@ +package cn.smartjavaai.common.utils; + +import org.bytedeco.ffmpeg.global.avcodec; +import org.bytedeco.javacv.FFmpegFrameGrabber; +import org.bytedeco.javacv.FFmpegFrameRecorder; +import org.bytedeco.javacv.Frame; +import org.bytedeco.javacv.OpenCVFrameConverter; +import org.bytedeco.opencv.global.opencv_core; +import org.bytedeco.opencv.opencv_core.Mat; + +/** + * 视频工具类 + * @author dwj + * @date 2025/7/17 + */ +public class VideoUtils { + + /** + * 视频旋转 + * @param inputPath 输入视频路径 + * @param outputPath 输出视频路径 + * @param angle 旋转角度 + * @param format 视频格式 + * @param videoCodec 视频编码器 + * @throws FFmpegFrameRecorder.Exception + * @throws FFmpegFrameGrabber.Exception + */ + public static void rotateVideo(String inputPath, String outputPath, int angle, String format, int videoCodec) throws FFmpegFrameRecorder.Exception, FFmpegFrameGrabber.Exception { + FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputPath); + grabber.start(); + int inputWidth = grabber.getImageWidth(); + int inputHeight = grabber.getImageHeight(); + int outputWidth = inputWidth; + int outputHeight = inputHeight; + + if (angle == 90 || angle == 270) { + outputWidth = inputHeight; + outputHeight = inputWidth; + } + FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputPath, + outputWidth, outputHeight, grabber.getAudioChannels()); + recorder.setVideoCodec(videoCodec); + recorder.setFormat(format); + recorder.start(); + Frame frame; + OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat(); + while ((frame = grabber.grab()) != null) { + if (frame.image != null) { + Mat mat = converter.convert(frame); + Mat rotated = new Mat(); + switch (angle) { + case 90: + opencv_core.transpose(mat, rotated); + opencv_core.flip(rotated, rotated, 1); + break; + case 180: + opencv_core.flip(mat, rotated, -1); + break; + case 270: + opencv_core.transpose(mat, rotated); + opencv_core.flip(rotated, rotated, 0); + break; + default: + rotated = mat.clone(); + break; + } + frame = converter.convert(rotated); + recorder.record(frame); + } + } + recorder.stop(); + recorder.release(); + grabber.stop(); + grabber.release(); + } + +} diff --git a/smartjavaai-face/pom.xml b/smartjavaai-face/pom.xml index e5980da..beecee2 100644 --- a/smartjavaai-face/pom.xml +++ b/smartjavaai-face/pom.xml @@ -6,11 +6,11 @@ cn.smartjavaai smartjavaai-parent - 1.0.19 + 1.0.20 smartjavaai-face - 1.0.19 + 1.0.20 smartjavaai-face SmartJavaAI https://github.com/geekwenjie/SmartJavaAI diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceRecConfig.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceRecConfig.java index 48fbbb8..7803f38 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceRecConfig.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceRecConfig.java @@ -23,31 +23,11 @@ public class FaceRecConfig { */ private FaceRecModelEnum modelEnum; - /** - * 置信度阈值 - */ - private double confidenceThreshold = FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD; - - /** - * 相似度阈值 作用:判断是否为同一人脸 - */ - //private double similarityThreshold = 0D; - - /** - * 非极大抑制阈值 作用:消除重叠检测框,保留最优结果 - */ - private double nmsThresh = FaceDetectConstant.NMS_THRESHOLD; - /** * 模型路径 */ private String modelPath; - /** - * 人脸库路径 - */ - private String faceDbPath; - /** * 设备类型 */ diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/LivenessConfig.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/LivenessConfig.java index 6085a90..8ca3a02 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/LivenessConfig.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/LivenessConfig.java @@ -48,6 +48,11 @@ public class LivenessConfig { */ private int frameCount = LivenessConstant.DEFAULT_FRAME_COUNT; + /** + * 视频检测最大帧数 + */ + private int maxVideoDetectFrames = LivenessConstant.DEFAULT_MAX_VIDEO_DETECT_FRAMES; + /** * 真人阈值 */ diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/constant/LivenessConstant.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/constant/LivenessConstant.java index 4f8969f..54737ed 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/constant/LivenessConstant.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/constant/LivenessConstant.java @@ -24,4 +24,9 @@ public class LivenessConstant { * 视频默认检测帧数 */ public static final int DEFAULT_FRAME_COUNT = 10; + + /** + * 视频默认最大检测帧数 + */ + public static final int DEFAULT_MAX_VIDEO_DETECT_FRAMES = Integer.MAX_VALUE; } diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/dao/FaceDao.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/dao/FaceDao.java index 5a0c035..972dd60 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/dao/FaceDao.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/dao/FaceDao.java @@ -1,6 +1,5 @@ package cn.smartjavaai.face.dao; -import cn.smartjavaai.face.entity.FaceData; import cn.smartjavaai.face.sqllite.RowMapper; import cn.smartjavaai.face.sqllite.SqliteHelper; import cn.smartjavaai.face.utils.VectorUtils; @@ -80,7 +79,7 @@ public class FaceDao { */ public FaceVector findById(String id) throws SQLException, ClassNotFoundException { SqliteHelper sqliteHelper = SqliteHelper.getInstance(dbFilePath); - String sql = "select \"id\",\"vector\",\"metadata\" from " + FACE_TABLE_NAME + " where \"id\"=" + id; + String sql = "select \"id\",\"vector\",\"metadata\" from " + FACE_TABLE_NAME + " where \"id\" = '" + id + "'"; List faceVectors = sqliteHelper.executeQuery(sql, new RowMapper() { @Override public FaceVector mapRow(ResultSet rs, int id) throws SQLException { @@ -164,8 +163,9 @@ public class FaceDao { * @throws ClassNotFoundException 类未找到异常 */ public List findFace(int pageNo, int pageSize) throws SQLException, ClassNotFoundException { + long offset = (pageNo - 1) * pageSize; String sql = "select \"id\",\"vector\",\"metadata\" from " + FACE_TABLE_NAME + - " limit " + pageNo * pageSize + "," + pageSize; + " limit " + offset + "," + pageSize; SqliteHelper sqliteHelper = SqliteHelper.getInstance(dbFilePath); return sqliteHelper.executeQuery(sql, new RowMapper() { @Override diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceData.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceData.java deleted file mode 100644 index 2f33f1f..0000000 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceData.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.smartjavaai.face.entity; - -import lombok.Data; - -/** - * 人脸数据 - * @author dwj - */ -@Data -public class FaceData { - - private String key; - private long index; - private byte[] imgData; - private int width = 256; - private int height = 256; - private int channel = 3; - -} diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/factory/ExpressionModelFactory.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/factory/ExpressionModelFactory.java index 39e89b8..d5327dd 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/factory/ExpressionModelFactory.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/factory/ExpressionModelFactory.java @@ -24,6 +24,9 @@ public class ExpressionModelFactory { // 使用 volatile 和双重检查锁定来确保线程安全的单例模式 private static volatile ExpressionModelFactory instance; + /** + * 模型缓存 + */ private static final ConcurrentHashMap modelMap = new ConcurrentHashMap<>(); /** diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/CommonFaceRecModel.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/CommonFaceRecModel.java index 749825e..63cd24f 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/CommonFaceRecModel.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/CommonFaceRecModel.java @@ -9,8 +9,6 @@ import ai.djl.ndarray.NDManager; import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.ModelNotFoundException; import ai.djl.repository.zoo.ZooModel; -import cn.hutool.core.lang.UUID; -import cn.hutool.core.lang.generator.UUIDGenerator; import cn.smartjavaai.common.entity.*; import cn.smartjavaai.common.entity.face.FaceInfo; import cn.smartjavaai.common.entity.face.FaceSearchResult; @@ -28,7 +26,7 @@ import cn.smartjavaai.face.enums.SimilarityType; import cn.smartjavaai.face.exception.FaceException; import cn.smartjavaai.face.factory.FaceDetModelFactory; import cn.smartjavaai.face.model.facedect.FaceDetModel; -import cn.smartjavaai.face.model.facerec.criterial.FaceRecCriteriaFactory; +import cn.smartjavaai.face.model.facerec.criteria.FaceRecCriteriaFactory; import cn.smartjavaai.face.preprocess.DJLImagePreprocessor; import cn.smartjavaai.face.utils.*; import cn.smartjavaai.face.vector.config.MilvusConfig; @@ -646,6 +644,22 @@ public class CommonFaceRecModel implements FaceRecModel{ } } + @Override + public R getFaceInfoById(String id) { + if(vectorDBClient == null){ + return R.fail(1000, "向量数据库未初始化成功"); + } + return R.ok(vectorDBClient.getFaceInfoById(id)); + } + + @Override + public R> listFaces(long pageNum, long pageSize) { + if(vectorDBClient == null){ + return R.fail(1000, "向量数据库未初始化成功"); + } + return R.ok(vectorDBClient.listFaces(pageNum, pageSize)); + } + @Override public void loadFaceFeatures() { if(Objects.isNull(vectorDBClient)){ diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FaceRecModel.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FaceRecModel.java index b353323..1a695bf 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FaceRecModel.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FaceRecModel.java @@ -6,6 +6,7 @@ import cn.smartjavaai.face.config.FaceRecConfig; import cn.smartjavaai.face.entity.FaceRegisterInfo; import cn.smartjavaai.face.entity.FaceSearchParams; import cn.smartjavaai.common.entity.face.FaceSearchResult; +import cn.smartjavaai.face.vector.entity.FaceVector; import java.awt.image.BufferedImage; import java.io.InputStream; @@ -245,8 +246,26 @@ public interface FaceRecModel extends AutoCloseable{ } + /** + * 使用人脸ID获取人脸信息 + * @param id + * @return + */ + default R getFaceInfoById(String id){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + /** + * 获取人脸列表 + * @param pageNum + * @param pageSize + * @return + */ + default R> listFaces(long pageNum, long pageSize){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + /** * 删除已注册人脸 diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/SeetaFace6FaceRecModel.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/SeetaFace6FaceRecModel.java index 1624b64..64b58d0 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/SeetaFace6FaceRecModel.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/SeetaFace6FaceRecModel.java @@ -486,13 +486,13 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{ * 检查是否存在人脸库 * @return */ - private boolean checkFaceDb(){ - if(Objects.nonNull(config) && StringUtils.isNotBlank(config.getFaceDbPath())){ - File file = new File(config.getFaceDbPath()); - return file.exists() && file.isFile(); - } - return false; - } +// private boolean checkFaceDb(){ +// if(Objects.nonNull(config) && StringUtils.isNotBlank(config.getFaceDbPath())){ +// File file = new File(config.getFaceDbPath()); +// return file.exists() && file.isFile(); +// } +// return false; +// } private FaceResult searchFaceDb(long index,float similar) { if(index >= 0){ @@ -837,6 +837,22 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{ FaceRecModel.super.upsertFace(faceRegisterInfo, imageData); } + @Override + public R getFaceInfoById(String id) { + if(vectorDBClient == null){ + return R.fail(1000, "向量数据库未初始化成功"); + } + return R.ok(vectorDBClient.getFaceInfoById(id)); + } + + @Override + public R> listFaces(long pageNum, long pageSize) { + if(vectorDBClient == null){ + return R.fail(1000, "向量数据库未初始化成功"); + } + return R.ok(vectorDBClient.listFaces(pageNum, pageSize)); + } + @Override public void close() throws Exception { if(Objects.nonNull(faceDetectorPool)){ diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/criterial/FaceRecCriteriaFactory.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/criteria/FaceRecCriteriaFactory.java similarity index 86% rename from smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/criterial/FaceRecCriteriaFactory.java rename to smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/criteria/FaceRecCriteriaFactory.java index be2e88a..47b27a9 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/criterial/FaceRecCriteriaFactory.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/criteria/FaceRecCriteriaFactory.java @@ -1,28 +1,15 @@ -package cn.smartjavaai.face.model.facerec.criterial; +package cn.smartjavaai.face.model.facerec.criteria; import ai.djl.Device; import ai.djl.modality.cv.Image; -import ai.djl.modality.cv.output.DetectedObjects; -import ai.djl.modality.cv.transform.Normalize; -import ai.djl.modality.cv.transform.Resize; -import ai.djl.modality.cv.transform.ToTensor; -import ai.djl.modality.cv.translator.ImageFeatureExtractor; -import ai.djl.modality.cv.translator.ImageFeatureExtractorFactory; import ai.djl.repository.zoo.Criteria; import ai.djl.training.util.ProgressBar; -import ai.djl.translate.Translator; import cn.smartjavaai.common.enums.DeviceEnum; -import cn.smartjavaai.face.config.FaceDetConfig; import cn.smartjavaai.face.config.FaceRecConfig; -import cn.smartjavaai.face.constant.FaceDetectConstant; import cn.smartjavaai.face.constant.FaceNetConstant; -import cn.smartjavaai.face.constant.RetinaFaceConstant; -import cn.smartjavaai.face.constant.UltraLightFastGenericFaceConstant; -import cn.smartjavaai.face.enums.FaceDetModelEnum; import cn.smartjavaai.face.enums.FaceRecModelEnum; import cn.smartjavaai.face.model.facerec.translator.FaceFeatureTranslator; import cn.smartjavaai.face.model.facerec.translator.FaceNetRecTranslator; -import cn.smartjavaai.face.translator.FaceDetectionTranslator; import org.apache.commons.lang3.StringUtils; import java.nio.file.Paths; diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/liveness/CommonLivenessModel.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/liveness/CommonLivenessModel.java index d72ed8b..491fbac 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/liveness/CommonLivenessModel.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/liveness/CommonLivenessModel.java @@ -354,6 +354,9 @@ public class CommonLivenessModel implements LivenessDetModel{ } // 逐帧处理视频 for (int frameIndex = 0; frameIndex < totalFrames; frameIndex++) { + if(frameIndex >= config.getMaxVideoDetectFrames()){ + return R.fail(10002, "超出最大检测帧数:" + config.getMaxVideoDetectFrames()); + } // 获取当前帧 Frame frame = grabber.grabImage(); if (frame != null) { diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/liveness/Seetaface6LivenessModel.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/liveness/Seetaface6LivenessModel.java index 130426d..d286af2 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/liveness/Seetaface6LivenessModel.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/liveness/Seetaface6LivenessModel.java @@ -515,6 +515,9 @@ public class Seetaface6LivenessModel implements LivenessDetModel{ } // 逐帧处理视频 for (int frameIndex = 0; frameIndex < totalFrames; frameIndex++) { + if(frameIndex >= config.getMaxVideoDetectFrames()){ + return R.fail(10002, "超出最大检测帧数:" + config.getMaxVideoDetectFrames()); + } // 获取当前帧 Frame frame = grabber.grabImage(); if (frame != null) { diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/MilvusClient.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/MilvusClient.java index f71eb9e..2f7a466 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/MilvusClient.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/MilvusClient.java @@ -14,8 +14,11 @@ import io.milvus.param.*; import io.milvus.param.collection.*; import io.milvus.param.dml.*; import io.milvus.param.index.CreateIndexParam; +import io.milvus.response.DescCollResponseWrapper; import io.milvus.response.QueryResultsWrapper; import io.milvus.response.SearchResultsWrapper; +import io.milvus.v2.service.collection.request.DescribeCollectionReq; +import io.milvus.v2.service.collection.response.DescribeCollectionResp; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -53,6 +56,14 @@ public class MilvusClient implements VectorDBClient { serviceClient = new MilvusServiceClient(connectParam); collectionName = StringUtils.isNotBlank(config.getCollectionName()) ? config.getCollectionName() : VectorDBConstants.Defaults.DEFAULT_COLLECTION_NAME; createCollection(collectionName, config.getDimension()); + + boolean isAutoID = isAutoID(collectionName); + if(isAutoID && config.getIdStrategy() != IdStrategy.AUTO){ + throw new VectorDBException("ID策略与当前Collection不匹配"); + } + if(!isAutoID && config.getIdStrategy() == IdStrategy.AUTO){ + throw new VectorDBException("ID策略与当前Collection不匹配"); + } if(config.isUseMemoryCache()){ // 加载集合到内存 loadFaceFeatures(); @@ -152,6 +163,21 @@ public class MilvusClient implements VectorDBClient { } } + /** + * 判断是否为自增长ID + * @param collectionName + * @return + */ + private boolean isAutoID(String collectionName) { + R response = serviceClient.describeCollection( + DescribeCollectionParam.newBuilder() + .withCollectionName(collectionName) + .build() + ); + DescCollResponseWrapper wrapper = new DescCollResponseWrapper(response.getData()); + return wrapper.getPrimaryField().isAutoID(); + } + @Override public void dropCollection(String collectionName) { try { @@ -204,6 +230,7 @@ public class MilvusClient implements VectorDBClient { if(faceVector.getVector() == null || faceVector.getVector().length == 0){ throw new VectorDBException("插入数据失败:vector不能为空"); } + //自定义ID if(config.getIdStrategy() == IdStrategy.CUSTOM){ if(StringUtils.isBlank(faceVector.getId())){ @@ -525,40 +552,22 @@ public class MilvusClient implements VectorDBClient { } } - public void releaseCollection(String collectionName) { - if (!isInit){ - throw new VectorDBException("Milvus未初始化完毕"); - } - R response = serviceClient.releaseCollection(ReleaseCollectionParam.newBuilder() - .withCollectionName(collectionName) - .build()); - - if (response.getStatus() != R.Status.Success.getCode()) { - throw new VectorDBException("Milvus releaseCollection失败,msg: " + response.getMessage()); - } - } - - @Override - public FaceSearchResult getById(String id) { + public FaceVector getFaceInfoById(String id) { try { - if (!isInit){ + if (!isInit) { throw new VectorDBException("Milvus未初始化完毕"); } - // 构造搜索参数 - SearchParam searchParam = SearchParam.newBuilder() - .withCollectionName(collectionName) - .withExpr(VectorDBConstants.FieldNames.ID_FIELD + " == " + id) - .withOutFields(Arrays.asList(VectorDBConstants.FieldNames.ID_FIELD, VectorDBConstants.FieldNames.METADATA_FIELD)) - .build(); - - + String expr = VectorDBConstants.FieldNames.ID_FIELD + " == '" + id + "'"; + if(config.getIdStrategy() == IdStrategy.AUTO){ + expr = VectorDBConstants.FieldNames.ID_FIELD + " == " + id; + } // 5. 执行查询 R response = serviceClient.query( QueryParam.newBuilder() .withCollectionName(collectionName) - .withExpr(VectorDBConstants.FieldNames.ID_FIELD + " == " + id) - .withOutFields(Arrays.asList(VectorDBConstants.FieldNames.ID_FIELD, VectorDBConstants.FieldNames.METADATA_FIELD)) + .withExpr(expr) + .withOutFields(Arrays.asList(VectorDBConstants.FieldNames.ID_FIELD, VectorDBConstants.FieldNames.VECTOR_FIELD, VectorDBConstants.FieldNames.METADATA_FIELD)) .build() ); @@ -574,12 +583,91 @@ public class MilvusClient implements VectorDBClient { } // 提取第一条记录 QueryResultsWrapper.RowRecord row = records.get(0); - return new FaceSearchResult(id, 1,(String)row.get(VectorDBConstants.FieldNames.METADATA_FIELD)); + Object vectorObj = row.get(VectorDBConstants.FieldNames.VECTOR_FIELD); + float[] vector = null; + if (vectorObj instanceof List) { + // Milvus SDK通常返回List,转成float[] + List vectorList = (List) vectorObj; + vector = new float[vectorList.size()]; + for (int i = 0; i < vectorList.size(); i++) { + vector[i] = vectorList.get(i); + } + } + return new FaceVector(id, vector, (String) row.get(VectorDBConstants.FieldNames.METADATA_FIELD)); } catch (Exception e) { - throw new VectorDBException("搜索 Milvus 向量失败", e); + throw new RuntimeException(e); } } + @Override + public List listFaces(long pageNum, long pageSize) { + try { + if (!isInit) { + throw new VectorDBException("Milvus未初始化完毕"); + } + if (pageNum < 1 || pageSize < 1) { + throw new IllegalArgumentException("pageNum和pageSize必须大于0"); + } + long offset = (pageNum - 1) * pageSize; + // 构造查询参数,使用offset和limit实现分页 + QueryParam queryParam = QueryParam.newBuilder() + .withCollectionName(collectionName) + .withOutFields(Arrays.asList( + VectorDBConstants.FieldNames.ID_FIELD, + VectorDBConstants.FieldNames.VECTOR_FIELD, + VectorDBConstants.FieldNames.METADATA_FIELD)) + .withOffset(offset) + .withLimit(pageSize) + .build(); + + R response = serviceClient.query(queryParam); + + if (response.getStatus() != R.Status.Success.getCode()) { + throw new VectorDBException("分页查询失败: " + response.getMessage()); + } + + QueryResultsWrapper wrapper = new QueryResultsWrapper(response.getData()); + List records = wrapper.getRowRecords(); + if (records.isEmpty()) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + for (QueryResultsWrapper.RowRecord row : records) { + String id = (String) row.get(VectorDBConstants.FieldNames.ID_FIELD); + Object vectorObj = row.get(VectorDBConstants.FieldNames.VECTOR_FIELD); + float[] vector = null; + if (vectorObj instanceof List) { + List vectorList = (List) vectorObj; + vector = new float[vectorList.size()]; + for (int i = 0; i < vectorList.size(); i++) { + vector[i] = vectorList.get(i); + } + } + String metadata = (String) row.get(VectorDBConstants.FieldNames.METADATA_FIELD); + result.add(new FaceVector(id, vector, metadata)); + } + + return result; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void releaseCollection(String collectionName) { + if (!isInit){ + throw new VectorDBException("Milvus未初始化完毕"); + } + R response = serviceClient.releaseCollection(ReleaseCollectionParam.newBuilder() + .withCollectionName(collectionName) + .build()); + + if (response.getStatus() != R.Status.Success.getCode()) { + throw new VectorDBException("Milvus releaseCollection失败,msg: " + response.getMessage()); + } + } + + @Override public void loadFaceFeatures() { // 加载集合到内存 diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/SQLiteClient.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/SQLiteClient.java index 098520e..11e2437 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/SQLiteClient.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/SQLiteClient.java @@ -196,6 +196,42 @@ public class SQLiteClient implements VectorDBClient { } } + @Override + public FaceVector getFaceInfoById(String id) { + if (!isInit) { + throw new VectorDBException("人脸库未加载完毕"); + } + // 先从内存缓存中获取 + FaceVector faceVector = memoryIndex.get(id); + if (faceVector == null) { + // 如果内存中没有,则从数据库查询 + try { + faceVector = faceDao.findById(id); + } catch (SQLException | ClassNotFoundException e) { + throw new VectorDBException("SQLite查询异常", e); + } + } + return faceVector; + } + + @Override + public List listFaces(long pageNum, long pageSize) { + if (!isInit) { + throw new VectorDBException("人脸库未加载完毕"); + } + + if (pageNum < 1 || pageSize < 1) { + throw new IllegalArgumentException("pageNum和pageSize必须大于0"); + } + + // 从数据库中查询指定分页的数据 + try { + return faceDao.findFace((int)pageNum, (int)pageSize); + } catch (Exception e) { + throw new VectorDBException("分页查询失败", e); + } + } + // ============= 私有辅助方法 ============= private void loadAllFeaturesToMemory() { @@ -234,19 +270,6 @@ public class SQLiteClient implements VectorDBClient { } } - @Override - public FaceSearchResult getById(String id) { - try { - FaceVector faceVector = faceDao.findById(id); - if(faceVector != null){ - return new FaceSearchResult(faceVector.getId(), 1.0f, faceVector.getMetadata()); - } - return null; - } catch (SQLException | RuntimeException | ClassNotFoundException e ) { - throw new VectorDBException("SQLite查询异常", e); - } - } - @Override public void loadFaceFeatures() { // 加载所有特征到内存 diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/VectorDBClient.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/VectorDBClient.java index 91ab325..4d3a3f7 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/VectorDBClient.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/VectorDBClient.java @@ -1,5 +1,6 @@ package cn.smartjavaai.face.vector.core; +import cn.smartjavaai.common.entity.R; import cn.smartjavaai.face.entity.FaceSearchParams; import cn.smartjavaai.face.vector.entity.FaceVector; import cn.smartjavaai.common.entity.face.FaceSearchResult; @@ -94,12 +95,21 @@ public interface VectorDBClient extends AutoCloseable { @Override void close(); - /** - * 获取指定ID的向量 + * 使用人脸ID获取人脸信息 + * @param id * @return */ - FaceSearchResult getById(String id); + FaceVector getFaceInfoById(String id); + + + /** + * 获取人脸列表 + * @param pageNum + * @param pageSize + * @return + */ + List listFaces(long pageNum, long pageSize); /** * 加载人脸特征到内存 diff --git a/smartjavaai-objectdetection/pom.xml b/smartjavaai-objectdetection/pom.xml index a3c550a..8b10643 100644 --- a/smartjavaai-objectdetection/pom.xml +++ b/smartjavaai-objectdetection/pom.xml @@ -6,11 +6,11 @@ cn.smartjavaai smartjavaai-parent - 1.0.19 + 1.0.20 smartjavaai-objectdetection - 1.0.19 + 1.0.20 smartjavaai-objectdetection SmartJavaAI https://github.com/geekwenjie/SmartJavaAI diff --git a/smartjavaai-ocr/pom.xml b/smartjavaai-ocr/pom.xml index 24fca8e..70315cd 100644 --- a/smartjavaai-ocr/pom.xml +++ b/smartjavaai-ocr/pom.xml @@ -6,7 +6,7 @@ cn.smartjavaai smartjavaai-parent - 1.0.19 + 1.0.20 smartjavaai-ocr @@ -18,9 +18,31 @@ smartjavaai-common ${project.version} + + + + + + + + + + + + + + org.apache.poi + poi + 4.0.0 + + + dom4j + dom4j + 1.6.1 + - 1.0.19 + 1.0.20 smartjavaai-ocr SmartJavaAI https://github.com/geekwenjie/SmartJavaAI diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/DirectionModelConfig.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/DirectionModelConfig.java index 4b90ba1..a9457d2 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/DirectionModelConfig.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/DirectionModelConfig.java @@ -1,8 +1,10 @@ package cn.smartjavaai.ocr.config; +import cn.smartjavaai.common.config.ModelConfig; import cn.smartjavaai.common.enums.DeviceEnum; import cn.smartjavaai.ocr.enums.CommonDetModelEnum; import cn.smartjavaai.ocr.enums.DirectionModelEnum; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel; import lombok.Data; @@ -12,33 +14,22 @@ import lombok.Data; * @date 2025/4/22 */ @Data -public class DirectionModelConfig { +public class DirectionModelConfig extends ModelConfig { /** * 模型 */ private DirectionModelEnum modelEnum; - /** - * 设备类型 - */ - private DeviceEnum device; - /** * 检测模型路径 */ private String modelPath; /** - * 检测模型 + * 文本检测模型 */ - private CommonDetModelEnum detModelEnum; - - /** - * 检测模型路径 - */ - private String detModelPath; - + private OcrCommonDetModel textDetModel; diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrDetModelConfig.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrDetModelConfig.java index f03bbf4..db19928 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrDetModelConfig.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrDetModelConfig.java @@ -1,5 +1,6 @@ package cn.smartjavaai.ocr.config; +import cn.smartjavaai.common.config.ModelConfig; import cn.smartjavaai.common.enums.DeviceEnum; import cn.smartjavaai.ocr.enums.CommonDetModelEnum; import lombok.Data; @@ -10,18 +11,13 @@ import lombok.Data; * @date 2025/4/22 */ @Data -public class OcrDetModelConfig { +public class OcrDetModelConfig extends ModelConfig { /** * 模型 */ private CommonDetModelEnum modelEnum; - /** - * 设备类型 - */ - private DeviceEnum device; - /** * 检测模型路径 */ diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrRecModelConfig.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrRecModelConfig.java index 0aa9b35..1850c46 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrRecModelConfig.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrRecModelConfig.java @@ -1,9 +1,12 @@ package cn.smartjavaai.ocr.config; +import cn.smartjavaai.common.config.ModelConfig; import cn.smartjavaai.common.enums.DeviceEnum; import cn.smartjavaai.ocr.enums.CommonDetModelEnum; import cn.smartjavaai.ocr.enums.CommonRecModelEnum; import cn.smartjavaai.ocr.enums.DirectionModelEnum; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; +import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel; import lombok.Data; /** @@ -12,41 +15,26 @@ import lombok.Data; * @date 2025/4/22 */ @Data -public class OcrRecModelConfig { - - /** - * 检测模型 - */ - private CommonDetModelEnum detModelEnum; +public class OcrRecModelConfig extends ModelConfig { /** * 识别模型 */ private CommonRecModelEnum recModelEnum; - /** - * 设备类型 - */ - private DeviceEnum device; - - /** - * 检测模型路径 - */ - private String detModelPath; - /** * 识别模型路径 */ private String recModelPath; /** - * 方向检测模型 + * 文本检测模型 */ - private DirectionModelEnum directionModelEnum; + private OcrCommonDetModel textDetModel; /** - * 方向检测模型路径 + * 文本方向模型 */ - private String directionModelPath; + private OcrDirectionModel directionModel; } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrRecOptions.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrRecOptions.java new file mode 100644 index 0000000..86e1b86 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/OcrRecOptions.java @@ -0,0 +1,30 @@ +package cn.smartjavaai.ocr.config; + +import lombok.Data; + +/** + * OCR 识别配置 + * @author dwj + */ +@Data +public class OcrRecOptions { + + /** + * 是否进行文本方向矫正 + */ + private boolean enableDirectionCorrect = false; + + /** + * 是否进行结果分行 + */ + private boolean enableLineSplit = true; + + + public OcrRecOptions(boolean enableDirectionCorrect, boolean enableLineSplit) { + this.enableDirectionCorrect = enableDirectionCorrect; + this.enableLineSplit = enableLineSplit; + } + + public OcrRecOptions() { + } +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/TableStructureConfig.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/TableStructureConfig.java new file mode 100644 index 0000000..6ca3e48 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/config/TableStructureConfig.java @@ -0,0 +1,27 @@ +package cn.smartjavaai.ocr.config; + +import cn.smartjavaai.common.config.ModelConfig; +import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.ocr.enums.CommonDetModelEnum; +import cn.smartjavaai.ocr.enums.TableStructureModelEnum; +import lombok.Data; + +/** + * OCR表格结构识别模型配置 + * @author dwj + */ +@Data +public class TableStructureConfig extends ModelConfig { + + /** + * 模型 + */ + private TableStructureModelEnum modelEnum; + + /** + * 检测模型路径 + */ + private String modelPath; + + +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrBox.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrBox.java index fc2b6df..53fa899 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrBox.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrBox.java @@ -1,5 +1,6 @@ package cn.smartjavaai.ocr.entity; +import cn.smartjavaai.common.entity.DetectionRectangle; import cn.smartjavaai.common.entity.Point; import lombok.Data; @@ -49,4 +50,23 @@ public class OcrBox { (float)bottomLeft.getX(), (float)bottomLeft.getY() }; } + + /** + * 转换为 DetectionRectangle,使用最小外包矩形 + */ + public DetectionRectangle toDetectionRectangle() { + float[] pts = toFloatArray(); + float minX = Math.min(Math.min(pts[0], pts[2]), Math.min(pts[4], pts[6])); + float minY = Math.min(Math.min(pts[1], pts[3]), Math.min(pts[5], pts[7])); + float maxX = Math.max(Math.max(pts[0], pts[2]), Math.max(pts[4], pts[6])); + float maxY = Math.max(Math.max(pts[1], pts[3]), Math.max(pts[5], pts[7])); + DetectionRectangle rect = new DetectionRectangle(); + rect.setX((int) minX); + rect.setY((int) minY); + rect.setWidth((int) (maxX - minX)); + rect.setHeight((int) (maxY - minY)); + return rect; + } + + } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrInfo.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrInfo.java index f287033..9661fdc 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrInfo.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrInfo.java @@ -4,6 +4,7 @@ import lombok.Data; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * OCR信息 @@ -15,13 +16,22 @@ public class OcrInfo { private List> lineList; + private List ocrItemList; + private String fullText; + public OcrInfo(List> lineList, String fullText) { this.lineList = lineList; this.fullText = fullText; } public OcrInfo() { } + + public List flattenLines() { + return lineList.stream() + .flatMap(List::stream) + .collect(Collectors.toList()); + } } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrItem.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrItem.java index 9c74752..19d02c0 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrItem.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/OcrItem.java @@ -31,7 +31,6 @@ public class OcrItem { private float score; - public OcrItem(OcrBox ocrBox, String text) { this.ocrBox = ocrBox; this.text = text; diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/TableStructureResult.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/TableStructureResult.java new file mode 100644 index 0000000..725bea2 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/entity/TableStructureResult.java @@ -0,0 +1,33 @@ +package cn.smartjavaai.ocr.entity; + +import lombok.Data; + +import java.util.List; + +/** + * @author dwj + */ +@Data +public class TableStructureResult { + + private List ocrItemList; + + private List tableTagList; + + private String html; + + + public TableStructureResult(List ocrItemList, List tableTagList) { + this.ocrItemList = ocrItemList; + this.tableTagList = tableTagList; + } + + public TableStructureResult() { + } + + public TableStructureResult(List ocrItemList, List tableTagList, String html) { + this.ocrItemList = ocrItemList; + this.tableTagList = tableTagList; + this.html = html; + } +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/CommonDetModelEnum.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/CommonDetModelEnum.java index d3aaae3..cb34e1b 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/CommonDetModelEnum.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/CommonDetModelEnum.java @@ -3,11 +3,16 @@ package cn.smartjavaai.ocr.enums; /** * OCR检测模型枚举 * @author dwj - * @date 2025/4/4 */ public enum CommonDetModelEnum { - PADDLEOCR_V5_DET_MODEL; + PP_OCR_V5_SERVER_DET_MODEL, + + PP_OCR_V5_MOBILE_DET_MODEL, + + PP_OCR_V4_SERVER_DET_MODEL, + + PP_OCR_V4_MOBILE_DET_MODEL; /** diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/CommonRecModelEnum.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/CommonRecModelEnum.java index 2348dcf..f3dc0b8 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/CommonRecModelEnum.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/CommonRecModelEnum.java @@ -3,11 +3,16 @@ package cn.smartjavaai.ocr.enums; /** * OCR识别模型枚举 * @author dwj - * @date 2025/4/4 */ public enum CommonRecModelEnum { - PADDLEOCR_V5_REC_MODEL; + PP_OCR_V5_SERVER_REC_MODEL, + + PP_OCR_V5_MOBILE_REC_MODEL, + + PP_OCR_V4_SERVER_REC_MODEL, + + PP_OCR_V4_MOBILE_REC_MODEL; /** diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/DirectionModelEnum.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/DirectionModelEnum.java index a7f50c5..17afe4c 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/DirectionModelEnum.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/DirectionModelEnum.java @@ -7,7 +7,12 @@ package cn.smartjavaai.ocr.enums; */ public enum DirectionModelEnum { - CH_PPOCR_MOBILE_V2_CLS; + CH_PPOCR_MOBILE_V2_CLS, + + PP_LCNET_X0_25, + + PP_LCNET_X1_0; + /** diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/TableStructureModelEnum.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/TableStructureModelEnum.java new file mode 100644 index 0000000..795e17a --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/enums/TableStructureModelEnum.java @@ -0,0 +1,30 @@ +package cn.smartjavaai.ocr.enums; + +/** + * OCR表格结构模型枚举 + * @author dwj + */ +public enum TableStructureModelEnum { + + SLANET, + //SLANEXT_WIRED, + SLANET_PLUS; + + + + + /** + * 根据名称获取枚举 (忽略大小写和下划线变体) + */ + public static TableStructureModelEnum fromName(String name) { + String formatted = name.trim().toUpperCase().replaceAll("[-_]", ""); + for (TableStructureModelEnum model : values()) { + if (model.name().replaceAll("_", "").equals(formatted)) { + return model; + } + } + throw new IllegalArgumentException("未知模型名称: " + name); + } + + +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/factory/OcrModelFactory.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/factory/OcrModelFactory.java index 4552092..7f79255 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/factory/OcrModelFactory.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/factory/OcrModelFactory.java @@ -3,14 +3,17 @@ package cn.smartjavaai.ocr.factory; import cn.smartjavaai.common.config.Config; import cn.smartjavaai.ocr.config.DirectionModelConfig; import cn.smartjavaai.ocr.config.OcrRecModelConfig; +import cn.smartjavaai.ocr.enums.CommonDetModelEnum; +import cn.smartjavaai.ocr.enums.CommonRecModelEnum; +import cn.smartjavaai.ocr.enums.DirectionModelEnum; import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; -import cn.smartjavaai.ocr.model.common.detect.PpOCRV5DetModel; -import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModelImpl; import cn.smartjavaai.ocr.config.OcrDetModelConfig; import cn.smartjavaai.ocr.exception.OcrException; import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel; -import cn.smartjavaai.ocr.model.common.direction.PPOCRMobileV2Model; -import cn.smartjavaai.ocr.model.common.recognize.PpOCRV5RecModel; +import cn.smartjavaai.ocr.model.common.direction.PPOCRMobileV2ClsModel; +import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel; +import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModelImpl; import lombok.extern.slf4j.Slf4j; import java.util.Map; @@ -27,29 +30,29 @@ public class OcrModelFactory { // 使用 volatile 和双重检查锁定来确保线程安全的单例模式 private static volatile OcrModelFactory instance; - private static final ConcurrentHashMap commonDetModelMap = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap commonDetModelMap = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap commonRecModelMap = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap commonRecModelMap = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap directionModelMap = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap directionModelMap = new ConcurrentHashMap<>(); /** * 检测模型注册表 */ - private static final Map> commonDetRegistry = + private static final Map> commonDetRegistry = new ConcurrentHashMap<>(); /** * 识别模型注册表 */ - private static final Map> commonRecRegistry = + private static final Map> commonRecRegistry = new ConcurrentHashMap<>(); /** * 方向分类模型注册表 */ - private static final Map> directionRegistry = + private static final Map> directionRegistry = new ConcurrentHashMap<>(); @@ -68,29 +71,29 @@ public class OcrModelFactory { /** * 注册通用检测模型 - * @param name + * @param detModelEnum * @param clazz */ - private static void registerCommonDetModel(String name, Class clazz) { - commonDetRegistry.put(name.toLowerCase(), clazz); + private static void registerCommonDetModel(CommonDetModelEnum detModelEnum, Class clazz) { + commonDetRegistry.put(detModelEnum, clazz); } /** * 注册通用识别模型 - * @param name + * @param recModelEnum * @param clazz */ - private static void registerCommonRecModel(String name, Class clazz) { - commonRecRegistry.put(name.toLowerCase(), clazz); + private static void registerCommonRecModel(CommonRecModelEnum recModelEnum, Class clazz) { + commonRecRegistry.put(recModelEnum, clazz); } /** * 注册通用方向分类模型 - * @param name + * @param directionModelEnum * @param clazz */ - private static void registerDirectionModel(String name, Class clazz) { - directionRegistry.put(name.toLowerCase(), clazz); + private static void registerDirectionModel(DirectionModelEnum directionModelEnum, Class clazz) { + directionRegistry.put(directionModelEnum, clazz); } @@ -103,7 +106,7 @@ public class OcrModelFactory { if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){ throw new OcrException("未配置OCR模型"); } - return commonDetModelMap.computeIfAbsent(config.getModelEnum().name(), k -> { + return commonDetModelMap.computeIfAbsent(config.getModelEnum(), k -> { return createCommonDetModel(config); }); } @@ -117,7 +120,7 @@ public class OcrModelFactory { if(Objects.isNull(config) || Objects.isNull(config.getRecModelEnum())){ throw new OcrException("未配置OCR模型"); } - return commonRecModelMap.computeIfAbsent(config.getRecModelEnum().name(), k -> { + return commonRecModelMap.computeIfAbsent(config.getRecModelEnum(), k -> { return createCommonRecModel(config); }); } @@ -131,7 +134,7 @@ public class OcrModelFactory { if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){ throw new OcrException("未配置OCR模型"); } - return directionModelMap.computeIfAbsent(config.getModelEnum().name(), k -> { + return directionModelMap.computeIfAbsent(config.getModelEnum(), k -> { return createDirectionModel(config); }); } @@ -144,7 +147,7 @@ public class OcrModelFactory { * @return */ private OcrCommonDetModel createCommonDetModel(OcrDetModelConfig config) { - Class clazz = commonDetRegistry.get(config.getModelEnum().name().toLowerCase()); + Class clazz = commonDetRegistry.get(config.getModelEnum()); if(clazz == null){ throw new OcrException("Unsupported model"); } @@ -165,7 +168,7 @@ public class OcrModelFactory { * @return */ private OcrCommonRecModel createCommonRecModel(OcrRecModelConfig config) { - Class clazz = commonRecRegistry.get(config.getRecModelEnum().name().toLowerCase()); + Class clazz = commonRecRegistry.get(config.getRecModelEnum()); if(clazz == null){ throw new OcrException("Unsupported model"); } @@ -185,7 +188,7 @@ public class OcrModelFactory { * @return */ private OcrDirectionModel createDirectionModel(DirectionModelConfig config) { - Class clazz = directionRegistry.get(config.getModelEnum().name().toLowerCase()); + Class clazz = directionRegistry.get(config.getModelEnum()); if(clazz == null){ throw new OcrException("Unsupported model"); } @@ -202,9 +205,18 @@ public class OcrModelFactory { // 初始化默认算法 static { - registerCommonDetModel("PADDLEOCR_V5_DET_MODEL", PpOCRV5DetModel.class); - registerCommonRecModel("PADDLEOCR_V5_REC_MODEL", PpOCRV5RecModel.class); - registerDirectionModel("CH_PPOCR_MOBILE_V2_CLS", PPOCRMobileV2Model.class); + //通用-检测模型 + registerCommonDetModel(CommonDetModelEnum.PP_OCR_V5_SERVER_DET_MODEL, OcrCommonDetModelImpl.class); + registerCommonDetModel(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL, OcrCommonDetModelImpl.class); + registerCommonDetModel(CommonDetModelEnum.PP_OCR_V4_SERVER_DET_MODEL, OcrCommonDetModelImpl.class); + registerCommonDetModel(CommonDetModelEnum.PP_OCR_V4_MOBILE_DET_MODEL, OcrCommonDetModelImpl.class); + registerCommonRecModel(CommonRecModelEnum.PP_OCR_V5_SERVER_REC_MODEL, OcrCommonRecModelImpl.class); + registerCommonRecModel(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL, OcrCommonRecModelImpl.class); + registerCommonRecModel(CommonRecModelEnum.PP_OCR_V4_SERVER_REC_MODEL, OcrCommonRecModelImpl.class); + registerCommonRecModel(CommonRecModelEnum.PP_OCR_V4_MOBILE_REC_MODEL, OcrCommonRecModelImpl.class); + registerDirectionModel(DirectionModelEnum.CH_PPOCR_MOBILE_V2_CLS, PPOCRMobileV2ClsModel.class); + registerDirectionModel(DirectionModelEnum.PP_LCNET_X0_25, PPOCRMobileV2ClsModel.class); + registerDirectionModel(DirectionModelEnum.PP_LCNET_X1_0, PPOCRMobileV2ClsModel.class); log.debug("缓存目录:{}", Config.getCachePath()); } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/factory/TableRecModelFactory.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/factory/TableRecModelFactory.java new file mode 100644 index 0000000..9723173 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/factory/TableRecModelFactory.java @@ -0,0 +1,119 @@ +package cn.smartjavaai.ocr.factory; + +import cn.smartjavaai.common.config.Config; +import cn.smartjavaai.ocr.config.DirectionModelConfig; +import cn.smartjavaai.ocr.config.OcrDetModelConfig; +import cn.smartjavaai.ocr.config.OcrRecModelConfig; +import cn.smartjavaai.ocr.config.TableStructureConfig; +import cn.smartjavaai.ocr.enums.CommonDetModelEnum; +import cn.smartjavaai.ocr.enums.CommonRecModelEnum; +import cn.smartjavaai.ocr.enums.DirectionModelEnum; +import cn.smartjavaai.ocr.enums.TableStructureModelEnum; +import cn.smartjavaai.ocr.exception.OcrException; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModelImpl; +import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel; +import cn.smartjavaai.ocr.model.common.direction.PPOCRMobileV2ClsModel; +import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel; +import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModelImpl; +import cn.smartjavaai.ocr.model.table.CommonTableStructureModel; +import cn.smartjavaai.ocr.model.table.TableStructureModel; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * OCR 表格识别模型工厂 + * @author dwj + */ +@Slf4j +public class TableRecModelFactory { + + // 使用 volatile 和双重检查锁定来确保线程安全的单例模式 + private static volatile TableRecModelFactory instance; + + /** + * 模型缓存 + */ + private static final ConcurrentHashMap tableStructureModelMap = new ConcurrentHashMap<>(); + + + /** + * 模型注册表 + */ + private static final Map> tableStructureRegistry = + new ConcurrentHashMap<>(); + + + public static TableRecModelFactory getInstance() { + if (instance == null) { + synchronized (TableRecModelFactory.class) { + if (instance == null) { + instance = new TableRecModelFactory(); + } + } + } + return instance; + } + + + + /** + * 注册模型 + * @param tableStructureModelEnum + * @param clazz + */ + private static void registerTableStructureModel(TableStructureModelEnum tableStructureModelEnum, Class clazz) { + tableStructureRegistry.put(tableStructureModelEnum, clazz); + } + + + /** + * 获取模型(通过配置) + * @param config + * @return + */ + public TableStructureModel getTableStructureModel(TableStructureConfig config) { + if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){ + throw new OcrException("未配置OCR模型"); + } + return tableStructureModelMap.computeIfAbsent(config.getModelEnum(), k -> { + return createTableStructureModel(config); + }); + } + + + + /** + * 创建模型 + * @param config + * @return + */ + private TableStructureModel createTableStructureModel(TableStructureConfig config) { + Class clazz = tableStructureRegistry.get(config.getModelEnum()); + if(clazz == null){ + throw new OcrException("Unsupported model"); + } + TableStructureModel model = null; + try { + model = (TableStructureModel) clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new OcrException(e); + } + model.loadModel(config); + return model; + } + + + + + // 初始化默认算法 + static { + registerTableStructureModel(TableStructureModelEnum.SLANET, CommonTableStructureModel.class); + registerTableStructureModel(TableStructureModelEnum.SLANET_PLUS, CommonTableStructureModel.class); + log.debug("缓存目录:{}", Config.getCachePath()); + } + +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/OcrCommonDetModel.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/OcrCommonDetModel.java index b28f232..aa29177 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/OcrCommonDetModel.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/OcrCommonDetModel.java @@ -29,8 +29,6 @@ public interface OcrCommonDetModel extends AutoCloseable{ } - - /** * 文本检测 * @param image BufferedImage @@ -77,4 +75,23 @@ public interface OcrCommonDetModel extends AutoCloseable{ throw new UnsupportedOperationException("默认不支持该功能"); } + /** + * 文本检测(批量) + * @param imageList BufferedImage + * @return + */ + default List> batchDetect(List imageList) { + throw new UnsupportedOperationException("默认不支持该功能"); + } + + /** + * 文本检测(批量) + * @param imageList DJL Image + * @return + */ + default List> batchDetectDJLImage(List imageList){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + + } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/PpOCRV5DetModel.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/OcrCommonDetModelImpl.java similarity index 81% rename from smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/PpOCRV5DetModel.java rename to smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/OcrCommonDetModelImpl.java index a09fee4..648c68c 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/PpOCRV5DetModel.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/OcrCommonDetModelImpl.java @@ -1,20 +1,16 @@ package cn.smartjavaai.ocr.model.common.detect; -import ai.djl.Device; import ai.djl.MalformedModelException; import ai.djl.engine.Engine; import ai.djl.inference.Predictor; import ai.djl.modality.cv.Image; import ai.djl.modality.cv.ImageFactory; -import ai.djl.modality.cv.output.DetectedObjects; import ai.djl.ndarray.NDList; import ai.djl.ndarray.NDManager; import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.ModelNotFoundException; import ai.djl.repository.zoo.ModelZoo; import ai.djl.repository.zoo.ZooModel; -import ai.djl.training.util.ProgressBar; -import cn.smartjavaai.common.enums.DeviceEnum; import cn.smartjavaai.common.pool.PredictorFactory; import cn.smartjavaai.common.utils.FileUtils; import cn.smartjavaai.common.utils.ImageUtils; @@ -22,7 +18,7 @@ import cn.smartjavaai.common.utils.OpenCVUtils; import cn.smartjavaai.ocr.config.OcrDetModelConfig; import cn.smartjavaai.ocr.entity.OcrBox; import cn.smartjavaai.ocr.exception.OcrException; -import cn.smartjavaai.ocr.model.common.detect.translator.PPOCRV5DetTranslator; +import cn.smartjavaai.ocr.model.common.detect.criteria.OcrCommonDetCriterialFactory; import cn.smartjavaai.ocr.utils.OcrUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -38,18 +34,14 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; +import java.util.*; /** - * PPOCRV5 检测模型 + * ocr通用检测模型实现类 * @author dwj - * @date 2025/4/21 */ @Slf4j -public class PpOCRV5DetModel implements OcrCommonDetModel { - +public class OcrCommonDetModelImpl implements OcrCommonDetModel{ private ObjectPool> detPredictorPool; @@ -62,22 +54,9 @@ public class PpOCRV5DetModel implements OcrCommonDetModel { if(StringUtils.isBlank(config.getDetModelPath())){ throw new OcrException("modelPath is null"); } - Device device = null; - if(!Objects.isNull(config.getDevice())){ - device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(); - } this.config = config; //初始化 检测Criteria - Criteria detCriteria = - Criteria.builder() - .optEngine("OnnxRuntime") - .setTypes(Image.class, NDList.class) - .optModelPath(Paths.get(config.getDetModelPath())) - .optTranslator(new PPOCRV5DetTranslator(new ConcurrentHashMap())) - .optDevice(device) - .optProgress(new ProgressBar()) - .build(); - + Criteria detCriteria = OcrCommonDetCriterialFactory.createCriteria(config); try{ detectionModel = ModelZoo.loadModel(detCriteria); // 创建池子:每个线程独享 Predictor @@ -107,28 +86,9 @@ public class PpOCRV5DetModel implements OcrCommonDetModel { @Override public List detect(Image image){ - Predictor predictor = null; - try (NDManager manager = NDManager.newBaseManager()) { - predictor = detPredictorPool.borrowObject(); - NDList result = predictor.predict(image); - result.attach(manager); - return OcrUtils.convertToOcrBox(result, image); - } catch (Exception e) { - throw new OcrException("OCR检测错误", e); - }finally { - if (predictor != null) { - try { - detPredictorPool.returnObject(predictor); //归还 - } catch (Exception e) { - log.warn("归还Predictor失败", e); - try { - predictor.close(); // 归还失败才销毁 - } catch (Exception ex) { - log.error("关闭Predictor失败", ex); - } - } - } - } + List imageList = Collections.singletonList(image); + List> result = batchDetectDJLImage(imageList); + return result.get(0); } @Override @@ -136,7 +96,7 @@ public class PpOCRV5DetModel implements OcrCommonDetModel { if(!FileUtils.isFileExists(imagePath)){ throw new OcrException("图像文件不存在"); } - try (NDManager manager = NDManager.newBaseManager()) { + try { Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); List boxList = detect(img); if(Objects.isNull(boxList) || boxList.isEmpty()){ @@ -194,10 +154,58 @@ public class PpOCRV5DetModel implements OcrCommonDetModel { img.save(outputStream, "png"); // 将字节流转换为 BufferedImage byte[] imageBytes = outputStream.toByteArray(); - ((Mat) img.getWrappedImage()).release(); return ImageIO.read(new ByteArrayInputStream(imageBytes)); } catch (IOException e) { throw new OcrException("导出图片失败", e); + } finally { + if (img != null){ + ((Mat) img.getWrappedImage()).release(); + } + } + } + + @Override + public List> batchDetect(List imageList) { + List djlImageList = new ArrayList<>(imageList.size()); + try { + for (BufferedImage bufferedImage : imageList) { + djlImageList.add(ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(bufferedImage))); + } + return batchDetectDJLImage(djlImageList); + } catch (Exception e) { + throw new OcrException(e); + } finally { + djlImageList.forEach(image -> ((Mat)image.getWrappedImage()).release()); + } + } + + @Override + public List> batchDetectDJLImage(List imageList) { + if(!ImageUtils.isAllImageSizeEqual(imageList)){ + throw new OcrException("图片尺寸不一致"); + } + Predictor predictor = null; + try (NDManager manager = NDManager.newBaseManager()) { + predictor = detPredictorPool.borrowObject(); + List result = predictor.batchPredict(imageList); + result.forEach(ndList -> ndList.attach(manager)); + return OcrUtils.convertToOcrBox(result); + } catch (Exception e) { + throw new OcrException("OCR检测错误", e); + }finally { + if (predictor != null) { + try { + detPredictorPool.returnObject(predictor); //归还 + } catch (Exception e) { + log.warn("归还Predictor失败", e); + try { + predictor.close(); // 归还失败才销毁 + } catch (Exception ex) { + log.error("关闭Predictor失败", ex); + } + } + } + } } @@ -218,4 +226,6 @@ public class PpOCRV5DetModel implements OcrCommonDetModel { log.warn("关闭 model 失败", e); } } + + } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/criteria/OcrCommonDetCriterialFactory.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/criteria/OcrCommonDetCriterialFactory.java new file mode 100644 index 0000000..74a6059 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/criteria/OcrCommonDetCriterialFactory.java @@ -0,0 +1,53 @@ +package cn.smartjavaai.ocr.model.common.detect.criteria; + +import ai.djl.Device; +import ai.djl.modality.cv.Image; +import ai.djl.ndarray.NDList; +import ai.djl.repository.zoo.Criteria; +import ai.djl.training.util.ProgressBar; +import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.ocr.config.OcrDetModelConfig; +import cn.smartjavaai.ocr.enums.CommonDetModelEnum; +import cn.smartjavaai.ocr.model.common.detect.translator.PPOCRDetTranslator; +import org.apache.commons.lang3.StringUtils; + +import java.nio.file.Paths; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author dwj + * @date 2025/7/8 + */ +public class OcrCommonDetCriterialFactory { + + + public static Criteria createCriteria(OcrDetModelConfig config) { + Device device = null; + if(!Objects.isNull(config.getDevice())){ + device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(); + } + Criteria criteria = null; + ConcurrentHashMap params = new ConcurrentHashMap(); + params.putAll(config.getCustomParams()); + if(StringUtils.isNotBlank(config.getBatchifier())){ + params.put("batchifier", config.getBatchifier()); + } + if(config.getModelEnum() == CommonDetModelEnum.PP_OCR_V5_SERVER_DET_MODEL || + config.getModelEnum() == CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL || + config.getModelEnum() == CommonDetModelEnum.PP_OCR_V4_SERVER_DET_MODEL || + config.getModelEnum() == CommonDetModelEnum.PP_OCR_V4_MOBILE_DET_MODEL + ){ + criteria = + Criteria.builder() + .optEngine("OnnxRuntime") + .setTypes(Image.class, NDList.class) + .optModelPath(Paths.get(config.getDetModelPath())) + .optTranslator(new PPOCRDetTranslator(params)) + .optDevice(device) + .optProgress(new ProgressBar()) + .build(); + } + return criteria; + } +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/translator/PPOCRV5DetTranslator.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/translator/PPOCRDetTranslator.java similarity index 95% rename from smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/translator/PPOCRV5DetTranslator.java rename to smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/translator/PPOCRDetTranslator.java index 9725561..23476d0 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/translator/PPOCRV5DetTranslator.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/detect/translator/PPOCRDetTranslator.java @@ -27,24 +27,35 @@ import java.util.Map; * @mail 179209347@qq.com * @website www.aias.top */ -public class PPOCRV5DetTranslator implements Translator { +public class PPOCRDetTranslator implements Translator { // det_algorithm == "DB" private final float thresh = 0.3f; private final boolean use_dilation = false; private final String score_mode = "fast"; private final String box_type = "quad"; + //检测的图像边长限制 private final int limit_side_len; + //输出的最大文本框数量 private final int max_candidates; + //文本框最小尺寸阈值 private final int min_size; + //文本框的分数阈值 private final float box_thresh; + + /** + * 这个参数是检测后处理时控制文本框大小的,默认1.6,可以尝试改成2.5或者更大,反之,如果觉得文本框不够紧凑,也可以把该参数调小。 + * 检测框大小过于紧贴文字或检测框过大,可以调整db_unclip_ratio这个参数,加大参数可以扩大检测框,减小参数可以减小检测框大小; + */ private final float unclip_ratio; private float ratio_h; private float ratio_w; private int img_height; private int img_width; - public PPOCRV5DetTranslator(Map arguments) { + private String batchifier; + + public PPOCRDetTranslator(Map arguments) { limit_side_len = arguments.containsKey("limit_side_len") ? Integer.parseInt(arguments.get("limit_side_len").toString()) @@ -65,6 +76,10 @@ public class PPOCRV5DetTranslator implements Translator { arguments.containsKey("unclip_ratio") ? Float.parseFloat(arguments.get("unclip_ratio").toString()) : 1.6f; + + batchifier = arguments.containsKey("batchifier") + ? arguments.get("batchifier").toString() + : "stack"; } @Override @@ -509,13 +524,13 @@ public class PPOCRV5DetTranslator implements Translator { new float[]{0.485f, 0.456f, 0.406f}, new float[]{0.229f, 0.224f, 0.225f}); - img = img.expandDims(0); +// img = img.expandDims(0); return new NDList(img); } @Override public Batchifier getBatchifier() { - return null; + return Batchifier.fromString(batchifier); } } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/OcrDirectionModel.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/OcrDirectionModel.java index c2f45c2..ef2e8a4 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/OcrDirectionModel.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/OcrDirectionModel.java @@ -8,6 +8,7 @@ import cn.smartjavaai.ocr.entity.DirectionInfo; import cn.smartjavaai.ocr.entity.OcrBox; import cn.smartjavaai.ocr.entity.OcrInfo; import cn.smartjavaai.ocr.entity.OcrItem; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; import org.opencv.core.Mat; import java.awt.image.BufferedImage; @@ -19,6 +20,14 @@ import java.util.List; */ public interface OcrDirectionModel extends AutoCloseable{ + default void setTextDetModel(OcrCommonDetModel detModel){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + + default OcrCommonDetModel getTextDetModel(){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + /** * 加载模型 * @param config @@ -71,7 +80,11 @@ public interface OcrDirectionModel extends AutoCloseable{ * @param manager * @return */ - default List detect(List boxList, Mat srcMat, NDManager manager) { + default List detect(List boxList, Mat srcMat) { + throw new UnsupportedOperationException("默认不支持该功能"); + } + + default List> batchDetect(List> boxList, List srcMatList) { throw new UnsupportedOperationException("默认不支持该功能"); } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/PPOCRMobileV2Model.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/PPOCRMobileV2ClsModel.java similarity index 51% rename from smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/PPOCRMobileV2Model.java rename to smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/PPOCRMobileV2ClsModel.java index 37c4e2d..483ada0 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/PPOCRMobileV2Model.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/PPOCRMobileV2ClsModel.java @@ -6,9 +6,7 @@ import ai.djl.engine.Engine; import ai.djl.inference.Predictor; import ai.djl.modality.cv.Image; import ai.djl.modality.cv.ImageFactory; -import ai.djl.modality.cv.output.Point; import ai.djl.ndarray.NDManager; -import ai.djl.opencv.OpenCVImageFactory; import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.ModelNotFoundException; import ai.djl.repository.zoo.ModelZoo; @@ -20,17 +18,15 @@ import cn.smartjavaai.common.utils.FileUtils; import cn.smartjavaai.common.utils.ImageUtils; import cn.smartjavaai.common.utils.OpenCVUtils; import cn.smartjavaai.ocr.config.DirectionModelConfig; -import cn.smartjavaai.ocr.config.OcrDetModelConfig; import cn.smartjavaai.ocr.entity.*; import cn.smartjavaai.ocr.enums.AngleEnum; import cn.smartjavaai.ocr.exception.OcrException; -import cn.smartjavaai.ocr.factory.OcrModelFactory; import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; +import cn.smartjavaai.ocr.model.common.direction.criteria.DirectionCriteriaFactory; import cn.smartjavaai.ocr.model.common.direction.translator.PpWordRotateTranslator; -import cn.smartjavaai.ocr.opencv.OcrNDArrayUtils; -import cn.smartjavaai.ocr.opencv.OcrOpenCVUtils; import cn.smartjavaai.ocr.utils.OcrUtils; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; @@ -45,9 +41,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** * PPOCRMobileV2Model 方向分类模型 @@ -55,38 +52,34 @@ import java.util.UUID; * @date 2025/4/21 */ @Slf4j -public class PPOCRMobileV2Model implements OcrDirectionModel { +public class PPOCRMobileV2ClsModel implements OcrDirectionModel { private ObjectPool> predictorPool; private DirectionModelConfig config; - private OcrCommonDetModel detModel; - private ZooModel model; + private OcrCommonDetModel textDetModel; + @Override public void loadModel(DirectionModelConfig config){ if(StringUtils.isBlank(config.getModelPath())){ throw new OcrException("modelPath is null"); } - this.config = config; + this.textDetModel = config.getTextDetModel(); Device device = null; if(!Objects.isNull(config.getDevice())){ device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(); } - Criteria criteria = - Criteria.builder() - .optEngine("OnnxRuntime") - .setTypes(Image.class, DirectionInfo.class) - .optModelPath(Paths.get(config.getModelPath())) - .optDevice(device) - .optTranslator(new PpWordRotateTranslator()) - .optProgress(new ProgressBar()) - .build(); + ConcurrentHashMap params = new ConcurrentHashMap(); + if(StringUtils.isNotBlank(config.getBatchifier())){ + params.put("batchifier", config.getBatchifier()); + } + Criteria criteria = DirectionCriteriaFactory.createCriteria(config); try{ model = ModelZoo.loadModel(criteria); // 创建池子:每个线程独享 Predictor @@ -96,15 +89,6 @@ public class PPOCRMobileV2Model implements OcrDirectionModel { } catch (IOException | ModelNotFoundException | MalformedModelException e) { throw new OcrException("模型加载失败", e); } - - //获取检测模型 - if(StringUtils.isNotBlank(config.getDetModelPath()) && Objects.nonNull(config.getDetModelEnum())){ - OcrDetModelConfig detModelConfig = new OcrDetModelConfig(); - detModelConfig.setModelEnum(config.getDetModelEnum()); - detModelConfig.setDetModelPath(config.getDetModelPath()); - detModelConfig.setDevice(config.getDevice()); - detModel = OcrModelFactory.getInstance().getDetModel(detModelConfig); - } } @Override @@ -115,124 +99,85 @@ public class PPOCRMobileV2Model implements OcrDirectionModel { Image img = null; try { img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); + return detect(img); } catch (IOException e) { throw new OcrException("无效的图片", e); + }finally { + if(img != null){ + ((Mat)img.getWrappedImage()).release(); + } } - List ocrItemList = detect(img); - ((Mat)img.getWrappedImage()).release(); - return ocrItemList; } @Override public List detect(Image image){ + if(Objects.isNull(textDetModel)){ + throw new OcrException("textDetModel is null"); + } //检测文本 - List boxeList = detModel.detect(image); + List boxeList = textDetModel.detect(image); if(Objects.isNull(boxeList) || boxeList.isEmpty()){ throw new OcrException("未检测到文本"); } - Predictor predictor = null; - List ocrItemList = new ArrayList<>(); - try (NDManager manager = NDManager.newBaseManager()) { - Mat srcMat = (Mat) image.getWrappedImage(); - predictor = predictorPool.borrowObject(); - for (OcrBox box : boxeList){ - OcrItem ocrItem = detect(box, srcMat, predictor, manager); - ocrItemList.add(ocrItem); - } - return ocrItemList; - } catch (Exception e) { - throw new OcrException("OCR检测错误", e); - }finally { - if (predictor != null) { - try { - predictorPool.returnObject(predictor); //归还 - } catch (Exception e) { - log.warn("归还Predictor失败", e); - try { - predictor.close(); // 归还失败才销毁 - } catch (Exception ex) { - log.error("关闭Predictor失败", ex); - } - } - } - } + Mat srcMat = (Mat) image.getWrappedImage(); + return detect(boxeList, srcMat); } - /** - * 基于文本框检测方向 - * @param box - * @param srcMat - * @param predictor - * @param manager - * @return - */ - private OcrItem detect(OcrBox box, Mat srcMat, Predictor predictor, NDManager manager){ - if(Objects.isNull(box)){ - throw new OcrException("box参数为空"); - } - try { - //透视变换及裁剪 - Image subImg = OcrUtils.transformAndCrop(srcMat, box); - DirectionInfo directionInfo = null; - String angle; - //高宽比 > 1.5 纵向 - if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { - //旋转图片90度 - subImg = OcrUtils.rotateImg(manager, subImg); - //检测方向 - directionInfo = predictor.predict(subImg); - if (directionInfo.getName().equalsIgnoreCase("Rotate")) { - angle = "270"; - } else { - angle = "90"; - } - }else{ //横向 - directionInfo = predictor.predict(subImg); - if (directionInfo.getName().equalsIgnoreCase("No Rotate")) { - angle = "0"; - } else { - angle = "180"; - } - } - ((Mat)subImg.getWrappedImage()).release(); - return new OcrItem(box, AngleEnum.fromValue(angle), directionInfo.getProb().floatValue()); - } catch (Exception e) { - throw new OcrException("OCR检测错误", e); - } - } +// /** +// * 基于文本框检测方向 +// * @param box +// * @param srcMat +// * @param predictor +// * @param manager +// * @return +// */ +// private OcrItem detect(OcrBox box, Mat srcMat, Predictor predictor, NDManager manager){ +// if(Objects.isNull(box)){ +// throw new OcrException("box参数为空"); +// } +// try { +// //透视变换及裁剪 +// Image subImg = OcrUtils.transformAndCrop(srcMat, box); +// DirectionInfo directionInfo = null; +// String angle; +// //高宽比 > 1.5 纵向 +// if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { +// //旋转图片90度 +// subImg = OcrUtils.rotateImg(manager, subImg); +// //检测方向 +// directionInfo = predictor.predict(subImg); +// if (directionInfo.getName().equalsIgnoreCase("Rotate")) { +// angle = "270"; +// } else { +// angle = "90"; +// } +// }else{ //横向 +// directionInfo = predictor.predict(subImg); +// if (directionInfo.getName().equalsIgnoreCase("No Rotate")) { +// angle = "0"; +// } else { +// angle = "180"; +// } +// } +// ((Mat)subImg.getWrappedImage()).release(); +// return new OcrItem(box, AngleEnum.fromValue(angle), directionInfo.getProb().floatValue()); +// } catch (Exception e) { +// throw new OcrException("OCR检测错误", e); +// } +// } @Override - public List detect(List boxList,Mat srcMat,NDManager manager){ + public List detect(List boxList,Mat srcMat){ if(Objects.isNull(boxList) || boxList.isEmpty()){ throw new OcrException("boxList为空"); } - Predictor predictor = null; - List ocrItemList = new ArrayList<>(); - try { - predictor = predictorPool.borrowObject(); - for (OcrBox box : boxList){ - OcrItem ocrItem = detect(box, srcMat, predictor, manager); - ocrItemList.add(ocrItem); - } - } catch (Exception e) { - throw new OcrException("OCR检测错误", e); - }finally { - if (predictor != null) { - try { - predictorPool.returnObject(predictor); //归还 - } catch (Exception e) { - log.warn("归还Predictor失败", e); - try { - predictor.close(); // 归还失败才销毁 - } catch (Exception ex) { - log.error("关闭Predictor失败", ex); - } - } - } + List> ocrItemList = batchDetect(Collections.singletonList(boxList), Collections.singletonList(srcMat)); + if(Objects.isNull(ocrItemList) || ocrItemList.isEmpty()){ + throw new OcrException("方向检测失败"); } - return ocrItemList; + return ocrItemList.get(0); } @Override @@ -240,8 +185,9 @@ public class PPOCRMobileV2Model implements OcrDirectionModel { if(!FileUtils.isFileExists(imagePath)){ throw new OcrException("图像文件不存在"); } - try (NDManager manager = NDManager.newBaseManager()) { - Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); + Image img = null; + try { + img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); List itemList = detect(img); if(Objects.isNull(itemList) || itemList.isEmpty()){ throw new OcrException("未检测到文字"); @@ -250,9 +196,12 @@ public class PPOCRMobileV2Model implements OcrDirectionModel { Path output = Paths.get(outputPath); log.debug("Saving to {}", output.toAbsolutePath().toString()); img.save(Files.newOutputStream(output), "png"); - ((Mat) img.getWrappedImage()).release(); } catch (IOException e) { throw new OcrException(e); + } finally { + if (img != null){ + ((Mat)img.getWrappedImage()).release(); + } } } @@ -297,13 +246,129 @@ public class PPOCRMobileV2Model implements OcrDirectionModel { img.save(outputStream, "png"); // 将字节流转换为 BufferedImage byte[] imageBytes = outputStream.toByteArray(); - ((Mat) img.getWrappedImage()).release(); return ImageIO.read(new ByteArrayInputStream(imageBytes)); } catch (IOException e) { throw new OcrException("导出图片失败", e); + } finally { + if (img != null){ + ((Mat) img.getWrappedImage()).release(); + } } } + @Override + public List> batchDetect(List> boxList, List srcMatList) { + if(CollectionUtils.isEmpty(boxList)){ + throw new OcrException("boxList 不能为空"); + } + if(CollectionUtils.isEmpty(srcMatList)){ + throw new OcrException("srcMatList 不能为空"); + } + //检查参数 + for (int i = 0; i < srcMatList.size(); i++) { + List ocrBoxes = boxList.get(i); + Mat mat = srcMatList.get(i); + if (ocrBoxes == null) { + throw new OcrException("第 " + i + " 个 boxList 为 null"); + } + if (ocrBoxes.isEmpty()) { + throw new OcrException("第 " + i + " 个 boxList 没有检测结果"); + } + if (mat.empty()) { + throw new OcrException("第 " + i + " 张图片为空 Mat"); + } + } + List imageList = new ArrayList(); + List isRotatedList = new ArrayList(); + int index = 0; + try (NDManager manager = model.getNDManager().newSubManager()){ + for(int i = 0; i < srcMatList.size(); i++){ + for (int j = 0; j < boxList.get(i).size(); j++){ + //透视变换及裁剪 + Image subImg = OcrUtils.transformAndCrop(srcMatList.get(i), boxList.get(i).get(j)); + //高宽比 > 1.5 纵向 + if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { + //旋转图片90度 + subImg = OcrUtils.rotateImg(manager, subImg); + isRotatedList.add(true); + imageList.add(subImg); + }else{ + isRotatedList.add(false); + imageList.add(subImg); + } + index++; + } + } + List> result = new ArrayList<>(); + List directionInfos = batchDetect(imageList); + if(CollectionUtils.isEmpty(directionInfos)){ + throw new OcrException("方向检测失败"); + } + index = 0; + for(int i = 0; i < srcMatList.size(); i++){ + List ocrItemList = new ArrayList<>(); + for (int j = 0; j < boxList.get(i).size(); j++){ + DirectionInfo directionInfo = directionInfos.get(index); + if(Objects.isNull(directionInfo)){ + throw new OcrException("方向检测失败: 第" + i + "张图片, 第" + j + "个文本块,未检测到方向"); + } + String angle; + if(isRotatedList.get(index)){ + if (directionInfo.getName().equalsIgnoreCase("Rotate")) { + angle = "270"; + } else { + angle = "90"; + } + }else{ + if (directionInfo.getName().equalsIgnoreCase("No Rotate")) { + angle = "0"; + } else { + angle = "180"; + } + } + OcrItem ocrItem = new OcrItem(boxList.get(i).get(j), AngleEnum.fromValue(angle), directionInfo.getProb().floatValue()); + ocrItemList.add(ocrItem); + index++; + } + result.add(ocrItemList); + } + return result; + } + } + + private List batchDetect(List imageList) { + Predictor predictor = null; + try { + predictor = predictorPool.borrowObject(); + return predictor.batchPredict(imageList); + } catch (Exception e) { + throw new OcrException("OCR检测错误", e); + }finally { + if (predictor != null) { + try { + predictorPool.returnObject(predictor); //归还 + } catch (Exception e) { + log.warn("归还Predictor失败", e); + try { + predictor.close(); // 归还失败才销毁 + } catch (Exception ex) { + log.error("关闭Predictor失败", ex); + } + } + } + } + } + + @Override + public void setTextDetModel(OcrCommonDetModel detModel) { + this.textDetModel = detModel; + } + + @Override + public OcrCommonDetModel getTextDetModel() { + return textDetModel; + } + @Override public void close() throws Exception { try { @@ -320,12 +385,5 @@ public class PPOCRMobileV2Model implements OcrDirectionModel { } catch (Exception e) { log.warn("关闭 model 失败", e); } - try { - if (detModel != null) { - detModel.close(); - } - } catch (Exception e) { - log.warn("关闭 model 失败", e); - } } } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/criteria/DirectionCriteriaFactory.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/criteria/DirectionCriteriaFactory.java new file mode 100644 index 0000000..540b698 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/criteria/DirectionCriteriaFactory.java @@ -0,0 +1,61 @@ +package cn.smartjavaai.ocr.model.common.direction.criteria; + +import ai.djl.Device; +import ai.djl.modality.cv.Image; +import ai.djl.ndarray.NDList; +import ai.djl.repository.zoo.Criteria; +import ai.djl.training.util.ProgressBar; +import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.ocr.config.DirectionModelConfig; +import cn.smartjavaai.ocr.config.OcrDetModelConfig; +import cn.smartjavaai.ocr.entity.DirectionInfo; +import cn.smartjavaai.ocr.enums.CommonDetModelEnum; +import cn.smartjavaai.ocr.enums.DirectionModelEnum; +import cn.smartjavaai.ocr.model.common.detect.translator.PPOCRDetTranslator; +import cn.smartjavaai.ocr.model.common.direction.translator.PpWordRotateTranslator; +import org.apache.commons.lang3.StringUtils; + +import java.nio.file.Paths; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 行方向分类 + * @author dwj + */ +public class DirectionCriteriaFactory { + + public static Criteria createCriteria(DirectionModelConfig config) { + Device device = null; + if(!Objects.isNull(config.getDevice())){ + device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(); + } + Criteria criteria = null; + ConcurrentHashMap params = new ConcurrentHashMap(); + params.putAll(config.getCustomParams()); + if(StringUtils.isNotBlank(config.getBatchifier())){ + params.put("batchifier", config.getBatchifier()); + } + if(config.getModelEnum() == DirectionModelEnum.CH_PPOCR_MOBILE_V2_CLS){ + params.put("resizeWidth", 192); + params.put("resizeHeight", 48); + }else if (config.getModelEnum() == DirectionModelEnum.PP_LCNET_X0_25){ + params.put("resizeWidth", 160); + params.put("resizeHeight", 80); + }else if (config.getModelEnum() == DirectionModelEnum.PP_LCNET_X1_0){ + params.put("resizeWidth", 160); + params.put("resizeHeight", 80); + } + criteria = + Criteria.builder() + .optEngine("OnnxRuntime") + .setTypes(Image.class, DirectionInfo.class) + .optModelPath(Paths.get(config.getModelPath())) + .optDevice(device) + .optTranslator(new PpWordRotateTranslator(params)) + .optProgress(new ProgressBar()) + .build(); + return criteria; + } + +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/translator/PpWordRotateTranslator.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/translator/PpWordRotateTranslator.java index c0e4e3a..3229f2f 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/translator/PpWordRotateTranslator.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/direction/translator/PpWordRotateTranslator.java @@ -13,6 +13,7 @@ import cn.smartjavaai.ocr.entity.DirectionInfo; import java.util.Arrays; import java.util.List; +import java.util.Map; /** * 方向检测 @@ -24,7 +25,24 @@ import java.util.List; public class PpWordRotateTranslator implements Translator { List classes = Arrays.asList("No Rotate", "Rotate"); - public PpWordRotateTranslator() { + private String batchifier; + + private int resizeHeight; + + private int resizeWidth; + + public PpWordRotateTranslator(Map arguments) { + batchifier = arguments.containsKey("batchifier") + ? arguments.get("batchifier").toString() + : "padding"; + + resizeWidth = arguments.containsKey("resizeWidth") + ? (Integer) arguments.get("resizeWidth") + : 192; + + resizeHeight = arguments.containsKey("resizeHeight") + ? (Integer) arguments.get("resizeHeight") + : 48; } @Override @@ -51,8 +69,8 @@ public class PpWordRotateTranslator implements Translator public NDList processInput(TranslatorContext ctx, Image input) { NDArray img = input.toNDArray(ctx.getNDManager()); int imgC = 3; - int imgH = 48; - int imgW = 192; + int imgH = resizeHeight; + int imgW = resizeWidth; NDArray array = ctx.getNDManager().zeros(new Shape(imgC, imgH, imgW)); @@ -74,13 +92,14 @@ public class PpWordRotateTranslator implements Translator array.set(new NDIndex(":,:,0:" + resized_w), img); - array = array.expandDims(0); +// array = array.expandDims(0); return new NDList(new NDArray[]{array}); } @Override public Batchifier getBatchifier() { - return null; + return Batchifier.fromString(batchifier); } + } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/OcrCommonRecModel.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/OcrCommonRecModel.java index 73dd78d..e9ac8b2 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/OcrCommonRecModel.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/OcrCommonRecModel.java @@ -3,8 +3,11 @@ package cn.smartjavaai.ocr.model.common.recognize; import ai.djl.modality.cv.Image; import cn.smartjavaai.ocr.config.OcrDetModelConfig; import cn.smartjavaai.ocr.config.OcrRecModelConfig; +import cn.smartjavaai.ocr.config.OcrRecOptions; import cn.smartjavaai.ocr.entity.OcrBox; import cn.smartjavaai.ocr.entity.OcrInfo; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; +import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel; import java.awt.image.BufferedImage; import java.util.List; @@ -15,6 +18,22 @@ import java.util.List; */ public interface OcrCommonRecModel extends AutoCloseable{ + default void setTextDetModel(OcrCommonDetModel detModel){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + + default OcrCommonDetModel getTextDetModel(){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + + default void setDirectionModel(OcrDirectionModel directionModel){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + + default OcrDirectionModel getDirectionModel(){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + /** * 加载模型 * @param config @@ -26,7 +45,16 @@ public interface OcrCommonRecModel extends AutoCloseable{ * @param imagePath 图片路径 * @return */ - default OcrInfo recognize(String imagePath) { + default OcrInfo recognize(String imagePath, OcrRecOptions options) { + throw new UnsupportedOperationException("默认不支持该功能"); + } + + /** + * 文本识别 + * @param image + * @return + */ + default OcrInfo recognize(Image image, OcrRecOptions options) { throw new UnsupportedOperationException("默认不支持该功能"); } @@ -36,7 +64,7 @@ public interface OcrCommonRecModel extends AutoCloseable{ * @param image BufferedImage * @return */ - default OcrInfo recognize(BufferedImage image) { + default OcrInfo recognize(BufferedImage image, OcrRecOptions options) { throw new UnsupportedOperationException("默认不支持该功能"); } @@ -46,7 +74,7 @@ public interface OcrCommonRecModel extends AutoCloseable{ * @param imageData 图片字节数组 * @return */ - default OcrInfo recognize(byte[] imageData) { + default OcrInfo recognize(byte[] imageData, OcrRecOptions options) { throw new UnsupportedOperationException("默认不支持该功能"); } @@ -56,7 +84,7 @@ public interface OcrCommonRecModel extends AutoCloseable{ * @param imagePath * @param outputPath */ - default void recognizeAndDraw(String imagePath, String outputPath, int fontSize) { + default void recognizeAndDraw(String imagePath, String outputPath, int fontSize, OcrRecOptions options) { throw new UnsupportedOperationException("默认不支持该功能"); } @@ -65,7 +93,16 @@ public interface OcrCommonRecModel extends AutoCloseable{ * @param sourceImage * @return */ - default BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize){ + default BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize, OcrRecOptions options){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + + + default List batchRecognize(List imageList, OcrRecOptions options) { + throw new UnsupportedOperationException("默认不支持该功能"); + } + + default List batchRecognizeDJLImage(List imageList, OcrRecOptions options) { throw new UnsupportedOperationException("默认不支持该功能"); } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/PpOCRV5RecModel.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/OcrCommonRecModelImpl.java similarity index 50% rename from smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/PpOCRV5RecModel.java rename to smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/OcrCommonRecModelImpl.java index 423b0a8..775efd0 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/PpOCRV5RecModel.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/OcrCommonRecModelImpl.java @@ -6,35 +6,28 @@ import ai.djl.engine.Engine; import ai.djl.inference.Predictor; import ai.djl.modality.cv.Image; import ai.djl.modality.cv.ImageFactory; -import ai.djl.modality.cv.output.Point; import ai.djl.ndarray.NDArray; -import ai.djl.ndarray.NDList; import ai.djl.ndarray.NDManager; -import ai.djl.opencv.OpenCVImageFactory; import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.ModelNotFoundException; import ai.djl.repository.zoo.ModelZoo; import ai.djl.repository.zoo.ZooModel; -import ai.djl.training.util.ProgressBar; import cn.smartjavaai.common.enums.DeviceEnum; import cn.smartjavaai.common.pool.PredictorFactory; import cn.smartjavaai.common.utils.FileUtils; import cn.smartjavaai.common.utils.ImageUtils; import cn.smartjavaai.common.utils.OpenCVUtils; -import cn.smartjavaai.ocr.config.DirectionModelConfig; -import cn.smartjavaai.ocr.config.OcrDetModelConfig; import cn.smartjavaai.ocr.config.OcrRecModelConfig; +import cn.smartjavaai.ocr.config.OcrRecOptions; import cn.smartjavaai.ocr.entity.*; import cn.smartjavaai.ocr.exception.OcrException; -import cn.smartjavaai.ocr.factory.OcrModelFactory; import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; -import cn.smartjavaai.ocr.model.common.detect.translator.PPOCRV5DetTranslator; import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel; -import cn.smartjavaai.ocr.model.common.recognize.translator.PPOCRV5RecTranslator; -import cn.smartjavaai.ocr.opencv.OcrNDArrayUtils; +import cn.smartjavaai.ocr.model.common.recognize.criteria.OcrCommonRecCriterialFactory; import cn.smartjavaai.ocr.opencv.OcrOpenCVUtils; import cn.smartjavaai.ocr.utils.OcrUtils; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; @@ -45,50 +38,37 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** * PPOCRV5 识别模型 * @author dwj - * @date 2025/4/21 */ @Slf4j -public class PpOCRV5RecModel implements OcrCommonRecModel { +public class OcrCommonRecModelImpl implements OcrCommonRecModel { private ObjectPool> recPredictorPool; private OcrRecModelConfig config; - private OcrCommonDetModel detModel; + private ZooModel recognitionModel; private OcrDirectionModel directionModel; - private ZooModel recognitionModel; + private OcrCommonDetModel textDetModel; @Override public void loadModel(OcrRecModelConfig config){ if(StringUtils.isBlank(config.getRecModelPath())){ throw new OcrException("recModelPath is null"); } - Device device = null; - if(!Objects.isNull(config.getDevice())){ - device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(); - } this.config = config; + this.directionModel = config.getDirectionModel(); + this.textDetModel = config.getTextDetModel(); //初始化 识别Criteria - Criteria recCriteria = - Criteria.builder() - .optEngine("OnnxRuntime") - .setTypes(Image.class, String.class) - .optModelPath(Paths.get(config.getRecModelPath())) - .optTranslator(new PPOCRV5RecTranslator(new ConcurrentHashMap())) - .optProgress(new ProgressBar()) - .optDevice(device) - .build(); + Criteria recCriteria = OcrCommonRecCriterialFactory.createCriteria(config); try{ recognitionModel = ModelZoo.loadModel(recCriteria); this.recPredictorPool = new GenericObjectPool<>(new PredictorFactory<>(recognitionModel)); @@ -98,29 +78,11 @@ public class PpOCRV5RecModel implements OcrCommonRecModel { throw new OcrException("识别模型加载失败", e); } - - //获取检测模型 - if(StringUtils.isNotBlank(config.getDetModelPath()) && Objects.nonNull(config.getDetModelEnum())){ - OcrDetModelConfig detModelConfig = new OcrDetModelConfig(); - detModelConfig.setModelEnum(config.getDetModelEnum()); - detModelConfig.setDetModelPath(config.getDetModelPath()); - detModelConfig.setDevice(config.getDevice()); - detModel = OcrModelFactory.getInstance().getDetModel(detModelConfig); - } - - //获取方向检测模型 - if(StringUtils.isNotBlank(config.getDirectionModelPath()) && Objects.nonNull(config.getDirectionModelEnum())){ - DirectionModelConfig directionModelConfig = new DirectionModelConfig(); - directionModelConfig.setModelEnum(config.getDirectionModelEnum()); - directionModelConfig.setModelPath(config.getDirectionModelPath()); - directionModelConfig.setDevice(config.getDevice()); - directionModel = OcrModelFactory.getInstance().getDirectionModel(directionModelConfig); - } } @Override - public OcrInfo recognize(String imagePath) { + public OcrInfo recognize(String imagePath, OcrRecOptions options) { if(StringUtils.isBlank(config.getRecModelPath())){ throw new OcrException("recModelPath为空,无法识别"); } @@ -130,78 +92,44 @@ public class PpOCRV5RecModel implements OcrCommonRecModel { Image img = null; try { img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); + return recognize(img, options); } catch (IOException e) { throw new OcrException("无效的图片", e); - } - OcrInfo ocrInfo = recognize(img); - ((Mat)img.getWrappedImage()).release(); - return ocrInfo; - } - - private OcrInfo recognize(Image image) { - //检测文本 - List boxeList = detModel.detect(image); - if(Objects.isNull(boxeList) || boxeList.isEmpty()){ - throw new OcrException("未检测到文本"); - } - Predictor predictor = null; - List rotatedBoxes = new ArrayList<>(); - List ocrItemList = new ArrayList<>(); - try (NDManager manager = NDManager.newBaseManager()) { - Mat srcMat = (Mat) image.getWrappedImage(); - predictor = recPredictorPool.borrowObject(); - //检测方向 - if(directionModel != null){ - ocrItemList = directionModel.detect(boxeList, srcMat, manager); - if(Objects.isNull(ocrItemList) || ocrItemList.isEmpty()){ - throw new OcrException("方向检测失败"); - } - for (OcrItem ocrItem : ocrItemList){ - //放射变换+裁剪 - Image subImage = OcrUtils.transformAndCrop(srcMat, ocrItem.getOcrBox()); - //ImageUtils.saveImage(subImage, UUID.randomUUID().toString() + "_aaa.png", "build/output"); - //纠正文本框 - subImage = OcrUtils.rotateImg(subImage, ocrItem.getAngle()); - //ImageUtils.saveImage(subImage, UUID.randomUUID().toString() + "_bbb.png", "build/output"); - //识别 - String name = predictor.predict(subImage); - ocrItem.setText(name); - NDArray ndArray = manager.create(ocrItem.getOcrBox().toFloatArray()); - rotatedBoxes.add(new RotatedBox(ndArray, ocrItem.getText())); - ((Mat)subImage.getWrappedImage()).release(); - } - }else{ - for (OcrBox box : boxeList){ - RotatedBox rotatedBox = recognize(box, srcMat, predictor, manager); - rotatedBoxes.add(rotatedBox); - } - } - //后处理 - return postProcessOcrResult(rotatedBoxes); - } catch (Exception e) { - throw new OcrException("OCR检测错误", e); - }finally { - if (predictor != null) { - try { - recPredictorPool.returnObject(predictor); //归还 - } catch (Exception e) { - log.warn("归还Predictor失败", e); - try { - predictor.close(); // 归还失败才销毁 - } catch (Exception ex) { - log.error("关闭Predictor失败", ex); - } - } + } finally { + if(img != null){ + ((Mat)img.getWrappedImage()).release(); } } } + /** + * + * @param image + * @param options + * @return + */ + @Override + public OcrInfo recognize(Image image, OcrRecOptions options) { + List result = batchRecognizeDJLImage(Collections.singletonList(image), options); + if(CollectionUtils.isEmpty(result)){ + throw new OcrException("OCR识别结果为空"); + } + return result.get(0); + } - private RotatedBox recognize(OcrBox box,Mat srcMat,Predictor recPredictor,NDManager manager){ - try { + /** + * 批量矫正文本框 + * @param boxList + * @param srcMat + * @param manager + * @return + */ + private List batchAlign(List boxList, Mat srcMat,NDManager manager){ + List imageList = new ArrayList<>(boxList.size()); + for (int i = 0; i < boxList.size(); i++) { //透视变换 + 裁剪 - Image subImg = OcrUtils.transformAndCrop(srcMat, box); + Image subImg = OcrUtils.transformAndCrop(srcMat, boxList.get(i)); //ImageUtils.saveImage(subImg, i + "crop.png", "build/output"); //高宽比 > 1.5 if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { @@ -209,21 +137,63 @@ public class PpOCRV5RecModel implements OcrCommonRecModel { subImg = OcrUtils.rotateImg(manager, subImg); //ImageUtils.saveImage(subImg, i + "rotate.png", "build/output"); } - String name = recPredictor.predict(subImg); - ((Mat)subImg.getWrappedImage()).release(); - NDArray pointsArray = manager.create(box.toFloatArray()); - return new RotatedBox(pointsArray, name); - } catch (Exception e) { - throw new OcrException("OCR检测错误", e); + imageList.add(subImg); } + return imageList; } + /** + * 批量矫正文本框 + * @param itemList + * @param srcMat + * @param manager + * @return + */ + private List batchAlignWithDirection(List itemList, Mat srcMat,NDManager manager){ + List imageList = new ArrayList<>(itemList.size()); + for (OcrItem ocrItem : itemList) { + //放射变换+裁剪 + Image subImage = OcrUtils.transformAndCrop(srcMat, ocrItem.getOcrBox()); + //ImageUtils.saveImage(subImage, UUID.randomUUID().toString() + "_aaa.png", "build/output"); + //纠正文本框 + subImage = OcrUtils.rotateImg(subImage, ocrItem.getAngle()); + imageList.add(subImage); + } + return imageList; + } + + + +// private RotatedBox recognize(OcrBox box,Mat srcMat,Predictor recPredictor,NDManager manager){ +// try { +// //透视变换 + 裁剪 +// Image subImg = OcrUtils.transformAndCrop(srcMat, box); +// //ImageUtils.saveImage(subImg, i + "crop.png", "build/output"); +// //高宽比 > 1.5 +// if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) { +// //旋转图片90度 +// subImg = OcrUtils.rotateImg(manager, subImg); +// //ImageUtils.saveImage(subImg, i + "rotate.png", "build/output"); +// } +// String name = recPredictor.predict(subImg); +// ((Mat)subImg.getWrappedImage()).release(); +// NDArray pointsArray = manager.create(box.toFloatArray()); +// return new RotatedBox(pointsArray, name); +// } catch (Exception e) { +// throw new OcrException("OCR检测错误", e); +// } +// } + /** * 后处理:排序,分行 * @param rotatedBoxes */ - private OcrInfo postProcessOcrResult(List rotatedBoxes){ + private OcrInfo postProcessOcrResult(List rotatedBoxes, OcrRecOptions ocrRecOptions){ + //不分行 + if(!ocrRecOptions.isEnableLineSplit()){ + return OcrUtils.convertRotatedBoxesToOcrItems(rotatedBoxes); + } //Y坐标升序排序 List initList = new ArrayList<>(); for (RotatedBox result : rotatedBoxes) { @@ -257,13 +227,13 @@ public class PpOCRV5RecModel implements OcrCommonRecModel { @Override - public void recognizeAndDraw(String imagePath, String outputPath, int fontSize) { + public void recognizeAndDraw(String imagePath, String outputPath, int fontSize, OcrRecOptions options) { if(!FileUtils.isFileExists(imagePath)){ throw new OcrException("图像文件不存在"); } try { Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); - OcrInfo ocrInfo = recognize(img); + OcrInfo ocrInfo = recognize(img, options); if(Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()){ throw new OcrException("未检测到文字"); } @@ -278,36 +248,36 @@ public class PpOCRV5RecModel implements OcrCommonRecModel { } @Override - public OcrInfo recognize(BufferedImage image) { + public OcrInfo recognize(BufferedImage image, OcrRecOptions options) { if(!ImageUtils.isImageValid(image)){ throw new OcrException("图像无效"); } Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); - OcrInfo ocrInfo = recognize(img); + OcrInfo ocrInfo = recognize(img, options); ((Mat)img.getWrappedImage()).release(); return ocrInfo; } @Override - public OcrInfo recognize(byte[] imageData) { + public OcrInfo recognize(byte[] imageData, OcrRecOptions options) { if(Objects.isNull(imageData)){ throw new OcrException("图像无效"); } try { BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); - return recognize(image); + return recognize(image, options); } catch (IOException e) { throw new OcrException("错误的图像", e); } } @Override - public BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize) { + public BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize, OcrRecOptions options) { if(!ImageUtils.isImageValid(sourceImage)){ throw new OcrException("图像无效"); } Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage)); - OcrInfo ocrInfo = recognize(img); + OcrInfo ocrInfo = recognize(img, options); if(Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()){ throw new OcrException("未检测到文字"); } @@ -325,6 +295,154 @@ public class PpOCRV5RecModel implements OcrCommonRecModel { } } + @Override + public List batchRecognize(List imageList, OcrRecOptions options) { + List djlImageList = new ArrayList<>(imageList.size()); + try { + for (BufferedImage bufferedImage : imageList) { + djlImageList.add(ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(bufferedImage))); + } + return batchRecognizeDJLImage(djlImageList, options); + } catch (Exception e) { + throw new OcrException(e); + } finally { + djlImageList.forEach(image -> ((Mat)image.getWrappedImage()).release()); + } + } + + @Override + public List batchRecognizeDJLImage(List imageList, OcrRecOptions options) { + if(Objects.isNull(textDetModel)){ + throw new OcrException("textDetModel is null"); + } + OcrRecOptions ocrRecOptions = options; + if(Objects.isNull(options)){ + ocrRecOptions = new OcrRecOptions(); + } + if(CollectionUtils.isEmpty(imageList)){ + throw new OcrException("imageList is empty"); + } + //检测文本 + List> boxeList = textDetModel.batchDetectDJLImage(imageList); + if(CollectionUtils.isEmpty(boxeList) || boxeList.size() != imageList.size()){ + throw new OcrException("未检测到文本"); + } + Predictor predictor = null; + List ocrInfoList = new ArrayList(); + try (NDManager manager = NDManager.newBaseManager()) { + predictor = recPredictorPool.borrowObject(); + List allImageAlignList = new ArrayList(); + //检测方向 + if(ocrRecOptions.isEnableDirectionCorrect()){ + if(Objects.isNull(directionModel)){ + throw new OcrException("请配置方向模型"); + } + List matList = imageList.stream() + .map(image -> (Mat)image.getWrappedImage()) + .collect(Collectors.toList()); + List> ocrItemList = directionModel.batchDetect(boxeList, matList); + if(CollectionUtils.isEmpty(ocrItemList) || ocrItemList.size() != imageList.size()){ + throw new OcrException("方向检测失败"); + } + allImageAlignList = new ArrayList(); + for (int i = 0; i < ocrItemList.size(); i++) { + Mat srcMat = (Mat) imageList.get(i).getWrappedImage(); + List imageAlignList = batchAlignWithDirection(ocrItemList.get(i), srcMat, manager); +// for(int j = 0; j < imageAlignList.size(); j++){ +// ImageUtils.saveImage(imageAlignList.get(j),"dir-"+i+"-"+j+".png","/Users/xxx/Downloads/testing33"); +// } + allImageAlignList.addAll(imageAlignList); + } + }else{ + for (int i = 0; i < boxeList.size(); i++) { + Mat srcMat = (Mat) imageList.get(i).getWrappedImage(); + List imageAlignList = batchAlign(boxeList.get(i), srcMat, manager); +// for(int j = 0; j < imageAlignList.size(); j++){ +// ImageUtils.saveImage(imageAlignList.get(j),i+"-"+j+".png","/Users/xxx/Downloads/testing33"); +// } + allImageAlignList.addAll(imageAlignList); + } + } + List textList = batchRecognize(allImageAlignList); + int textIndex = 0; + for (int i = 0; i < boxeList.size(); i++) { + List rotatedBoxes = new ArrayList<>(); + for (int j = 0; j < boxeList.get(i).size(); j++){ + if(textIndex >= textList.size()){ + throw new OcrException("识别失败: 第" + i + "张图片, 第" + j + "个文本块,未识别到文本"); + } + OcrBox box = boxeList.get(i).get(j); + NDArray pointsArray = manager.create(box.toFloatArray()); + rotatedBoxes.add(new RotatedBox(pointsArray, textList.get(textIndex))); + textIndex++; + } + OcrInfo ocrInfo = postProcessOcrResult(rotatedBoxes, ocrRecOptions); + ocrInfoList.add(ocrInfo); + } + return ocrInfoList; + } catch (Exception e) { + throw new OcrException("OCR检测错误", e); + }finally { + if (predictor != null) { + try { + recPredictorPool.returnObject(predictor); //归还 + } catch (Exception e) { + log.warn("归还Predictor失败", e); + try { + predictor.close(); // 归还失败才销毁 + } catch (Exception ex) { + log.error("关闭Predictor失败", ex); + } + } + } + } + } + + private List batchRecognize(List imageAlignList){ + Predictor predictor = null; + try { + predictor = recPredictorPool.borrowObject(); + List textList = predictor.batchPredict(imageAlignList); + imageAlignList.forEach(subImg -> ((Mat)subImg.getWrappedImage()).release()); + return textList; + } catch (Exception e) { + throw new OcrException("OCR检测错误", e); + }finally { + if (predictor != null) { + try { + recPredictorPool.returnObject(predictor); //归还 + } catch (Exception e) { + log.warn("归还Predictor失败", e); + try { + predictor.close(); // 归还失败才销毁 + } catch (Exception ex) { + log.error("关闭Predictor失败", ex); + } + } + } + } + } + + @Override + public void setTextDetModel(OcrCommonDetModel detModel) { + this.textDetModel = detModel; + } + + @Override + public OcrCommonDetModel getTextDetModel() { + return textDetModel; + } + + @Override + public void setDirectionModel(OcrDirectionModel directionModel) { + this.directionModel = directionModel; + } + + @Override + public OcrDirectionModel getDirectionModel() { + return directionModel; + } + @Override public void close() throws Exception { try { @@ -334,20 +452,6 @@ public class PpOCRV5RecModel implements OcrCommonRecModel { } catch (Exception e) { log.warn("关闭 predictorPool 失败", e); } - try { - if (detModel != null) { - detModel.close(); - } - } catch (Exception e) { - log.warn("关闭 model 失败", e); - } - try { - if (directionModel != null) { - directionModel.close(); - } - } catch (Exception e) { - log.warn("关闭 model 失败", e); - } try { if (recognitionModel != null) { recognitionModel.close(); diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/criteria/OcrCommonRecCriterialFactory.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/criteria/OcrCommonRecCriterialFactory.java new file mode 100644 index 0000000..6eb61e5 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/criteria/OcrCommonRecCriterialFactory.java @@ -0,0 +1,51 @@ +package cn.smartjavaai.ocr.model.common.recognize.criteria; + +import ai.djl.Device; +import ai.djl.modality.cv.Image; +import ai.djl.repository.zoo.Criteria; +import ai.djl.training.util.ProgressBar; +import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.ocr.config.OcrRecModelConfig; +import cn.smartjavaai.ocr.enums.CommonRecModelEnum; +import cn.smartjavaai.ocr.model.common.recognize.translator.PPOCRRecTranslator; +import org.apache.commons.lang3.StringUtils; + +import java.nio.file.Paths; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author dwj + * @date 2025/7/8 + */ +public class OcrCommonRecCriterialFactory { + + + public static Criteria createCriteria(OcrRecModelConfig config) { + Device device = null; + if(!Objects.isNull(config.getDevice())){ + device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(); + } + Criteria criteria = null; + ConcurrentHashMap params = new ConcurrentHashMap(); + params.putAll(config.getCustomParams()); + if(StringUtils.isNotBlank(config.getBatchifier())){ + params.put("batchifier", config.getBatchifier()); + } + if(config.getRecModelEnum() == CommonRecModelEnum.PP_OCR_V5_SERVER_REC_MODEL || + config.getRecModelEnum() == CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL || + config.getRecModelEnum() == CommonRecModelEnum.PP_OCR_V4_SERVER_REC_MODEL || + config.getRecModelEnum() == CommonRecModelEnum.PP_OCR_V4_MOBILE_REC_MODEL ){ + criteria = + Criteria.builder() + .optEngine("OnnxRuntime") + .setTypes(Image.class, String.class) + .optModelPath(Paths.get(config.getRecModelPath())) + .optTranslator(new PPOCRRecTranslator(params)) + .optProgress(new ProgressBar()) + .optDevice(device) + .build(); + } + return criteria; + } +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/translator/PPOCRV5RecTranslator.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/translator/PPOCRRecTranslator.java similarity index 87% rename from smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/translator/PPOCRV5RecTranslator.java rename to smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/translator/PPOCRRecTranslator.java index dab81d9..5f2df56 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/translator/PPOCRV5RecTranslator.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/common/recognize/translator/PPOCRRecTranslator.java @@ -23,15 +23,20 @@ import java.util.Map; * 文字识别前后处理 * */ -public class PPOCRV5RecTranslator implements Translator { +public class PPOCRRecTranslator implements Translator { private List table; private final boolean use_space_char; - public PPOCRV5RecTranslator(Map arguments) { + private String batchifier; + + public PPOCRRecTranslator(Map arguments) { use_space_char = arguments.containsKey("use_space_char") ? Boolean.parseBoolean(arguments.get("use_space_char").toString()) : true; + batchifier = arguments.containsKey("batchifier") + ? arguments.get("batchifier").toString() + : "padding"; } @Override @@ -57,7 +62,8 @@ public class PPOCRV5RecTranslator implements Translator { StringBuilder sb = new StringBuilder(); NDArray tokens = list.singletonOrThrow(); - long[] indices = tokens.get(0).argMax(1).toLongArray(); +// long[] indices = tokens.get(0).argMax(1).toLongArray(); + long[] indices = tokens.argMax(1).toLongArray(); boolean[] selection = new boolean[indices.length]; Arrays.fill(selection, true); for (int i = 1; i < indices.length; i++) { @@ -111,13 +117,13 @@ public class PPOCRV5RecTranslator implements Translator { padding_im.set(new NDIndex(":,:,0:" + resized_w), resized_image); padding_im = padding_im.flip(0); - padding_im = padding_im.expandDims(0); +// padding_im = padding_im.expandDims(0); return new NDList(padding_im); } @Override public Batchifier getBatchifier() { - return null; + return Batchifier.fromString(batchifier); } } diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/CommonTableStructureModel.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/CommonTableStructureModel.java new file mode 100644 index 0000000..a8a506b --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/CommonTableStructureModel.java @@ -0,0 +1,160 @@ +package cn.smartjavaai.ocr.model.table; + +import ai.djl.MalformedModelException; +import ai.djl.engine.Engine; +import ai.djl.inference.Predictor; +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.ImageFactory; +import ai.djl.ndarray.NDList; +import ai.djl.ndarray.NDManager; +import ai.djl.repository.zoo.Criteria; +import ai.djl.repository.zoo.ModelNotFoundException; +import ai.djl.repository.zoo.ModelZoo; +import ai.djl.repository.zoo.ZooModel; +import cn.smartjavaai.common.entity.R; +import cn.smartjavaai.common.pool.PredictorFactory; +import cn.smartjavaai.common.utils.FileUtils; +import cn.smartjavaai.common.utils.ImageUtils; +import cn.smartjavaai.common.utils.OpenCVUtils; +import cn.smartjavaai.ocr.config.OcrDetModelConfig; +import cn.smartjavaai.ocr.config.TableStructureConfig; +import cn.smartjavaai.ocr.entity.OcrBox; +import cn.smartjavaai.ocr.entity.OcrItem; +import cn.smartjavaai.ocr.entity.TableStructureResult; +import cn.smartjavaai.ocr.exception.OcrException; +import cn.smartjavaai.ocr.model.table.criteria.StructureCriteriaFactory; +import cn.smartjavaai.ocr.utils.OcrUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.opencv.core.Mat; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; + +/** + * 表格结构模型 + * @author dwj + */ +@Slf4j +public class CommonTableStructureModel implements TableStructureModel{ + + private ZooModel model; + + private ObjectPool> predictorPool; + + @Override + public void loadModel(TableStructureConfig config) { + if(StringUtils.isBlank(config.getModelPath())){ + throw new OcrException("modelPath is null"); + } + Criteria criteria = StructureCriteriaFactory.createCriteria(config); + try{ + model = ModelZoo.loadModel(criteria); + // 创建池子:每个线程独享 Predictor + this.predictorPool = new GenericObjectPool<>(new PredictorFactory<>(model)); + log.debug("当前设备: " + model.getNDManager().getDevice()); + log.debug("当前引擎: " + Engine.getInstance().getEngineName()); + } catch (IOException | ModelNotFoundException | MalformedModelException e) { + throw new OcrException("表格结构识别模型加载失败", e); + } + } + + @Override + public R detect(BufferedImage image) { + if(!ImageUtils.isImageValid(image)){ + return R.fail(R.Status.INVALID_IMAGE); + } + Image img = null; + try { + img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); + return detect(img); + } catch (Exception e) { + throw new OcrException(e); + } finally { + if(Objects.nonNull(img)){ + ((Mat)img.getWrappedImage()).release(); + } + } + } + + @Override + public R detect(String imagePath) { + if(!FileUtils.isFileExists(imagePath)){ + return R.fail(R.Status.FILE_NOT_FOUND); + } + Image img = null; + try { + img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); + return detect(img); + } catch (IOException e) { + throw new OcrException("无效的图片", e); + } finally { + if (Objects.nonNull(img)){ + ((Mat)img.getWrappedImage()).release(); + } + } + } + + @Override + public R detect(byte[] imageData) { + if(Objects.isNull(imageData)){ + return R.fail(R.Status.INVALID_IMAGE); + } + try { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); + return detect(image); + } catch (IOException e) { + throw new OcrException("错误的图像", e); + } + } + + @Override + public R detect(Image image) { + Predictor predictor = null; + try { + predictor = predictorPool.borrowObject(); + TableStructureResult result = predictor.predict(image); + return R.ok(result); + } catch (Exception e) { + throw new OcrException("OCR检测错误", e); + }finally { + if (predictor != null) { + try { + predictorPool.returnObject(predictor); //归还 + } catch (Exception e) { + log.warn("归还Predictor失败", e); + try { + predictor.close(); // 归还失败才销毁 + } catch (Exception ex) { + log.error("关闭Predictor失败", ex); + } + } + } + } + } + + @Override + public void close() throws Exception { + try { + if (predictorPool != null) { + predictorPool.close(); + } + } catch (Exception e) { + log.warn("关闭 predictorPool 失败", e); + } + try { + if (model != null) { + model.close(); + } + } catch (Exception e) { + log.warn("关闭 model 失败", e); + } + } +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/TableRecognizer.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/TableRecognizer.java new file mode 100644 index 0000000..faf76a6 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/TableRecognizer.java @@ -0,0 +1,454 @@ +package cn.smartjavaai.ocr.model.table; + +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.ImageFactory; +import ai.djl.modality.cv.output.BoundingBox; +import ai.djl.modality.cv.output.DetectedObjects; +import ai.djl.modality.cv.output.Rectangle; +import ai.djl.translate.TranslateException; +import ai.djl.util.JsonUtils; +import cn.smartjavaai.common.entity.DetectionRectangle; +import cn.smartjavaai.common.entity.R; +import cn.smartjavaai.common.utils.FileUtils; +import cn.smartjavaai.common.utils.ImageUtils; +import cn.smartjavaai.common.utils.OpenCVUtils; +import cn.smartjavaai.ocr.config.OcrRecModelConfig; +import cn.smartjavaai.ocr.config.OcrRecOptions; +import cn.smartjavaai.ocr.entity.OcrBox; +import cn.smartjavaai.ocr.entity.OcrInfo; +import cn.smartjavaai.ocr.entity.OcrItem; +import cn.smartjavaai.ocr.entity.TableStructureResult; +import cn.smartjavaai.ocr.exception.OcrException; +import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel; +import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel; +import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel; +import cn.smartjavaai.ocr.utils.ConvertHtml2Excel; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.opencv.core.Mat; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.*; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 表格内容识别器 + * @author dwj + */ +@Slf4j +public class TableRecognizer { + + private OcrCommonDetModel textDetector; + private TableStructureModel tableStructureModel; + private OcrCommonRecModel textRecModel; + private OcrDirectionModel directionModel; + + private TableRecognizer(Builder builder) { + this.tableStructureModel = builder.tableStructureModel; + this.textRecModel = builder.textRecModel; + this.directionModel = builder.directionModel; + this.textDetector = builder.textDetector; + textRecModel.setTextDetModel(textDetector); + textRecModel.setDirectionModel(directionModel); + } + + public static Builder builder() { + return new Builder(); + } + + // 链式设置文本识别模型 + public TableRecognizer withTextRecModel(OcrCommonRecModel textRecModel) { + this.textRecModel = textRecModel; + return this; + } + + // 链式设置表格结构模型 + public TableRecognizer withStructureModel(TableStructureModel tableStructureModel) { + this.tableStructureModel = tableStructureModel; + return this; + } + + /** + * 表格识别 + * @param image + * @return + */ + public R recognize(Image image) { + //表格结构识别 + R result = tableStructureModel.detect(image); + if(!result.isSuccess()){ + return R.fail(result.getCode(), result.getMessage()); + } + //文本检测+文字识别 + boolean enableDirectionCorrect = directionModel == null ? false : true; + OcrRecOptions options = new OcrRecOptions(enableDirectionCorrect, false); + OcrInfo ocrInfo = textRecModel.recognize(image, options); + List tableContentList = buildTable(result.getData(), ocrInfo); + String html = convertHtml(result.getData().getTableTagList(), tableContentList); + result.getData().setHtml(html); + return result; + } + + + /** + * 表格识别 + * @param image + * @return + */ + public R recognize(BufferedImage image) { + if(!ImageUtils.isImageValid(image)){ + return R.fail(R.Status.INVALID_IMAGE); + } + Image img = null; + try { + img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); + return recognize(img); + } catch (Exception e) { + throw new OcrException(e); + } finally { + if(Objects.nonNull(img)){ + ((Mat)img.getWrappedImage()).release(); + } + } + } + + /** + * 表格识别 + * @param imagePath + * @return + */ + public R recognize(String imagePath) { + if(!FileUtils.isFileExists(imagePath)){ + return R.fail(R.Status.FILE_NOT_FOUND); + } + Image img = null; + try { + img = ImageFactory.getInstance().fromFile(Paths.get(imagePath)); + return recognize(img); + } catch (IOException e) { + throw new OcrException("无效的图片", e); + } finally { + if (Objects.nonNull(img)){ + ((Mat)img.getWrappedImage()).release(); + } + } + } + + + /** + * 表格识别 + * @param imageData + * @return + */ + public R recognize(byte[] imageData) { + if(Objects.isNull(imageData)){ + return R.fail(R.Status.INVALID_IMAGE); + } + try { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); + return recognize(image); + } catch (IOException e) { + throw new OcrException("错误的图像", e); + } + } + + /** + * 绘制表格 + * @param tableStructureResult + * @param image + * @param savePath + */ + public void drawTable(TableStructureResult tableStructureResult, BufferedImage image, String savePath){ + if(Objects.isNull(tableStructureResult) || CollectionUtils.isEmpty(tableStructureResult.getTableTagList())){ + throw new OcrException("表格结构为空"); + } + for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++){ + OcrItem item = tableStructureResult.getOcrItemList().get(i); + DetectionRectangle detectionRectangle = item.getOcrBox().toDetectionRectangle(); + ImageUtils.drawImageRectWithText(image, detectionRectangle, i + "", Color.RED); + } + ImageUtils.saveImage(image, savePath); + } + + /** + * 删除 HTML 中第一个 段落 + * @param html 原始 HTML + * @return 去掉 ", styleStart); + if (styleEnd == -1) { + return html; // 没闭合标签,不处理 + } + styleEnd += "".length(); + // 去掉 style 块 + return html.substring(0, styleStart) + html.substring(styleEnd); + } + + /** + * 导出 Excel + * @param html + * @param savePath + */ + public void exportExcel(String html, String savePath){ + try { + String content = removeStyleBlock(html); + content = content.replace("", ""); + content = content.replace("", ""); + HSSFWorkbook workbook = ConvertHtml2Excel.table2Excel(content); + workbook.write(new File(savePath)); + } catch (Exception e) { + throw new OcrException("导出excel失败,请检查表结构是否识别正确"); + } + } + + + /** + * 构建表格 + * @param tableStructureResult + * @param ocrInfo + * @return + */ + public List buildTable(TableStructureResult tableStructureResult, OcrInfo ocrInfo) { + // 获取 Cell 与 文本检测框 的对应关系(1:N)。 + Map> matched = new ConcurrentHashMap<>(); + List ocrItems = ocrInfo.getOcrItemList(); + + for (int i = 0; i < ocrItems.size(); i++) { + OcrBox ocrBox = ocrItems.get(i).getOcrBox(); + int[] box_1 = { + (int)ocrBox.getTopLeft().getX(), + (int)ocrBox.getTopLeft().getY(), + (int)ocrBox.getBottomRight().getX(), + (int)ocrBox.getBottomRight().getY() + }; + // 获取两两cell之间的L1距离和 1- IOU + List> distances = new ArrayList<>(); + for (OcrItem cell : tableStructureResult.getOcrItemList()) { + OcrBox cellBox = cell.getOcrBox(); + int[] box_2 = { + (int)cellBox.getTopLeft().getX(), + (int)cellBox.getTopLeft().getY(), + (int)cellBox.getBottomRight().getX(), + (int)cellBox.getBottomRight().getY() + }; + float distance = distance(box_1, box_2); + float iou = 1 - computeIou(box_1, box_2); + distances.add(Pair.of(distance, iou)); + } + // 根据距离和IOU挑选最"近"的cell + Pair nearest = sorted(distances); + + // 获取最小距离对应的下标id,也等价于cell的下标id (distances列表是根据遍历cells生成的) + int id = 0; + for (int idx = 0; idx < distances.size(); idx++) { + Pair current = distances.get(idx); + if (current.getLeft().floatValue() == nearest.getLeft().floatValue() + && current.getRight().floatValue() == nearest.getRight().floatValue()) { + id = idx; + break; + } + } + if (!matched.containsKey(id)) { + List textIds = new ArrayList<>(); + textIds.add(i); + // cell id, text id list (dt_boxes index list) + matched.put(id, textIds); + } else { + matched.get(id).add(i); + } + } + + List cell_contents = new ArrayList<>(); + List probs = new ArrayList<>(); + for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++) { + List textIds = matched.get(i); + List contents = new ArrayList<>(); + String content = ""; + if (textIds != null) { + for (Integer id : textIds) { + contents.add(ocrItems.get(id).getText()); + } + content = StringUtils.join(contents, " "); + } + cell_contents.add(content); + probs.add(-1.0); + } + return cell_contents; + } + + /** + * 计算欧式距离 + * Calculate L1 distance + * + * @param box_1 + * @param box_2 + * @return + */ + private int distance(int[] box_1, int[] box_2) { + int x1 = box_1[0]; + int y1 = box_1[1]; + int x2 = box_1[2]; + int y2 = box_1[3]; + int x3 = box_2[0]; + int y3 = box_2[1]; + int x4 = box_2[2]; + int y4 = box_2[3]; + int dis = Math.abs(x3 - x1) + Math.abs(y3 - y1) + Math.abs(x4 - x2) + Math.abs(y4 - y2); + int dis_2 = Math.abs(x3 - x1) + Math.abs(y3 - y1); + int dis_3 = Math.abs(x4 - x2) + Math.abs(y4 - y2); + return dis + Math.min(dis_2, dis_3); + } + + /** + * 计算交并比 + * computing IoU + * + * @param rec1: (y0, x0, y1, x1), which reflects (top, left, bottom, right) + * @param rec2: (y0, x0, y1, x1) + * @return scala value of IoU + */ + private float computeIou(int[] rec1, int[] rec2) { + // computing area of each rectangles + int S_rec1 = (rec1[2] - rec1[0]) * (rec1[3] - rec1[1]); + int S_rec2 = (rec2[2] - rec2[0]) * (rec2[3] - rec2[1]); + + // computing the sum_area + int sum_area = S_rec1 + S_rec2; + + // find the each edge of intersect rectangle + int left_line = Math.max(rec1[1], rec2[1]); + int right_line = Math.min(rec1[3], rec2[3]); + int top_line = Math.max(rec1[0], rec2[0]); + int bottom_line = Math.min(rec1[2], rec2[2]); + + // judge if there is an intersect + if (left_line >= right_line || top_line >= bottom_line) { + return 0.0f; + } else { + float intersect = (right_line - left_line) * (bottom_line - top_line); + return (intersect / (sum_area - intersect)) * 1.0f; + } + } + + /** + * 距离排序 + * Distance sorted + * + * @param distances + * @return + */ + private Pair sorted(List> distances) { + Comparator> comparator = + new Comparator>() { + @Override + public int compare(Pair a1, Pair a2) { + // 首先根据IoU排序 + if (a1.getRight().floatValue() > a2.getRight().floatValue()) { + return 1; + } else if (a1.getRight().floatValue() == a2.getRight().floatValue()) { + // 然后根据L1距离排序 + if (a1.getLeft().floatValue() > a2.getLeft().floatValue()) { + return 1; + } + return -1; + } + return -1; + } + }; + + // 距离排序 + List> newDistances = new ArrayList<>(); + CollectionUtils.addAll(newDistances, new Object[distances.size()]); + Collections.copy(newDistances, distances); + Collections.sort(newDistances, comparator); + return newDistances.get(0); + } + + /** + * 生成表格html + * Generate table html + * + * @param pred_structures + * @param cell_contents + * @return + */ + public String convertHtml(List pred_structures, List cell_contents) { + StringBuffer html = new StringBuffer(); + // 添加统一的样式(可选放到中) + html.append("\n"); + int td_index = 0; + for (String tag : pred_structures) { + if (tag.contains("")) { + String content = cell_contents.get(td_index); + html.append(""); + html.append(content); + html.append(""); + td_index++; + continue; + } + html.append(tag); + } + return html.toString(); + } + + + public static class Builder { + private TableStructureModel tableStructureModel; + private OcrCommonRecModel textRecModel; + private OcrDirectionModel directionModel; + private OcrCommonDetModel textDetector; + + public Builder withStructureModel(TableStructureModel model) { + this.tableStructureModel = model; + return this; + } + + public Builder withTextRecModel(OcrCommonRecModel model) { + this.textRecModel = model; + return this; + } + + public Builder withDirectionModel(OcrDirectionModel model) { + this.directionModel = model; + return this; + } + + public Builder withTextDetModel(OcrCommonDetModel model) { + this.textDetector = model; + return this; + } + + public TableRecognizer build() { + if (this.tableStructureModel == null) { + throw new IllegalStateException("tableStructureModel 未设置"); + } + if (this.textDetector == null) { + throw new IllegalStateException("textDetector 未设置"); + } + if (this.textRecModel == null) { + throw new IllegalStateException("textRecModel 未设置"); + } + return new TableRecognizer(this); + } + } + +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/TableStructureModel.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/TableStructureModel.java new file mode 100644 index 0000000..a1ea4e3 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/TableStructureModel.java @@ -0,0 +1,63 @@ +package cn.smartjavaai.ocr.model.table; + +import ai.djl.modality.cv.Image; +import cn.smartjavaai.common.entity.R; +import cn.smartjavaai.ocr.config.OcrDetModelConfig; +import cn.smartjavaai.ocr.config.TableStructureConfig; +import cn.smartjavaai.ocr.entity.OcrBox; +import cn.smartjavaai.ocr.entity.OcrItem; +import cn.smartjavaai.ocr.entity.TableStructureResult; + +import java.awt.image.BufferedImage; +import java.util.List; + +/** + * 表格结构识别模型 + * @author dwj + */ +public interface TableStructureModel extends AutoCloseable{ + + /** + * 加载模型 + * @param config + */ + void loadModel(TableStructureConfig config); + + + /** + * 表格结构检测 + * @param image + * @return + */ + default R detect(BufferedImage image){ + throw new UnsupportedOperationException("默认不支持该功能"); + } + + /** + * 表格结构检测 + * @param imagePath 图片路径 + * @return + */ + default R detect(String imagePath) { + throw new UnsupportedOperationException("默认不支持该功能"); + } + + /** + * 表格结构检测 + * @param imageData 图片字节数组 + * @return + */ + default R detect(byte[] imageData) { + throw new UnsupportedOperationException("默认不支持该功能"); + } + + + /** + * 表格结构检测 + * @param image DJL Image + * @return + */ + default R detect(Image image){ + throw new UnsupportedOperationException("默认不支持该功能"); + } +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/criteria/StructureCriteriaFactory.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/criteria/StructureCriteriaFactory.java new file mode 100644 index 0000000..b54afe8 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/criteria/StructureCriteriaFactory.java @@ -0,0 +1,60 @@ +package cn.smartjavaai.ocr.model.table.criteria; + +import ai.djl.Device; +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.output.DetectedObjects; +import ai.djl.repository.zoo.Criteria; +import ai.djl.training.util.ProgressBar; +import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.ocr.config.TableStructureConfig; +import cn.smartjavaai.ocr.entity.OcrItem; +import cn.smartjavaai.ocr.entity.TableStructureResult; +import cn.smartjavaai.ocr.enums.TableStructureModelEnum; +import cn.smartjavaai.ocr.model.table.translator.TableStructTranslator; + +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author dwj + * @date 2025/7/10 + */ +public class StructureCriteriaFactory { + + + public static Criteria createCriteria(TableStructureConfig config) { + Device device = null; + if(!Objects.isNull(config.getDevice())){ + device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId()); + } + Criteria criteria = null; + if(config.getModelEnum() == TableStructureModelEnum.SLANET){ + criteria = + Criteria.builder() + .optEngine("OnnxRuntime") + .setTypes(Image.class, TableStructureResult.class) + .optModelPath(Paths.get(config.getModelPath())) + .optOption("removePass", "repeated_fc_relu_fuse_pass") + .optDevice(device) + .optTranslator(new TableStructTranslator()) + .optProgress(new ProgressBar()) + .build(); + }else if(config.getModelEnum() == TableStructureModelEnum.SLANET_PLUS){ + criteria = + Criteria.builder() + .optEngine("OnnxRuntime") + .setTypes(Image.class, TableStructureResult.class) + .optModelPath(Paths.get(config.getModelPath())) + .optOption("removePass", "repeated_fc_relu_fuse_pass") + .optDevice(device) + .optTranslator(new TableStructTranslator()) + .optProgress(new ProgressBar()) + .build(); + } + return criteria; + } + + +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/translator/TableStructTranslator.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/translator/TableStructTranslator.java new file mode 100644 index 0000000..08041ef --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/model/table/translator/TableStructTranslator.java @@ -0,0 +1,198 @@ +package cn.smartjavaai.ocr.model.table.translator; + +import ai.djl.Model; +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.output.BoundingBox; +import ai.djl.modality.cv.output.Rectangle; +import ai.djl.modality.cv.util.NDImageUtils; +import ai.djl.ndarray.NDArray; +import ai.djl.ndarray.NDList; +import ai.djl.ndarray.index.NDIndex; +import ai.djl.ndarray.types.DataType; +import ai.djl.ndarray.types.Shape; +import ai.djl.translate.Batchifier; +import ai.djl.translate.Translator; +import ai.djl.translate.TranslatorContext; +import ai.djl.util.Utils; +import cn.smartjavaai.common.entity.Point; +import cn.smartjavaai.ocr.entity.OcrBox; +import cn.smartjavaai.ocr.entity.OcrItem; +import cn.smartjavaai.ocr.entity.TableStructureResult; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 表格识别的前后处理 + */ +public class TableStructTranslator implements Translator { + + private final int maxLength = 488; + private int height; + private int width; + private float scale = 1.0f; + private float xScale; + private float yScale; + private List dict; + private String beg_str = "sos"; + private String end_str = "eos"; + private List td_token = new ArrayList<>(); + + @Override + public void prepare(TranslatorContext ctx) throws IOException { + Model model = ctx.getModel(); + try (InputStream is = model.getArtifact("table_structure_dict_ch.txt").openStream()) { + dict = Utils.readLines(is, false); + dict.add(0,beg_str); + if(dict.contains("")) + dict.remove(""); + if(!dict.contains("")) + dict.add(""); + dict.add(end_str); + } + + td_token.add(""); + td_token.add(""); + } + + @Override + public NDList processInput(TranslatorContext ctx, Image input) { + NDArray img = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR); + height = input.getHeight(); + width = input.getWidth(); + + img = ResizeTableImage(img, height, width, maxLength); + img = PaddingTableImage(ctx, img, maxLength); + + img = img.transpose(2, 0, 1).div(255).flip(0); + img = NDImageUtils.normalize( + img, new float[]{0.485f, 0.456f, 0.406f}, new float[]{0.229f, 0.224f, 0.225f}); + img = img.expandDims(0); + return new NDList(img); + } + + @Override + public TableStructureResult processOutput(TranslatorContext ctx, NDList list) { + NDArray bbox_preds = list.get(0); + NDArray structure_probs = list.get(1); + + NDArray structure_idx = structure_probs.argMax(2); + structure_probs = structure_probs.max(new int[]{2}); + + List> structure_batch_list = new ArrayList<>(); + List> bbox_batch_list = new ArrayList<>(); + List> result_score_list = new ArrayList<>(); + + // get ignored tokens + int beg_idx = dict.indexOf(beg_str); + int end_idx = dict.indexOf(end_str); + + long batch_size = structure_idx.size(0); + for (int batch_idx = 0; batch_idx < batch_size; batch_idx++) { + List structure_list = new ArrayList<>(); + List bbox_list = new ArrayList<>(); + List score_list = new ArrayList<>(); + + long len = structure_idx.get(batch_idx).size(); + for (int idx = 0; idx < len; idx++) { + int char_idx = (int) structure_idx.get(batch_idx).get(idx).toLongArray()[0]; + if (idx > 0 && char_idx == end_idx) { + break; + } +// if (char_idx == beg_idx || char_idx == end_idx) { +// continue; +// } + String text = dict.get(char_idx); + if(td_token.indexOf(text)>-1){ + NDArray bbox = bbox_preds.get(batch_idx, idx); +// bbox.set(new NDIndex("0::2"), bbox.get(new NDIndex("0::2"))); +// bbox.set(new NDIndex("1::2"), bbox.get(new NDIndex("1::2"))); + bbox_list.add(bbox); + } + structure_list.add(text); + score_list.add(structure_probs.get(batch_idx, idx)); + } + + structure_batch_list.add(structure_list); // structure_str + bbox_batch_list.add(bbox_list); + result_score_list.add(score_list); + } + List structure_str_list =structure_batch_list.get(0); + List bbox_list = bbox_batch_list.get(0); + List score_list = result_score_list.get(0); + + structure_str_list.add(0,""); + structure_str_list.add(1,""); + structure_str_list.add(2,""); + structure_str_list.add("
"); + structure_str_list.add(""); + structure_str_list.add(""); + + List ocrItemList = new ArrayList<>(); + + for (int i = 0; i < bbox_list.size(); i++) { + NDArray box = bbox_list.get(i); + float[] arr = new float[4]; + arr[0] = box.get(new NDIndex("0::2")).min().toFloatArray()[0]; + arr[1] = box.get(new NDIndex("1::2")).min().toFloatArray()[0]; + arr[2] = box.get(new NDIndex("0::2")).max().toFloatArray()[0]; + arr[3] = box.get(new NDIndex("1::2")).max().toFloatArray()[0]; + + Point topLeft = new Point(arr[0] * xScale * width, arr[1] * yScale * height); + Point topRight = new Point(arr[2] * xScale * width, arr[1] * yScale * height); + Point bottomRight = new Point(arr[2] * xScale * width, arr[3] * yScale * height); + Point bottomLeft = new Point(arr[0] * xScale * width, arr[3] * yScale * height); + + + OcrBox ocrBox = new OcrBox(topLeft, topRight, bottomRight, bottomLeft); + //String tag = structure_str_list.get(i + 3); // 前面加了 所以偏移+3 + float score = score_list.get(i).toFloatArray()[0]; // 获取每个结构token的得分 + OcrItem item = new OcrItem(); + item.setOcrBox(ocrBox); + item.setScore(score); + //item.setTableTag(tag); + ocrItemList.add(item); + } + return new TableStructureResult(ocrItemList, structure_str_list); + } + + @Override + public Batchifier getBatchifier() { + return null; + } + + private NDArray ResizeTableImage(NDArray img, int height, int width, int maxLen) { + int localMax = Math.max(height, width); + float ratio = maxLen * 1.0f / localMax; + int resize_h = (int) (height * ratio); + int resize_w = (int) (width * ratio); + scale = ratio; + + if(width > height){ + xScale = 1f; + yScale = (float)width /(float)height; + } else{ + xScale = (float)height /(float)width; + yScale = 1f; + } + + img = NDImageUtils.resize(img, resize_w, resize_h); + return img; + } + + private NDArray PaddingTableImage(TranslatorContext ctx, NDArray img, int maxLen) { + NDArray paddingImg = ctx.getNDManager().zeros(new Shape(maxLen, maxLen, 3), DataType.UINT8); + paddingImg.set( + new NDIndex("0:" + img.getShape().get(0) + ",0:" + img.getShape().get(1) + ",:"), img); + return paddingImg; + } + +} diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/ConvertHtml2Excel.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/ConvertHtml2Excel.java new file mode 100644 index 0000000..e868545 --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/ConvertHtml2Excel.java @@ -0,0 +1,233 @@ +package cn.smartjavaai.ocr.utils; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Auther: xiaoqiang + * @Date: 2020/12/9 9:16 + * @Description: + */ +public class ConvertHtml2Excel { + + /** + * html表格转excel + * + * @param tableHtml 如 + *
+ * .. + *
+ * @return + */ + public static HSSFWorkbook table2Excel(String tableHtml) { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + List crossRowEleMetaLs = new ArrayList<>(); + int rowIndex = 0; + try { + Document data = DocumentHelper.parseText(tableHtml); + // 生成表头 + Element thead = data.getRootElement().element("thead"); + HSSFCellStyle titleStyle = getTitleStyle(wb); + int ls=0;//列数 + if (thead != null) { + List trLs = thead.elements("tr"); + for (Element trEle : trLs) { + HSSFRow row = sheet.createRow(rowIndex); + List thLs = trEle.elements("td"); + ls=thLs.size(); + makeRowCell(thLs, rowIndex, row, 0, titleStyle, crossRowEleMetaLs); + rowIndex++; + } + } + // 生成表体 + Element tbody = data.getRootElement().element("tbody"); + HSSFCellStyle contentStyle = getContentStyle(wb); + if (tbody != null) { + List trLs = tbody.elements("tr"); + for (Element trEle : trLs) { + HSSFRow row = sheet.createRow(rowIndex); + List thLs = trEle.elements("th"); + int cellIndex = makeRowCell(thLs, rowIndex, row, 0, titleStyle, crossRowEleMetaLs); + List tdLs = trEle.elements("td"); + makeRowCell(tdLs, rowIndex, row, cellIndex, contentStyle, crossRowEleMetaLs); + rowIndex++; + } + } + // 合并表头 + for (CrossRangeCellMeta crcm : crossRowEleMetaLs) { + sheet.addMergedRegion(new CellRangeAddress(crcm.getFirstRow(), crcm.getLastRow(), crcm.getFirstCol(), crcm.getLastCol())); + setRegionStyle(sheet, new CellRangeAddress(crcm.getFirstRow(), crcm.getLastRow(), crcm.getFirstCol(), crcm.getLastCol()),titleStyle); + } + for(int i=0;i tdLs, int rowIndex, HSSFRow row, int startCellIndex, HSSFCellStyle cellStyle, + List crossRowEleMetaLs) { + int i = startCellIndex; + for (int eleIndex = 0; eleIndex < tdLs.size(); i++, eleIndex++) { + int captureCellSize = getCaptureCellSize(rowIndex, i, crossRowEleMetaLs); + while (captureCellSize > 0) { + for (int j = 0; j < captureCellSize; j++) {// 当前行跨列处理(补单元格) + row.createCell(i); + i++; + } + captureCellSize = getCaptureCellSize(rowIndex, i, crossRowEleMetaLs); + } + Element thEle = tdLs.get(eleIndex); + String val = thEle.getTextTrim(); + if (StringUtils.isBlank(val)) { + Element e = thEle.element("a"); + if (e != null) { + val = e.getTextTrim(); + } + } + HSSFCell c = row.createCell(i); + if (NumberUtils.isNumber(val)) { + c.setCellValue(Double.parseDouble(val)); + c.setCellType(CellType.NUMERIC); + } else { + c.setCellValue(val); + } + int rowSpan = NumberUtils.toInt(thEle.attributeValue("rowspan"), 1); + int colSpan = NumberUtils.toInt(thEle.attributeValue("colspan"), 1); + c.setCellStyle(cellStyle); + if (rowSpan > 1 || colSpan > 1) { // 存在跨行或跨列 + crossRowEleMetaLs.add(new CrossRangeCellMeta(rowIndex, i, rowSpan, colSpan)); + } + if (colSpan > 1) {// 当前行跨列处理(补单元格) + for (int j = 1; j < colSpan; j++) { + i++; + row.createCell(i); + } + } + } + return i; + } + + /** + * 设置合并单元格的边框样式 + * + * @param sheet + * @param region + * @param cs + */ + public static void setRegionStyle(HSSFSheet sheet, CellRangeAddress region, HSSFCellStyle cs) { + for (int i = region.getFirstRow(); i <= region.getLastRow(); i++) { + HSSFRow row = sheet.getRow(i); + for (int j = region.getFirstColumn(); j <= region.getLastColumn(); j++) { + HSSFCell cell = row.getCell(j); + cell.setCellStyle(cs); + } + } + } + + /** + * 获得因rowSpan占据的单元格 + * + * @param rowIndex 行号 + * @param colIndex 列号 + * @param crossRowEleMetaLs 跨行列元数据 + * @return 当前行在某列需要占据单元格 + */ + private static int getCaptureCellSize(int rowIndex, int colIndex, List crossRowEleMetaLs) { + int captureCellSize = 0; + for (CrossRangeCellMeta crossRangeCellMeta : crossRowEleMetaLs) { + if (crossRangeCellMeta.getFirstRow() < rowIndex && crossRangeCellMeta.getLastRow() >= rowIndex) { + if (crossRangeCellMeta.getFirstCol() <= colIndex && crossRangeCellMeta.getLastCol() >= colIndex) { + captureCellSize = crossRangeCellMeta.getLastCol() - colIndex + 1; + } + } + } + return captureCellSize; + } + + /** + * 获得标题样式 + * + * @param workbook + * @return + */ + private static HSSFCellStyle getTitleStyle(HSSFWorkbook workbook) { + //short titlebackgroundcolor = IndexedColors.GREY_25_PERCENT.index; + short fontSize = 12; + String fontName = "宋体"; + HSSFCellStyle style = workbook.createCellStyle(); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setAlignment(HorizontalAlignment.CENTER); + style.setBorderBottom(BorderStyle.THIN); //下边框 + style.setBorderLeft(BorderStyle.THIN);//左边框 + style.setBorderTop(BorderStyle.THIN);//上边框 + style.setBorderRight(BorderStyle.THIN);//右边框 + //style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + //style.setFillForegroundColor(titlebackgroundcolor);// 背景色 + + HSSFFont font = workbook.createFont(); + font.setFontName(fontName); + font.setFontHeightInPoints(fontSize); + font.setBold(true); + style.setFont(font); + return style; + } + + /** + * 获得内容样式 + * + * @param wb + * @return + */ + private static HSSFCellStyle getContentStyle(HSSFWorkbook wb) { + short fontSize = 12; + String fontName = "宋体"; + HSSFCellStyle style = wb.createCellStyle(); + style.setBorderBottom(BorderStyle.THIN); //下边框 + style.setBorderLeft(BorderStyle.THIN);//左边框 + style.setBorderTop(BorderStyle.THIN);//上边框 + style.setBorderRight(BorderStyle.THIN);//右边框 + HSSFFont font = wb.createFont(); + font.setFontName(fontName); + font.setFontHeightInPoints(fontSize); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER);//水平居中 + style.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中 + style.setWrapText(true); + return style; + } +} + diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/CrossRangeCellMeta.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/CrossRangeCellMeta.java new file mode 100644 index 0000000..5e3150c --- /dev/null +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/CrossRangeCellMeta.java @@ -0,0 +1,42 @@ +package cn.smartjavaai.ocr.utils; + +/** + * @Auther: xiaoqiang + * @Date: 2020/12/9 9:17 + * @Description: + */ +public class CrossRangeCellMeta { + public CrossRangeCellMeta(int firstRowIndex, int firstColIndex, int rowSpan, int colSpan) { + super(); + this.firstRowIndex = firstRowIndex; + this.firstColIndex = firstColIndex; + this.rowSpan = rowSpan; + this.colSpan = colSpan; + } + + private int firstRowIndex; + private int firstColIndex; + private int rowSpan;// 跨越行数 + private int colSpan;// 跨越列数 + + public int getFirstRow() { + return firstRowIndex; + } + + public int getLastRow() { + return firstRowIndex + rowSpan - 1; + } + + public int getFirstCol() { + return firstColIndex; + } + + public int getLastCol() { + return firstColIndex + colSpan - 1; + } + + public int getColSpan(){ + return colSpan; + } +} + diff --git a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/OcrUtils.java b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/OcrUtils.java index 6039b4f..797181b 100644 --- a/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/OcrUtils.java +++ b/smartjavaai-ocr/src/main/java/cn/smartjavaai/ocr/utils/OcrUtils.java @@ -11,14 +11,12 @@ import ai.djl.ndarray.NDManager; import ai.djl.opencv.OpenCVImageFactory; import cn.smartjavaai.common.entity.*; import cn.smartjavaai.common.entity.Point; -import cn.smartjavaai.ocr.entity.OcrBox; -import cn.smartjavaai.ocr.entity.OcrInfo; -import cn.smartjavaai.ocr.entity.OcrItem; -import cn.smartjavaai.ocr.entity.RotatedBoxCompX; +import cn.smartjavaai.ocr.entity.*; import cn.smartjavaai.ocr.enums.AngleEnum; import cn.smartjavaai.ocr.opencv.OcrNDArrayUtils; import cn.smartjavaai.ocr.opencv.OcrOpenCVUtils; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; import org.opencv.core.Mat; import org.opencv.core.Scalar; import org.opencv.imgproc.Imgproc; @@ -26,10 +24,8 @@ import org.opencv.imgproc.Imgproc; import java.awt.*; import java.awt.image.BufferedImage; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Iterator; +import java.util.*; import java.util.List; -import java.util.Objects; /** * @author dwj @@ -42,27 +38,41 @@ public class OcrUtils { /** * 转换为OcrBox * @param dt_boxes - * @param img * @return */ - public static List convertToOcrBox(NDList dt_boxes, Image img){ - if(Objects.isNull(dt_boxes) || dt_boxes.size() == 0){ - return null; - } - List boxList = new ArrayList(); - for(NDArray box : dt_boxes){ + public static List convertToOcrBox(NDList dt_boxes) { + List boxList = new ArrayList<>(); + for (NDArray box : dt_boxes) { float[] pointsArr = box.toFloatArray(); - //log.debug("points: {}", pointsArr); - float[] lt = java.util.Arrays.copyOfRange(pointsArr, 0, 2); - float[] rt = java.util.Arrays.copyOfRange(pointsArr, 2, 4); - float[] rb = java.util.Arrays.copyOfRange(pointsArr, 4, 6); - float[] lb = java.util.Arrays.copyOfRange(pointsArr, 6, 8); - OcrBox ocrBox = new OcrBox(new Point(lt[0], lt[1]), new Point(rt[0], rt[1]), new Point(rb[0], rb[1]), new Point(lb[0], lb[1])); + OcrBox ocrBox = new OcrBox( + new Point(pointsArr[0], pointsArr[1]), + new Point(pointsArr[2], pointsArr[3]), + new Point(pointsArr[4], pointsArr[5]), + new Point(pointsArr[6], pointsArr[7]) + ); boxList.add(ocrBox); } return boxList; } + /** + * 转换为OcrBox + * @param dt_boxes + * @return + */ + public static List> convertToOcrBox(List ndLists) { + if (ndLists == null || ndLists.isEmpty()) { + return Collections.emptyList(); + } + List> boxLists = new ArrayList<>(); + for (NDList dt_boxes : ndLists) { + boxLists.add(convertToOcrBox(dt_boxes)); + } + return boxLists; + } + + + /** * 欧式距离计算 * @@ -140,7 +150,6 @@ public class OcrUtils { if(Objects.isNull(lines) || lines.size() == 0){ return null; } - List detectionInfoList = new ArrayList(); List> lineList = new ArrayList>(); String fullText = ""; for(ArrayList boxList : lines){ @@ -165,6 +174,36 @@ public class OcrUtils { return new OcrInfo(lineList, fullText); } + public static OcrInfo convertRotatedBoxesToOcrItems(List rotatedBoxes) { + OcrInfo ocrInfo = new OcrInfo(); + List ocrItems = new ArrayList<>(); + StringBuilder fullText = new StringBuilder(); + for (RotatedBox rotatedBox : rotatedBoxes) { + NDArray box = rotatedBox.getBox(); + float[] points = box.toFloatArray(); + Point topLeft = new Point(points[0], points[1]); + Point topRight = new Point(points[2], points[3]); + Point bottomRight = new Point(points[4], points[5]); + Point bottomLeft = new Point(points[6], points[7]); + + OcrBox ocrBox = new OcrBox(topLeft, topRight, bottomRight, bottomLeft); + String text = rotatedBox.getText(); + + OcrItem item = new OcrItem(); + item.setOcrBox(ocrBox); + item.setText(text); + ocrItems.add(item); + fullText.append(text + " "); + } + if (fullText.length() > 0) { + fullText.deleteCharAt(fullText.length() - 1); + } + ocrInfo.setOcrItemList(ocrItems); + ocrInfo.setFullText(fullText.toString()); + return ocrInfo; + } + + /** * 放射变换+裁剪 @@ -235,26 +274,28 @@ public class OcrUtils { // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角 BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); g.setStroke(bStroke); - for(List ocrItemList : ocrInfo.getLineList()){ - for(OcrItem item : ocrItemList){ - OcrBox box = item.getOcrBox(); - int[] xPoints = { - (int)box.getTopLeft().getX(), - (int)box.getTopRight().getX(), - (int)box.getBottomRight().getX(), - (int)box.getBottomLeft().getX(), - (int)box.getTopLeft().getX() - }; - int[] yPoints = { - (int)box.getTopLeft().getY(), - (int)box.getTopRight().getY(), - (int)box.getBottomRight().getY(), - (int)box.getBottomLeft().getY(), - (int)box.getTopLeft().getY() - }; - g.drawPolyline(xPoints, yPoints, 5); - g.drawString(item.getText(), xPoints[0], yPoints[0]); - } + List ocrItemList = ocrInfo.getOcrItemList(); + if(CollectionUtils.isNotEmpty(ocrInfo.getLineList())){ + ocrItemList = ocrInfo.flattenLines(); + } + for(OcrItem item : ocrItemList){ + OcrBox box = item.getOcrBox(); + int[] xPoints = { + (int)box.getTopLeft().getX(), + (int)box.getTopRight().getX(), + (int)box.getBottomRight().getX(), + (int)box.getBottomLeft().getX(), + (int)box.getTopLeft().getX() + }; + int[] yPoints = { + (int)box.getTopLeft().getY(), + (int)box.getTopRight().getY(), + (int)box.getBottomRight().getY(), + (int)box.getBottomLeft().getY(), + (int)box.getTopLeft().getY() + }; + g.drawPolyline(xPoints, yPoints, 5); + g.drawString(item.getText(), xPoints[0], yPoints[0]); } } finally { g.dispose(); diff --git a/smartjavaai-translate/pom.xml b/smartjavaai-translate/pom.xml index 7438e4b..d856263 100644 --- a/smartjavaai-translate/pom.xml +++ b/smartjavaai-translate/pom.xml @@ -20,7 +20,7 @@ - 1.0.19 + 1.0.20 smartjavaai-translate SmartJavaAI https://github.com/geekwenjie/SmartJavaAI