From 90f7844669d58d92c0fe94c7e47f1fdb73bb400b Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 14:15:16 +0100 Subject: [PATCH 01/28] test install dptext_detr --- .github/workflows/mr_ci_text_spotting.yml | 40 +++++++++++++++++++++++ tests/test_text_spotting/test_import.py | 5 +++ 2 files changed, 45 insertions(+) create mode 100644 .github/workflows/mr_ci_text_spotting.yml create mode 100644 tests/test_text_spotting/test_import.py diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml new file mode 100644 index 00000000..7082b423 --- /dev/null +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -0,0 +1,40 @@ +--- +name: Units Tests + +on: [push] + +jobs: + + unit-tests: + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + python-version: [3.9, 3.12] + fail-fast: false + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - name: Update pip + run: | + python -m ensurepip + python -m pip install --upgrade pip + + - name: Install dependencies (DPText_DETR) + run: | + python -m pip install ".[dev]" + python -m pip install 'git+https://github.com/facebookresearch/detectron2.git' + python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR + python -m pip install pytest-cov + + - name: Test with pytest + run: | + python -m pytest ./tests/test_text_spotting/ diff --git a/tests/test_text_spotting/test_import.py b/tests/test_text_spotting/test_import.py new file mode 100644 index 00000000..8ec103f8 --- /dev/null +++ b/tests/test_text_spotting/test_import.py @@ -0,0 +1,5 @@ + +def test_import(): + from mapreader import ( + DPTextDETRRunner, + ) \ No newline at end of file From 8e004883fea941fb3c0c8588814c50c7dd571803 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 14:46:56 +0100 Subject: [PATCH 02/28] use detectron2 tests to set up install detectron2 --- .github/workflows/mr_ci_text_spotting.yml | 61 ++++++++++++++++------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 7082b423..a26260a8 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -1,25 +1,33 @@ --- -name: Units Tests +name: Units Tests - Text Spotting on: [push] +# Run linter with github actions for quick feedbacks. jobs: - unit-tests: + macos_tests: + runs-on: macos-latest + # run on PRs, or commits to facebookresearch (not internal) strategy: - matrix: - os: [ubuntu-latest, windows-latest] - python-version: [3.9, 3.12] fail-fast: false - - runs-on: ${{ matrix.os }} + matrix: + torch: ["1.13.1", "2.2.2"] + include: + - torch: "1.13.1" + torchvision: "0.14.1" + - torch: "2.2.2" + torchvision: "0.17.2" + python-version: ["3.9", "3.12"] + env: + # point datasets to ~/.torch so it's cached by CI + DETECTRON2_DATASETS: ~/.torch/datasets steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 2 + - name: Checkout + uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} @@ -28,13 +36,30 @@ jobs: python -m ensurepip python -m pip install --upgrade pip - - name: Install dependencies (DPText_DETR) + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: | + ${{ env.pythonLocation }}/lib/${{ matrix.python-version }}/site-packages + ~/.torch + key: ${{ runner.os }}-torch${{ matrix.torch }}-${{ hashFiles('setup.py') }}-20220119 + + - name: Install dependencies run: | - python -m pip install ".[dev]" - python -m pip install 'git+https://github.com/facebookresearch/detectron2.git' - python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR - python -m pip install pytest-cov + python -m pip install -U pip + python -m pip install wheel ninja opencv-python-headless onnx pytest-xdist + python -m pip install torch==${{matrix.torch}} torchvision==${{matrix.torchvision}} -f https://download.pytorch.org/whl/torch_stable.html + # install from github to get latest; install iopath first since fvcore depends on it + python -m pip install -U 'git+https://github.com/facebookresearch/iopath' + python -m pip install -U 'git+https://github.com/facebookresearch/fvcore' + wget https://raw.githubusercontent.com/pytorch/pytorch/master/torch/utils/collect_env.py + python collect_env.py - - name: Test with pytest + - name: Build and install run: | - python -m pytest ./tests/test_text_spotting/ + CC=clang CXX=clang++ python -m pip install -e 'git+https://github.com/facebookresearch/detectron2.git' + python -m detectron2.utils.collect_env + python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR + + - name: Run unittests + run: python -m pytest tests/test_text_spotting/ From 3ecc6d81fa96381141d86e29bb4f91ef55c647c3 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 14:53:52 +0100 Subject: [PATCH 03/28] fix -e --- .github/workflows/mr_ci_text_spotting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index a26260a8..446fa147 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -57,7 +57,7 @@ jobs: - name: Build and install run: | - CC=clang CXX=clang++ python -m pip install -e 'git+https://github.com/facebookresearch/detectron2.git' + CC=clang CXX=clang++ python -m pip install 'git+https://github.com/facebookresearch/detectron2.git' python -m detectron2.utils.collect_env python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR From 25be6522b01f63c97dcef0d1280c2481caf0d275 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 15:08:15 +0100 Subject: [PATCH 04/28] only test for python 3.9 --- .github/workflows/mr_ci_text_spotting.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 446fa147..c9b88f22 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -18,7 +18,7 @@ jobs: torchvision: "0.14.1" - torch: "2.2.2" torchvision: "0.17.2" - python-version: ["3.9", "3.12"] + env: # point datasets to ~/.torch so it's cached by CI DETECTRON2_DATASETS: ~/.torch/datasets @@ -26,10 +26,10 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.9 - name: Update pip run: | @@ -40,7 +40,7 @@ jobs: uses: actions/cache@v2 with: path: | - ${{ env.pythonLocation }}/lib/${{ matrix.python-version }}/site-packages + ${{ env.pythonLocation }}/lib/python3.9/site-packages ~/.torch key: ${{ runner.os }}-torch${{ matrix.torch }}-${{ hashFiles('setup.py') }}-20220119 @@ -59,6 +59,7 @@ jobs: run: | CC=clang CXX=clang++ python -m pip install 'git+https://github.com/facebookresearch/detectron2.git' python -m detectron2.utils.collect_env + python -m pip install ".[dev]" python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR - name: Run unittests From 4b74728df0caa6d389ab8e2a65f93f6a2a61873a Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 16:04:33 +0100 Subject: [PATCH 05/28] add initial tests for all 3 pipelines --- .github/workflows/mr_ci_text_spotting.yml | 23 ++++++++++-- test_text_spotting/test_deepsolo_runner.py | 41 ++++++++++++++++++++++ test_text_spotting/test_dptext_runner.py | 41 ++++++++++++++++++++++ test_text_spotting/test_maptext_runner.py | 41 ++++++++++++++++++++++ tests/test_text_spotting/test_import.py | 5 --- 5 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 test_text_spotting/test_deepsolo_runner.py create mode 100644 test_text_spotting/test_dptext_runner.py create mode 100644 test_text_spotting/test_maptext_runner.py delete mode 100644 tests/test_text_spotting/test_import.py diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index c9b88f22..4966b4fd 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -60,7 +60,24 @@ jobs: CC=clang CXX=clang++ python -m pip install 'git+https://github.com/facebookresearch/detectron2.git' python -m detectron2.utils.collect_env python -m pip install ".[dev]" - python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR - - name: Run unittests - run: python -m pytest tests/test_text_spotting/ + - name: Install DPText-DETR and run unittests + run: | + git clone https://github.com/maps-as-data/DPText-DETR.git + python -m pip install DPText-DETR # Install DPText-DETR + wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth + python -m pytest test_text_spotting/test_dptext_runner.py + + - name: Install DeepSolo and run unittests + run: | + git clone https://github.com/maps-as-data/DeepSolo.git + python -m pip install DeepSolo # Install DeepSolo + wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth + python -m pytest test_text_spotting/test_deepsolo_runner.py + + - name: Install MapTextPipeline and run unittests + run: | + git clone https://github.com/maps-as-data/MapTextPipeline.git + python -m pip install MapTextPipeline # Install MapTextPipeline + wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth + python -m pytest test_text_spotting/test_maptext_runner.py diff --git a/test_text_spotting/test_deepsolo_runner.py b/test_text_spotting/test_deepsolo_runner.py new file mode 100644 index 00000000..1edef9d2 --- /dev/null +++ b/test_text_spotting/test_deepsolo_runner.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import pathlib + +import pytest +from detectron2.engine import DefaultPredictor + +from mapreader import DeepSoloRunner +from mapreader.load import MapImages + + +@pytest.fixture +def sample_dir(): + return pathlib.Path(__file__).resolve().parent.parent / "sample_files" + + +@pytest.fixture +def init_dataframes(sample_dir, tmp_path): + """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. + Returns + ------- + tuple + path to parent and patch dataframes + """ + maps = MapImages(f"{sample_dir}/cropped_74488689.png") + maps.add_metadata(f"{sample_dir}/ts_downloaded_maps.csv") + maps.patchify_all(patch_size=3, path_save=tmp_path) # gives 9 patches + maps.add_center_coord(tree_level="parent") + maps.add_patch_polygons() + parent_df, patch_df = maps.convert_images() + return parent_df, patch_df + + +def test_dptext_init(init_dataframes): + parent_df, patch_df = init_dataframes + runner = DeepSoloRunner( + parent_df, + patch_df, + ) + assert isinstance(runner, DeepSoloRunner) + assert isinstance(runner.predictor, DefaultPredictor) diff --git a/test_text_spotting/test_dptext_runner.py b/test_text_spotting/test_dptext_runner.py new file mode 100644 index 00000000..01ae5cfa --- /dev/null +++ b/test_text_spotting/test_dptext_runner.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import pathlib + +import pytest +from detectron2.engine import DefaultPredictor + +from mapreader import DPTextDETRRunner +from mapreader.load import MapImages + + +@pytest.fixture +def sample_dir(): + return pathlib.Path(__file__).resolve().parent.parent / "sample_files" + + +@pytest.fixture +def init_dataframes(sample_dir, tmp_path): + """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. + Returns + ------- + tuple + path to parent and patch dataframes + """ + maps = MapImages(f"{sample_dir}/cropped_74488689.png") + maps.add_metadata(f"{sample_dir}/ts_downloaded_maps.csv") + maps.patchify_all(patch_size=3, path_save=tmp_path) # gives 9 patches + maps.add_center_coord(tree_level="parent") + maps.add_patch_polygons() + parent_df, patch_df = maps.convert_images() + return parent_df, patch_df + + +def test_dptext_init(init_dataframes): + parent_df, patch_df = init_dataframes + runner = DPTextDETRRunner( + parent_df, + patch_df, + ) + assert isinstance(runner, DPTextDETRRunner) + assert isinstance(runner.predictor, DefaultPredictor) diff --git a/test_text_spotting/test_maptext_runner.py b/test_text_spotting/test_maptext_runner.py new file mode 100644 index 00000000..d29a4854 --- /dev/null +++ b/test_text_spotting/test_maptext_runner.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import pathlib + +import pytest +from detectron2.engine import DefaultPredictor + +from mapreader import MapTextRunner +from mapreader.load import MapImages + + +@pytest.fixture +def sample_dir(): + return pathlib.Path(__file__).resolve().parent.parent / "sample_files" + + +@pytest.fixture +def init_dataframes(sample_dir, tmp_path): + """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. + Returns + ------- + tuple + path to parent and patch dataframes + """ + maps = MapImages(f"{sample_dir}/cropped_74488689.png") + maps.add_metadata(f"{sample_dir}/ts_downloaded_maps.csv") + maps.patchify_all(patch_size=3, path_save=tmp_path) # gives 9 patches + maps.add_center_coord(tree_level="parent") + maps.add_patch_polygons() + parent_df, patch_df = maps.convert_images() + return parent_df, patch_df + + +def test_dptext_init(init_dataframes): + parent_df, patch_df = init_dataframes + runner = MapTextRunner( + parent_df, + patch_df, + ) + assert isinstance(runner, MapTextRunner) + assert isinstance(runner.predictor, DefaultPredictor) diff --git a/tests/test_text_spotting/test_import.py b/tests/test_text_spotting/test_import.py deleted file mode 100644 index 8ec103f8..00000000 --- a/tests/test_text_spotting/test_import.py +++ /dev/null @@ -1,5 +0,0 @@ - -def test_import(): - from mapreader import ( - DPTextDETRRunner, - ) \ No newline at end of file From 02f0e7fd6bac0966ba01d46a79d5ccd9cae6b404 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 16:09:50 +0100 Subject: [PATCH 06/28] fix installs --- .github/workflows/mr_ci_text_spotting.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 4966b4fd..81aa4fc2 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -64,20 +64,20 @@ jobs: - name: Install DPText-DETR and run unittests run: | git clone https://github.com/maps-as-data/DPText-DETR.git - python -m pip install DPText-DETR # Install DPText-DETR + python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth python -m pytest test_text_spotting/test_dptext_runner.py - name: Install DeepSolo and run unittests run: | git clone https://github.com/maps-as-data/DeepSolo.git - python -m pip install DeepSolo # Install DeepSolo + python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth python -m pytest test_text_spotting/test_deepsolo_runner.py - name: Install MapTextPipeline and run unittests run: | git clone https://github.com/maps-as-data/MapTextPipeline.git - python -m pip install MapTextPipeline # Install MapTextPipeline + python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' # Install MapTextPipeline wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth python -m pytest test_text_spotting/test_maptext_runner.py From 92e579ab8d37ce666ce08d1ba699993e60d9fccf Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 16:14:38 +0100 Subject: [PATCH 07/28] fix path to sample files --- test_text_spotting/test_deepsolo_runner.py | 2 +- test_text_spotting/test_dptext_runner.py | 2 +- test_text_spotting/test_maptext_runner.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test_text_spotting/test_deepsolo_runner.py b/test_text_spotting/test_deepsolo_runner.py index 1edef9d2..00239717 100644 --- a/test_text_spotting/test_deepsolo_runner.py +++ b/test_text_spotting/test_deepsolo_runner.py @@ -11,7 +11,7 @@ @pytest.fixture def sample_dir(): - return pathlib.Path(__file__).resolve().parent.parent / "sample_files" + return pathlib.Path(__file__).resolve().parent.parent / "tests" / "sample_files" @pytest.fixture diff --git a/test_text_spotting/test_dptext_runner.py b/test_text_spotting/test_dptext_runner.py index 01ae5cfa..a23d3dd1 100644 --- a/test_text_spotting/test_dptext_runner.py +++ b/test_text_spotting/test_dptext_runner.py @@ -11,7 +11,7 @@ @pytest.fixture def sample_dir(): - return pathlib.Path(__file__).resolve().parent.parent / "sample_files" + return pathlib.Path(__file__).resolve().parent.parent / "tests" / "sample_files" @pytest.fixture diff --git a/test_text_spotting/test_maptext_runner.py b/test_text_spotting/test_maptext_runner.py index d29a4854..06d33a68 100644 --- a/test_text_spotting/test_maptext_runner.py +++ b/test_text_spotting/test_maptext_runner.py @@ -11,7 +11,7 @@ @pytest.fixture def sample_dir(): - return pathlib.Path(__file__).resolve().parent.parent / "sample_files" + return pathlib.Path(__file__).resolve().parent.parent / "tests" / "sample_files" @pytest.fixture From 06255aaa1a5201b215370c3d80ea5590eb4494a9 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 16:22:30 +0100 Subject: [PATCH 08/28] fix installation issues (hopefully) --- .github/workflows/mr_ci_text_spotting.yml | 18 +++++++++++++++--- test_text_spotting/test_deepsolo_runner.py | 4 ++++ test_text_spotting/test_dptext_runner.py | 3 +++ test_text_spotting/test_maptext_runner.py | 3 +++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 81aa4fc2..df6c6db0 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -61,23 +61,35 @@ jobs: python -m detectron2.utils.collect_env python -m pip install ".[dev]" - - name: Install DPText-DETR and run unittests + - name: Install DPText-DETR run: | git clone https://github.com/maps-as-data/DPText-DETR.git + python -m pip uninstall adet -y python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth + + - name: Run DPText-DETR unittests + run: | python -m pytest test_text_spotting/test_dptext_runner.py - - name: Install DeepSolo and run unittests + - name: Install DeepSolo run: | git clone https://github.com/maps-as-data/DeepSolo.git + python -m pip uninstall adet -y python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth + + - name: Run DeepSolo unittests + run: | python -m pytest test_text_spotting/test_deepsolo_runner.py - - name: Install MapTextPipeline and run unittests + - name: Install MapTextPipeline run: | git clone https://github.com/maps-as-data/MapTextPipeline.git + python -m pip uninstall adet -y python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' # Install MapTextPipeline wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth + + - name: Run MapTextPipeline unittests + run: | python -m pytest test_text_spotting/test_maptext_runner.py diff --git a/test_text_spotting/test_deepsolo_runner.py b/test_text_spotting/test_deepsolo_runner.py index 00239717..020b9e6f 100644 --- a/test_text_spotting/test_deepsolo_runner.py +++ b/test_text_spotting/test_deepsolo_runner.py @@ -2,7 +2,11 @@ import pathlib +import adet import pytest + +print(adet.__version__) + from detectron2.engine import DefaultPredictor from mapreader import DeepSoloRunner diff --git a/test_text_spotting/test_dptext_runner.py b/test_text_spotting/test_dptext_runner.py index a23d3dd1..26c7159f 100644 --- a/test_text_spotting/test_dptext_runner.py +++ b/test_text_spotting/test_dptext_runner.py @@ -2,9 +2,12 @@ import pathlib +import adet import pytest from detectron2.engine import DefaultPredictor +print(adet.__version__) + from mapreader import DPTextDETRRunner from mapreader.load import MapImages diff --git a/test_text_spotting/test_maptext_runner.py b/test_text_spotting/test_maptext_runner.py index 06d33a68..63922201 100644 --- a/test_text_spotting/test_maptext_runner.py +++ b/test_text_spotting/test_maptext_runner.py @@ -2,9 +2,12 @@ import pathlib +import adet import pytest from detectron2.engine import DefaultPredictor +print(adet.__version__) + from mapreader import MapTextRunner from mapreader.load import MapImages From cb5a964463b3a0607b80b7655fd4d1cdcbe2e632 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 16:27:12 +0100 Subject: [PATCH 09/28] fix --- .github/workflows/mr_ci_text_spotting.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index df6c6db0..6c2cef7a 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -64,7 +64,7 @@ jobs: - name: Install DPText-DETR run: | git clone https://github.com/maps-as-data/DPText-DETR.git - python -m pip uninstall adet -y + python -m pip uninstall AdelaiDet -y python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth @@ -75,7 +75,7 @@ jobs: - name: Install DeepSolo run: | git clone https://github.com/maps-as-data/DeepSolo.git - python -m pip uninstall adet -y + python -m pip uninstall AdelaiDet -y python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth @@ -86,7 +86,7 @@ jobs: - name: Install MapTextPipeline run: | git clone https://github.com/maps-as-data/MapTextPipeline.git - python -m pip uninstall adet -y + python -m pip uninstall AdelaiDet -y python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' # Install MapTextPipeline wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth From a991783889f2d1f23de6c817b668d9f8a71f9a63 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 16:34:35 +0100 Subject: [PATCH 10/28] force clang for install --- .github/workflows/mr_ci_text_spotting.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 6c2cef7a..da8c7cd9 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -65,7 +65,7 @@ jobs: run: | git clone https://github.com/maps-as-data/DPText-DETR.git python -m pip uninstall AdelaiDet -y - python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR + CC=clang CXX=clang++ python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth - name: Run DPText-DETR unittests @@ -76,7 +76,7 @@ jobs: run: | git clone https://github.com/maps-as-data/DeepSolo.git python -m pip uninstall AdelaiDet -y - python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo + CC=clang CXX=clang++ python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth - name: Run DeepSolo unittests @@ -87,7 +87,7 @@ jobs: run: | git clone https://github.com/maps-as-data/MapTextPipeline.git python -m pip uninstall AdelaiDet -y - python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' # Install MapTextPipeline + CC=clang CXX=clang++ python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' # Install MapTextPipeline wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth - name: Run MapTextPipeline unittests From e026d1bdabbaa4d65a1ba990789e100b0a7a39b8 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 16:48:08 +0100 Subject: [PATCH 11/28] try force reinstall --- .github/workflows/mr_ci_text_spotting.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index da8c7cd9..9aa3af36 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -64,8 +64,7 @@ jobs: - name: Install DPText-DETR run: | git clone https://github.com/maps-as-data/DPText-DETR.git - python -m pip uninstall AdelaiDet -y - CC=clang CXX=clang++ python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR + python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth - name: Run DPText-DETR unittests @@ -75,8 +74,7 @@ jobs: - name: Install DeepSolo run: | git clone https://github.com/maps-as-data/DeepSolo.git - python -m pip uninstall AdelaiDet -y - CC=clang CXX=clang++ python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo + python -m ppip install 'git+https://github.com/maps-as-data/DeepSolo.git' --no-deps --force-reinstall # Install DeepSolo wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth - name: Run DeepSolo unittests @@ -86,8 +84,7 @@ jobs: - name: Install MapTextPipeline run: | git clone https://github.com/maps-as-data/MapTextPipeline.git - python -m pip uninstall AdelaiDet -y - CC=clang CXX=clang++ python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' # Install MapTextPipeline + python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' --no-deps --force-reinstall # Install MapTextPipeline wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth - name: Run MapTextPipeline unittests From 1a021bf0420873c7e9eb37f22cdb77ae1f2c18d8 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Wed, 4 Sep 2024 16:51:10 +0100 Subject: [PATCH 12/28] typo --- .github/workflows/mr_ci_text_spotting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 9aa3af36..1f316939 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -74,7 +74,7 @@ jobs: - name: Install DeepSolo run: | git clone https://github.com/maps-as-data/DeepSolo.git - python -m ppip install 'git+https://github.com/maps-as-data/DeepSolo.git' --no-deps --force-reinstall # Install DeepSolo + python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' --no-deps --force-reinstall # Install DeepSolo wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth - name: Run DeepSolo unittests From 7f7a5907fe4211b5da30e2ec8530a5603000b0c9 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Thu, 5 Sep 2024 08:21:15 +0100 Subject: [PATCH 13/28] test install deepsolo first --- .github/workflows/mr_ci_text_spotting.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 1f316939..1818501b 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -61,20 +61,10 @@ jobs: python -m detectron2.utils.collect_env python -m pip install ".[dev]" - - name: Install DPText-DETR - run: | - git clone https://github.com/maps-as-data/DPText-DETR.git - python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR - wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth - - - name: Run DPText-DETR unittests - run: | - python -m pytest test_text_spotting/test_dptext_runner.py - - name: Install DeepSolo run: | git clone https://github.com/maps-as-data/DeepSolo.git - python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' --no-deps --force-reinstall # Install DeepSolo + python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth - name: Run DeepSolo unittests From 0893724c938f055a662c59a430080b07879b4182 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Thu, 5 Sep 2024 09:02:07 +0100 Subject: [PATCH 14/28] try excluding adelaidet cache --- .github/workflows/mr_ci_text_spotting.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 1818501b..0c986b73 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -37,10 +37,11 @@ jobs: python -m pip install --upgrade pip - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | - ${{ env.pythonLocation }}/lib/python3.9/site-packages + ${{ env.pythonLocation }}/lib/python3.9/site-packages/* + !${{ env.pythonLocation }}/lib/python3.9/site-packages/AdelaiDet* ~/.torch key: ${{ runner.os }}-torch${{ matrix.torch }}-${{ hashFiles('setup.py') }}-20220119 @@ -61,10 +62,21 @@ jobs: python -m detectron2.utils.collect_env python -m pip install ".[dev]" + - name: Install DPText-DETR + run: | + git clone https://github.com/maps-as-data/DPText-DETR.git + python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' --force-reinstall # Install DPText-DETR + wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth + + - name: Run DPText-DETR unittests + run: | + python -m pytest test_text_spotting/test_dptext_runner.py + + - name: Install DeepSolo run: | git clone https://github.com/maps-as-data/DeepSolo.git - python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo + python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' --force-reinstall # Install DeepSolo wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth - name: Run DeepSolo unittests @@ -74,7 +86,7 @@ jobs: - name: Install MapTextPipeline run: | git clone https://github.com/maps-as-data/MapTextPipeline.git - python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' --no-deps --force-reinstall # Install MapTextPipeline + python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' --force-reinstall # Install MapTextPipeline wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth - name: Run MapTextPipeline unittests From 12b1729d821cc32d801f376fe6b8d10143fc9033 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Thu, 5 Sep 2024 09:21:37 +0100 Subject: [PATCH 15/28] force numpy version to <2.0.0 --- .github/workflows/mr_ci_text_spotting.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 0c986b73..bbb62af5 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -49,6 +49,7 @@ jobs: run: | python -m pip install -U pip python -m pip install wheel ninja opencv-python-headless onnx pytest-xdist + python -m pip install numpy<2.0.0 python -m pip install torch==${{matrix.torch}} torchvision==${{matrix.torchvision}} -f https://download.pytorch.org/whl/torch_stable.html # install from github to get latest; install iopath first since fvcore depends on it python -m pip install -U 'git+https://github.com/facebookresearch/iopath' @@ -66,6 +67,7 @@ jobs: run: | git clone https://github.com/maps-as-data/DPText-DETR.git python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' --force-reinstall # Install DPText-DETR + python -m pip install numpy<2.0.0 wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth - name: Run DPText-DETR unittests @@ -77,6 +79,7 @@ jobs: run: | git clone https://github.com/maps-as-data/DeepSolo.git python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' --force-reinstall # Install DeepSolo + python -m pip install numpy<2.0.0 wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth - name: Run DeepSolo unittests @@ -87,6 +90,7 @@ jobs: run: | git clone https://github.com/maps-as-data/MapTextPipeline.git python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' --force-reinstall # Install MapTextPipeline + python -m pip install numpy<2.0.0 wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth - name: Run MapTextPipeline unittests From 38bc844459ce6fa8d0eaeead645f837c7e1e59c9 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Thu, 5 Sep 2024 09:44:11 +0100 Subject: [PATCH 16/28] force numpy to <2.0.0 --- .github/workflows/mr_ci_text_spotting.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index bbb62af5..d9381470 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -49,7 +49,7 @@ jobs: run: | python -m pip install -U pip python -m pip install wheel ninja opencv-python-headless onnx pytest-xdist - python -m pip install numpy<2.0.0 + python -m pip install "numpy<2.0.0" python -m pip install torch==${{matrix.torch}} torchvision==${{matrix.torchvision}} -f https://download.pytorch.org/whl/torch_stable.html # install from github to get latest; install iopath first since fvcore depends on it python -m pip install -U 'git+https://github.com/facebookresearch/iopath' @@ -67,7 +67,7 @@ jobs: run: | git clone https://github.com/maps-as-data/DPText-DETR.git python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' --force-reinstall # Install DPText-DETR - python -m pip install numpy<2.0.0 + python -m pip install "numpy<2.0.0" wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth - name: Run DPText-DETR unittests @@ -79,7 +79,7 @@ jobs: run: | git clone https://github.com/maps-as-data/DeepSolo.git python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' --force-reinstall # Install DeepSolo - python -m pip install numpy<2.0.0 + python -m pip install "numpy<2.0.0" wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth - name: Run DeepSolo unittests @@ -90,7 +90,7 @@ jobs: run: | git clone https://github.com/maps-as-data/MapTextPipeline.git python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' --force-reinstall # Install MapTextPipeline - python -m pip install numpy<2.0.0 + python -m pip install "numpy<2.0.0" wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth - name: Run MapTextPipeline unittests From fe999cfc4bcbcc4be36172bfecbb74d559464048 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Thu, 5 Sep 2024 09:47:40 +0100 Subject: [PATCH 17/28] force numpy to numpy==1.26.4 --- .github/workflows/mr_ci_text_spotting.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index d9381470..dde5e748 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -49,7 +49,7 @@ jobs: run: | python -m pip install -U pip python -m pip install wheel ninja opencv-python-headless onnx pytest-xdist - python -m pip install "numpy<2.0.0" + python -m pip install numpy==1.26.4 python -m pip install torch==${{matrix.torch}} torchvision==${{matrix.torchvision}} -f https://download.pytorch.org/whl/torch_stable.html # install from github to get latest; install iopath first since fvcore depends on it python -m pip install -U 'git+https://github.com/facebookresearch/iopath' @@ -67,7 +67,7 @@ jobs: run: | git clone https://github.com/maps-as-data/DPText-DETR.git python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' --force-reinstall # Install DPText-DETR - python -m pip install "numpy<2.0.0" + python -m pip install numpy==1.26.4 wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth - name: Run DPText-DETR unittests @@ -79,7 +79,7 @@ jobs: run: | git clone https://github.com/maps-as-data/DeepSolo.git python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' --force-reinstall # Install DeepSolo - python -m pip install "numpy<2.0.0" + python -m pip install numpy==1.26.4 wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth - name: Run DeepSolo unittests From 324d0fc15028d2e9ff24957fe494ed0c750aa765 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Thu, 5 Sep 2024 10:00:51 +0100 Subject: [PATCH 18/28] remove caching --- .github/workflows/mr_ci_text_spotting.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index dde5e748..e516e112 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -36,15 +36,6 @@ jobs: python -m ensurepip python -m pip install --upgrade pip - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: | - ${{ env.pythonLocation }}/lib/python3.9/site-packages/* - !${{ env.pythonLocation }}/lib/python3.9/site-packages/AdelaiDet* - ~/.torch - key: ${{ runner.os }}-torch${{ matrix.torch }}-${{ hashFiles('setup.py') }}-20220119 - - name: Install dependencies run: | python -m pip install -U pip From 1e5b49378fb64acc47a9d60aaed4bf50ff9df5c1 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Thu, 5 Sep 2024 16:54:18 +0100 Subject: [PATCH 19/28] add tests for 3 runners --- test_text_spotting/test_deepsolo_runner.py | 185 +++++++++++++++++- test_text_spotting/test_dptext_runner.py | 176 ++++++++++++++++- test_text_spotting/test_maptext_runner.py | 184 ++++++++++++++++- tests/sample_files/mapreader_text.png | Bin 0 -> 4234 bytes .../sample_files/mapreader_text_metadata.csv | 2 + 5 files changed, 520 insertions(+), 27 deletions(-) create mode 100644 tests/sample_files/mapreader_text.png create mode 100644 tests/sample_files/mapreader_text_metadata.csv diff --git a/test_text_spotting/test_deepsolo_runner.py b/test_text_spotting/test_deepsolo_runner.py index 020b9e6f..52860ea8 100644 --- a/test_text_spotting/test_deepsolo_runner.py +++ b/test_text_spotting/test_deepsolo_runner.py @@ -1,17 +1,21 @@ from __future__ import annotations +import os import pathlib import adet +import geopandas as gpd +import pandas as pd import pytest - -print(adet.__version__) - from detectron2.engine import DefaultPredictor +from detectron2.structures.instances import Instances from mapreader import DeepSoloRunner from mapreader.load import MapImages +print(adet.__version__) +ADET_PATH = pathlib.Path(adet.__path__[0]).resolve().parent + @pytest.fixture def sample_dir(): @@ -26,20 +30,181 @@ def init_dataframes(sample_dir, tmp_path): tuple path to parent and patch dataframes """ - maps = MapImages(f"{sample_dir}/cropped_74488689.png") - maps.add_metadata(f"{sample_dir}/ts_downloaded_maps.csv") - maps.patchify_all(patch_size=3, path_save=tmp_path) # gives 9 patches - maps.add_center_coord(tree_level="parent") - maps.add_patch_polygons() + maps = MapImages(f"{sample_dir}/mapreader_text.png") + maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") + maps.patchify_all(patch_size=800, path_save=tmp_path) + maps.check_georeferencing() parent_df, patch_df = maps.convert_images() return parent_df, patch_df -def test_dptext_init(init_dataframes): +@pytest.fixture +def init_runner(init_dataframes): + parent_df, patch_df = init_dataframes + runner = DeepSoloRunner( + patch_df, + parent_df=parent_df, + cfg_file=f"{ADET_PATH}/configs/R_50/IC15/finetune_150k_tt_mlt_13_15_textocr.yaml", + ) + return runner + + +def test_deepsolo_init(init_dataframes): parent_df, patch_df = init_dataframes runner = DeepSoloRunner( - parent_df, patch_df, + parent_df=parent_df, + cfg_file=f"{ADET_PATH}/configs/R_50/IC15/finetune_150k_tt_mlt_13_15_textocr.yaml", ) assert isinstance(runner, DeepSoloRunner) assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_deepsolo_init_str(init_dataframes, tmp_path): + parent_df, patch_df = init_dataframes + parent_df = parent_df.to_csv(f"{tmp_path}/parent_df.csv") + patch_df = patch_df.to_csv(f"{tmp_path}/patch_df.csv") + runner = DeepSoloRunner( + f"{tmp_path}/patch_df.csv", + parent_df=f"{tmp_path}/parent_df.csv", + cfg_file=f"{ADET_PATH}/configs/R_50/IC15/finetune_150k_tt_mlt_13_15_textocr.yaml", + ) + assert isinstance(runner, DeepSoloRunner) + assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_deepsolo_init_pathlib(init_dataframes, tmp_path): + parent_df, patch_df = init_dataframes + parent_df = parent_df.to_csv(f"{tmp_path}/parent_df.csv") + patch_df = patch_df.to_csv(f"{tmp_path}/patch_df.csv") + runner = DeepSoloRunner( + pathlib.Path(f"{tmp_path}/patch_df.csv"), + parent_df=pathlib.Path(f"{tmp_path}/parent_df.csv"), + cfg_file=f"{ADET_PATH}/configs/R_50/IC15/finetune_150k_tt_mlt_13_15_textocr.yaml", + ) + assert isinstance(runner, DeepSoloRunner) + assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_deepsolo_init_tsv(init_dataframes, tmp_path): + parent_df, patch_df = init_dataframes + parent_df = parent_df.to_csv(f"{tmp_path}/parent_df.tsv", sep="\t") + patch_df = patch_df.to_csv(f"{tmp_path}/patch_df.tsv", sep="\t") + runner = DeepSoloRunner( + f"{tmp_path}/patch_df.tsv", + parent_df=f"{tmp_path}/parent_df.tsv", + delimiter="\t", + cfg_file=f"{ADET_PATH}/configs/R_50/IC15/finetune_150k_tt_mlt_13_15_textocr.yaml", + ) + assert isinstance(runner, DeepSoloRunner) + assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_deepsolo_run_all(init_runner): + runner = init_runner + # dict + out = runner.run_all() + assert isinstance(out, dict) + assert "patch-0-0-800-40-#mapreader_text.png#.png" in out.keys() + assert isinstance(out["patch-0-0-800-40-#mapreader_text.png#.png"], list) + # dataframe + runner.patch_predictions = {} + out = runner.run_all(return_dataframe=True) + assert isinstance(out, pd.DataFrame) + assert set(out.columns) == set(["image_id", "geometry", "text", "score"]) + assert "patch-0-0-800-40-#mapreader_text.png#.png" in out["image_id"].values + + +def test_deepsolo_convert_to_parent(init_runner): + runner = init_runner + _ = runner.run_all() + # dict + out = runner.convert_to_parent_pixel_bounds() + assert isinstance(out, dict) + assert "mapreader_text.png" in out.keys() + assert isinstance(out["mapreader_text.png"], list) + # dataframe + runner.parent_predictions = {} + out = runner.convert_to_parent_pixel_bounds(return_dataframe=True) + assert isinstance(out, pd.DataFrame) + assert set(out.columns) == set( + ["image_id", "patch_id", "geometry", "text", "score"] + ) + assert "mapreader_text.png" in out["image_id"].values + + +def test_deepsolo_convert_to_parent_coords(init_runner): + runner = init_runner + _ = runner.run_all() + # dict + out = runner.convert_to_coords() + assert isinstance(out, dict) + assert "mapreader_text.png" in out.keys() + assert isinstance(out["mapreader_text.png"], list) + # dataframe + runner.parent_predictions = {} + out = runner.convert_to_coords(return_dataframe=True) + assert isinstance(out, gpd.GeoDataFrame) + assert set(out.columns) == set( + ["image_id", "patch_id", "geometry", "crs", "text", "score"] + ) + assert "mapreader_text.png" in out["image_id"].values + assert out.crs == runner.parent_df.crs + + +def test_deepsolo_deduplicate(sample_dir, tmp_path): + maps = MapImages(f"{sample_dir}/mapreader_text.png") + maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") + maps.patchify_all(patch_size=800, path_save=tmp_path, overlap=0.5) + maps.check_georeferencing() + parent_df, patch_df = maps.convert_images() + runner = DeepSoloRunner( + patch_df, + parent_df=parent_df, + cfg_file=f"{ADET_PATH}/configs/R_50/IC15/finetune_150k_tt_mlt_13_15_textocr.yaml", + ) + _ = runner.run_all() + out = runner.convert_to_parent_pixel_bounds(deduplicate=False) + len_before = len(out["mapreader_text.png"]) + runner.patch_predictions = {} + out_07 = runner.convert_to_parent_pixel_bounds(deduplicate=True) + len_07 = len(out_07["mapreader_text.png"]) + print(len_before, len_07) + assert len_before >= len_07 + runner.patch_predictions = {} + out_05 = runner.convert_to_parent_pixel_bounds(deduplicate=True, min_ioa=0.5) + len_05 = len(out_05["mapreader_text.png"]) + print(len_before, len_05) + assert len_before >= len_05 + assert len_07 >= len_05 + + +def test_deepsolo_run_on_image(init_runner): + runner = init_runner + out = runner.run_on_image( + runner.patch_df.iloc[0]["image_path"], return_outputs=True + ) + assert isinstance(out, dict) + assert "instances" in out.keys() + assert isinstance(out["instances"], Instances) + + +def test_deepsolo_save_to_geojson(init_runner, tmp_path): + runner = init_runner + _ = runner.run_all() + _ = runner.convert_to_coords() + runner.save_to_geojson(f"{tmp_path}/text.geojson") + assert os.path.exists(f"{tmp_path}/text.geojson") + gdf = gpd.read_file(f"{tmp_path}/text.geojson") + assert isinstance(gdf, gpd.GeoDataFrame) + assert set(gdf.columns) == set( + ["image_id", "patch_id", "geometry", "crs", "text", "score"] + ) diff --git a/test_text_spotting/test_dptext_runner.py b/test_text_spotting/test_dptext_runner.py index 26c7159f..f6d1b377 100644 --- a/test_text_spotting/test_dptext_runner.py +++ b/test_text_spotting/test_dptext_runner.py @@ -1,16 +1,21 @@ from __future__ import annotations +import os import pathlib import adet +import geopandas as gpd +import pandas as pd import pytest from detectron2.engine import DefaultPredictor - -print(adet.__version__) +from detectron2.structures.instances import Instances from mapreader import DPTextDETRRunner from mapreader.load import MapImages +print(adet.__version__) +ADET_PATH = pathlib.Path(adet.__path__[0]).resolve().parent + @pytest.fixture def sample_dir(): @@ -25,20 +30,175 @@ def init_dataframes(sample_dir, tmp_path): tuple path to parent and patch dataframes """ - maps = MapImages(f"{sample_dir}/cropped_74488689.png") - maps.add_metadata(f"{sample_dir}/ts_downloaded_maps.csv") - maps.patchify_all(patch_size=3, path_save=tmp_path) # gives 9 patches - maps.add_center_coord(tree_level="parent") - maps.add_patch_polygons() + maps = MapImages(f"{sample_dir}/mapreader_text.png") + maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") + maps.patchify_all(patch_size=800, path_save=tmp_path) + maps.check_georeferencing() parent_df, patch_df = maps.convert_images() return parent_df, patch_df +@pytest.fixture +def init_runner(init_dataframes): + parent_df, patch_df = init_dataframes + runner = DPTextDETRRunner( + patch_df, + parent_df=parent_df, + cfg_file=f"{ADET_PATH}/configs/DPText_DETR/ArT/R_50_poly.yaml", + ) + return runner + + def test_dptext_init(init_dataframes): parent_df, patch_df = init_dataframes runner = DPTextDETRRunner( - parent_df, patch_df, + parent_df=parent_df, + cfg_file=f"{ADET_PATH}/configs/DPText_DETR/ArT/R_50_poly.yaml", + ) + assert isinstance(runner, DPTextDETRRunner) + assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_dptext_init_str(init_dataframes, tmp_path): + parent_df, patch_df = init_dataframes + parent_df = parent_df.to_csv(f"{tmp_path}/parent_df.csv") + patch_df = patch_df.to_csv(f"{tmp_path}/patch_df.csv") + runner = DPTextDETRRunner( + f"{tmp_path}/patch_df.csv", + parent_df=f"{tmp_path}/parent_df.csv", + cfg_file=f"{ADET_PATH}/configs/DPText_DETR/ArT/R_50_poly.yaml", + ) + assert isinstance(runner, DPTextDETRRunner) + assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_dptext_init_pathlib(init_dataframes, tmp_path): + parent_df, patch_df = init_dataframes + parent_df = parent_df.to_csv(f"{tmp_path}/parent_df.csv") + patch_df = patch_df.to_csv(f"{tmp_path}/patch_df.csv") + runner = DPTextDETRRunner( + pathlib.Path(f"{tmp_path}/patch_df.csv"), + parent_df=pathlib.Path(f"{tmp_path}/parent_df.csv"), + cfg_file=f"{ADET_PATH}/configs/DPText_DETR/ArT/R_50_poly.yaml", ) assert isinstance(runner, DPTextDETRRunner) assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_dptext_init_tsv(init_dataframes, tmp_path): + parent_df, patch_df = init_dataframes + parent_df = parent_df.to_csv(f"{tmp_path}/parent_df.tsv", sep="\t") + patch_df = patch_df.to_csv(f"{tmp_path}/patch_df.tsv", sep="\t") + runner = DPTextDETRRunner( + f"{tmp_path}/patch_df.tsv", + parent_df=f"{tmp_path}/parent_df.tsv", + delimiter="\t", + cfg_file=f"{ADET_PATH}/configs/DPText_DETR/ArT/R_50_poly.yaml", + ) + assert isinstance(runner, DPTextDETRRunner) + assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_dptext_run_all(init_runner): + runner = init_runner + # dict + out = runner.run_all() + assert isinstance(out, dict) + assert "patch-0-0-800-40-#mapreader_text.png#.png" in out.keys() + assert isinstance(out["patch-0-0-800-40-#mapreader_text.png#.png"], list) + # dataframe + runner.patch_predictions = {} + out = runner.run_all(return_dataframe=True) + assert isinstance(out, pd.DataFrame) + assert set(out.columns) == set(["image_id", "geometry", "score"]) + assert "patch-0-0-800-40-#mapreader_text.png#.png" in out["image_id"].values + + +def test_dptext_convert_to_parent(init_runner): + runner = init_runner + _ = runner.run_all() + # dict + out = runner.convert_to_parent_pixel_bounds() + assert isinstance(out, dict) + assert "mapreader_text.png" in out.keys() + assert isinstance(out["mapreader_text.png"], list) + # dataframe + runner.parent_predictions = {} + out = runner.convert_to_parent_pixel_bounds(return_dataframe=True) + assert isinstance(out, pd.DataFrame) + assert set(out.columns) == set(["image_id", "patch_id", "geometry", "score"]) + assert "mapreader_text.png" in out["image_id"].values + + +def test_dptext_convert_to_parent_coords(init_runner): + runner = init_runner + _ = runner.run_all() + # dict + out = runner.convert_to_coords() + assert isinstance(out, dict) + assert "mapreader_text.png" in out.keys() + assert isinstance(out["mapreader_text.png"], list) + # dataframe + runner.parent_predictions = {} + out = runner.convert_to_coords(return_dataframe=True) + assert isinstance(out, gpd.GeoDataFrame) + assert set(out.columns) == set(["image_id", "patch_id", "geometry", "crs", "score"]) + assert "mapreader_text.png" in out["image_id"].values + assert out.crs == runner.parent_df.crs + + +def test_dptext_deduplicate(sample_dir, tmp_path): + maps = MapImages(f"{sample_dir}/mapreader_text.png") + maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") + maps.patchify_all(patch_size=800, path_save=tmp_path, overlap=0.5) + maps.check_georeferencing() + parent_df, patch_df = maps.convert_images() + runner = DPTextDETRRunner( + patch_df, + parent_df=parent_df, + cfg_file=f"{ADET_PATH}/configs/DPText_DETR/ArT/R_50_poly.yaml", + ) + _ = runner.run_all() + out = runner.convert_to_parent_pixel_bounds(deduplicate=False) + len_before = len(out["mapreader_text.png"]) + runner.patch_predictions = {} + out_07 = runner.convert_to_parent_pixel_bounds(deduplicate=True) + len_07 = len(out_07["mapreader_text.png"]) + print(len_before, len_07) + assert len_before >= len_07 + runner.patch_predictions = {} + out_05 = runner.convert_to_parent_pixel_bounds(deduplicate=True, min_ioa=0.5) + len_05 = len(out_05["mapreader_text.png"]) + print(len_before, len_05) + assert len_before >= len_05 + assert len_07 >= len_05 + + +def test_dptext_run_on_image(init_runner): + runner = init_runner + out = runner.run_on_image( + runner.patch_df.iloc[0]["image_path"], return_outputs=True + ) + assert isinstance(out, dict) + assert "instances" in out.keys() + assert isinstance(out["instances"], Instances) + + +def test_dptext_save_to_geojson(init_runner, tmp_path): + runner = init_runner + _ = runner.run_all() + _ = runner.convert_to_coords() + runner.save_to_geojson(f"{tmp_path}/text.geojson") + assert os.path.exists(f"{tmp_path}/text.geojson") + gdf = gpd.read_file(f"{tmp_path}/text.geojson") + assert isinstance(gdf, gpd.GeoDataFrame) + assert set(gdf.columns) == set(["image_id", "patch_id", "geometry", "crs", "score"]) diff --git a/test_text_spotting/test_maptext_runner.py b/test_text_spotting/test_maptext_runner.py index 63922201..2553f385 100644 --- a/test_text_spotting/test_maptext_runner.py +++ b/test_text_spotting/test_maptext_runner.py @@ -1,16 +1,21 @@ from __future__ import annotations +import os import pathlib import adet +import geopandas as gpd +import pandas as pd import pytest from detectron2.engine import DefaultPredictor - -print(adet.__version__) +from detectron2.structures.instances import Instances from mapreader import MapTextRunner from mapreader.load import MapImages +print(adet.__version__) +ADET_PATH = pathlib.Path(adet.__path__[0]).resolve().parent + @pytest.fixture def sample_dir(): @@ -25,20 +30,181 @@ def init_dataframes(sample_dir, tmp_path): tuple path to parent and patch dataframes """ - maps = MapImages(f"{sample_dir}/cropped_74488689.png") - maps.add_metadata(f"{sample_dir}/ts_downloaded_maps.csv") - maps.patchify_all(patch_size=3, path_save=tmp_path) # gives 9 patches - maps.add_center_coord(tree_level="parent") - maps.add_patch_polygons() + maps = MapImages(f"{sample_dir}/mapreader_text.png") + maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") + maps.patchify_all(patch_size=800, path_save=tmp_path) + maps.check_georeferencing() parent_df, patch_df = maps.convert_images() return parent_df, patch_df -def test_dptext_init(init_dataframes): +@pytest.fixture +def init_runner(init_dataframes): parent_df, patch_df = init_dataframes runner = MapTextRunner( - parent_df, patch_df, + parent_df=parent_df, + cfg_file=f"{ADET_PATH}/configs/ViTAEv2_S/rumsey/final_rumsey.yaml", + ) + return runner + + +def test_maptext_init(init_dataframes): + parent_df, patch_df = init_dataframes + runner = MapTextRunner( + patch_df, + parent_df=parent_df, + cfg_file=f"{ADET_PATH}/configs/ViTAEv2_S/rumsey/final_rumsey.yaml", + ) + assert isinstance(runner, MapTextRunner) + assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_maptext_init_str(init_dataframes, tmp_path): + parent_df, patch_df = init_dataframes + parent_df = parent_df.to_csv(f"{tmp_path}/parent_df.csv") + patch_df = patch_df.to_csv(f"{tmp_path}/patch_df.csv") + runner = MapTextRunner( + f"{tmp_path}/patch_df.csv", + parent_df=f"{tmp_path}/parent_df.csv", + cfg_file=f"{ADET_PATH}/configs/ViTAEv2_S/rumsey/final_rumsey.yaml", + ) + assert isinstance(runner, MapTextRunner) + assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_maptext_init_pathlib(init_dataframes, tmp_path): + parent_df, patch_df = init_dataframes + parent_df = parent_df.to_csv(f"{tmp_path}/parent_df.csv") + patch_df = patch_df.to_csv(f"{tmp_path}/patch_df.csv") + runner = MapTextRunner( + pathlib.Path(f"{tmp_path}/patch_df.csv"), + parent_df=pathlib.Path(f"{tmp_path}/parent_df.csv"), + cfg_file=f"{ADET_PATH}/configs/ViTAEv2_S/rumsey/final_rumsey.yaml", + ) + assert isinstance(runner, MapTextRunner) + assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_maptext_init_tsv(init_dataframes, tmp_path): + parent_df, patch_df = init_dataframes + parent_df = parent_df.to_csv(f"{tmp_path}/parent_df.tsv", sep="\t") + patch_df = patch_df.to_csv(f"{tmp_path}/patch_df.tsv", sep="\t") + runner = MapTextRunner( + f"{tmp_path}/patch_df.tsv", + parent_df=f"{tmp_path}/parent_df.tsv", + delimiter="\t", + cfg_file=f"{ADET_PATH}/configs/ViTAEv2_S/rumsey/final_rumsey.yaml", ) assert isinstance(runner, MapTextRunner) assert isinstance(runner.predictor, DefaultPredictor) + assert isinstance(runner.parent_df.iloc[0]["coordinates"], tuple) + assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) + + +def test_maptext_run_all(init_runner): + runner = init_runner + # dict + out = runner.run_all() + assert isinstance(out, dict) + assert "patch-0-0-800-40-#mapreader_text.png#.png" in out.keys() + assert isinstance(out["patch-0-0-800-40-#mapreader_text.png#.png"], list) + # dataframe + runner.patch_predictions = {} + out = runner.run_all(return_dataframe=True) + assert isinstance(out, pd.DataFrame) + assert set(out.columns) == set(["image_id", "geometry", "text", "score"]) + assert "patch-0-0-800-40-#mapreader_text.png#.png" in out["image_id"].values + + +def test_maptext_convert_to_parent(init_runner): + runner = init_runner + _ = runner.run_all() + # dict + out = runner.convert_to_parent_pixel_bounds() + assert isinstance(out, dict) + assert "mapreader_text.png" in out.keys() + assert isinstance(out["mapreader_text.png"], list) + # dataframe + runner.parent_predictions = {} + out = runner.convert_to_parent_pixel_bounds(return_dataframe=True) + assert isinstance(out, pd.DataFrame) + assert set(out.columns) == set( + ["image_id", "patch_id", "geometry", "text", "score"] + ) + assert "mapreader_text.png" in out["image_id"].values + + +def test_maptext_convert_to_parent_coords(init_runner): + runner = init_runner + _ = runner.run_all() + # dict + out = runner.convert_to_coords() + assert isinstance(out, dict) + assert "mapreader_text.png" in out.keys() + assert isinstance(out["mapreader_text.png"], list) + # dataframe + runner.parent_predictions = {} + out = runner.convert_to_coords(return_dataframe=True) + assert isinstance(out, gpd.GeoDataFrame) + assert set(out.columns) == set( + ["image_id", "patch_id", "geometry", "crs", "text", "score"] + ) + assert "mapreader_text.png" in out["image_id"].values + assert out.crs == runner.parent_df.crs + + +def test_maptext_deduplicate(sample_dir, tmp_path): + maps = MapImages(f"{sample_dir}/mapreader_text.png") + maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") + maps.patchify_all(patch_size=800, path_save=tmp_path, overlap=0.5) + maps.check_georeferencing() + parent_df, patch_df = maps.convert_images() + runner = MapTextRunner( + patch_df, + parent_df=parent_df, + cfg_file=f"{ADET_PATH}/configs/ViTAEv2_S/rumsey/final_rumsey.yaml", + ) + _ = runner.run_all() + out = runner.convert_to_parent_pixel_bounds(deduplicate=False) + len_before = len(out["mapreader_text.png"]) + runner.patch_predictions = {} + out_07 = runner.convert_to_parent_pixel_bounds(deduplicate=True) + len_07 = len(out_07["mapreader_text.png"]) + print(len_before, len_07) + assert len_before >= len_07 + runner.patch_predictions = {} + out_05 = runner.convert_to_parent_pixel_bounds(deduplicate=True, min_ioa=0.5) + len_05 = len(out_05["mapreader_text.png"]) + print(len_before, len_05) + assert len_before >= len_05 + assert len_07 >= len_05 + + +def test_maptext_run_on_image(init_runner): + runner = init_runner + out = runner.run_on_image( + runner.patch_df.iloc[0]["image_path"], return_outputs=True + ) + assert isinstance(out, dict) + assert "instances" in out.keys() + assert isinstance(out["instances"], Instances) + + +def test_maptext_save_to_geojson(init_runner, tmp_path): + runner = init_runner + _ = runner.run_all() + _ = runner.convert_to_coords() + runner.save_to_geojson(f"{tmp_path}/text.geojson") + assert os.path.exists(f"{tmp_path}/text.geojson") + gdf = gpd.read_file(f"{tmp_path}/text.geojson") + assert isinstance(gdf, gpd.GeoDataFrame) + assert set(gdf.columns) == set( + ["image_id", "patch_id", "geometry", "crs", "text", "score"] + ) diff --git a/tests/sample_files/mapreader_text.png b/tests/sample_files/mapreader_text.png new file mode 100644 index 0000000000000000000000000000000000000000..402b53b7fa46bb6a3639989facc786b1b4ea3cc1 GIT binary patch literal 4234 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 H_HX_JS*)s% literal 0 HcmV?d00001 diff --git a/tests/sample_files/mapreader_text_metadata.csv b/tests/sample_files/mapreader_text_metadata.csv new file mode 100644 index 00000000..7aa8481a --- /dev/null +++ b/tests/sample_files/mapreader_text_metadata.csv @@ -0,0 +1,2 @@ +,name,url,coordinates,crs,published_date,grid_bb +0,mapreader_text.png,https://maps.nls.uk/view/74488689,"(-4.833984375, 55.78892895389263, -4.19677734375, 56.05976947910656)",EPSG:4326,1898,"[(14, 7972, 5097)x(14, 8000, 5118)]" From 4f26fac9a180a3310ea3f0d90456d8eaa9ac346a Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Fri, 6 Sep 2024 09:29:01 +0100 Subject: [PATCH 20/28] print paths (for debugging) --- test_text_spotting/test_dptext_runner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test_text_spotting/test_dptext_runner.py b/test_text_spotting/test_dptext_runner.py index f6d1b377..e71f89cb 100644 --- a/test_text_spotting/test_dptext_runner.py +++ b/test_text_spotting/test_dptext_runner.py @@ -14,6 +14,8 @@ from mapreader.load import MapImages print(adet.__version__) +print(adet.__path__, flush=True) +print(adet.__file__, flush=True) ADET_PATH = pathlib.Path(adet.__path__[0]).resolve().parent From b14ac62e26aa4ca20ed59c12a173b66a55f21fee Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Fri, 6 Sep 2024 09:40:00 +0100 Subject: [PATCH 21/28] turn on printing --- .github/workflows/mr_ci_text_spotting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index e516e112..3327850c 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -63,7 +63,7 @@ jobs: - name: Run DPText-DETR unittests run: | - python -m pytest test_text_spotting/test_dptext_runner.py + python -m pytest test_text_spotting/test_dptext_runner.py -s - name: Install DeepSolo From 45865c8309b2b4c9f2eddbffdf45e2c2d9e03a87 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Fri, 6 Sep 2024 10:00:20 +0100 Subject: [PATCH 22/28] use cloned path if running with GH actions --- test_text_spotting/test_dptext_runner.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test_text_spotting/test_dptext_runner.py b/test_text_spotting/test_dptext_runner.py index e71f89cb..5dae1041 100644 --- a/test_text_spotting/test_dptext_runner.py +++ b/test_text_spotting/test_dptext_runner.py @@ -14,9 +14,13 @@ from mapreader.load import MapImages print(adet.__version__) -print(adet.__path__, flush=True) -print(adet.__file__, flush=True) -ADET_PATH = pathlib.Path(adet.__path__[0]).resolve().parent + +# use cloned DPText-DETR path if running in github actions +ADET_PATH = ( + pathlib.Path("./DPText-DETR/").resolve() + if os.getenv("GITHUB_ACTIONS") == "true" + else pathlib.Path(adet.__path__[0]).resolve().parent +) @pytest.fixture From 52b6c970a510bd3ec8e7a1b36b497d8624b7502e Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Fri, 6 Sep 2024 10:09:09 +0100 Subject: [PATCH 23/28] add to DeepSolo and MapText files too --- .github/workflows/mr_ci_text_spotting.yml | 2 +- test_text_spotting/test_deepsolo_runner.py | 8 +++++++- test_text_spotting/test_maptext_runner.py | 8 +++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 3327850c..e516e112 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -63,7 +63,7 @@ jobs: - name: Run DPText-DETR unittests run: | - python -m pytest test_text_spotting/test_dptext_runner.py -s + python -m pytest test_text_spotting/test_dptext_runner.py - name: Install DeepSolo diff --git a/test_text_spotting/test_deepsolo_runner.py b/test_text_spotting/test_deepsolo_runner.py index 52860ea8..26607cf3 100644 --- a/test_text_spotting/test_deepsolo_runner.py +++ b/test_text_spotting/test_deepsolo_runner.py @@ -14,7 +14,13 @@ from mapreader.load import MapImages print(adet.__version__) -ADET_PATH = pathlib.Path(adet.__path__[0]).resolve().parent + +# use cloned DeepSolo path if running in github actions +ADET_PATH = ( + pathlib.Path("./DeepSolo/").resolve() + if os.getenv("GITHUB_ACTIONS") == "true" + else pathlib.Path(adet.__path__[0]).resolve().parent +) @pytest.fixture diff --git a/test_text_spotting/test_maptext_runner.py b/test_text_spotting/test_maptext_runner.py index 2553f385..64ae00d9 100644 --- a/test_text_spotting/test_maptext_runner.py +++ b/test_text_spotting/test_maptext_runner.py @@ -14,7 +14,13 @@ from mapreader.load import MapImages print(adet.__version__) -ADET_PATH = pathlib.Path(adet.__path__[0]).resolve().parent + +# use cloned MapTextPipeline path if running in github actions +ADET_PATH = ( + pathlib.Path("./MapTextPipeline/").resolve() + if os.getenv("GITHUB_ACTIONS") == "true" + else pathlib.Path(adet.__path__[0]).resolve().parent +) @pytest.fixture From a9f54d9a64d5a07ebcc0a067da4b8142ad3d6d2a Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Fri, 6 Sep 2024 14:01:33 +0100 Subject: [PATCH 24/28] speed up testing --- .github/workflows/mr_ci_text_spotting.yml | 6 ++-- test_text_spotting/test_deepsolo_runner.py | 41 +++++++++++----------- test_text_spotting/test_dptext_runner.py | 41 +++++++++++----------- test_text_spotting/test_maptext_runner.py | 41 +++++++++++----------- 4 files changed, 66 insertions(+), 63 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index e516e112..7efe2476 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -57,7 +57,7 @@ jobs: - name: Install DPText-DETR run: | git clone https://github.com/maps-as-data/DPText-DETR.git - python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' --force-reinstall # Install DPText-DETR + python -m pip install 'git+https://github.com/maps-as-data/DPText-DETR.git' # Install DPText-DETR python -m pip install numpy==1.26.4 wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth @@ -69,7 +69,7 @@ jobs: - name: Install DeepSolo run: | git clone https://github.com/maps-as-data/DeepSolo.git - python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' --force-reinstall # Install DeepSolo + python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo python -m pip install numpy==1.26.4 wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth @@ -80,7 +80,7 @@ jobs: - name: Install MapTextPipeline run: | git clone https://github.com/maps-as-data/MapTextPipeline.git - python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' --force-reinstall # Install MapTextPipeline + python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' # Install MapTextPipeline python -m pip install "numpy<2.0.0" wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth diff --git a/test_text_spotting/test_deepsolo_runner.py b/test_text_spotting/test_deepsolo_runner.py index 26607cf3..2b4c3922 100644 --- a/test_text_spotting/test_deepsolo_runner.py +++ b/test_text_spotting/test_deepsolo_runner.py @@ -23,12 +23,12 @@ ) -@pytest.fixture +@pytest.fixture(scope="session") def sample_dir(): return pathlib.Path(__file__).resolve().parent.parent / "tests" / "sample_files" -@pytest.fixture +@pytest.fixture(scope="session") def init_dataframes(sample_dir, tmp_path): """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. Returns @@ -44,7 +44,7 @@ def init_dataframes(sample_dir, tmp_path): return parent_df, patch_df -@pytest.fixture +@pytest.fixture(scope="session") def init_runner(init_dataframes): parent_df, patch_df = init_dataframes runner = DeepSoloRunner( @@ -55,6 +55,13 @@ def init_runner(init_dataframes): return runner +@pytest.fixture(scope="session") +def runner_run_all(init_runner): + runner = init_runner + _ = runner.run_all() + return runner + + def test_deepsolo_init(init_dataframes): parent_df, patch_df = init_dataframes runner = DeepSoloRunner( @@ -122,24 +129,21 @@ def test_deepsolo_run_all(init_runner): assert "patch-0-0-800-40-#mapreader_text.png#.png" in out.keys() assert isinstance(out["patch-0-0-800-40-#mapreader_text.png#.png"], list) # dataframe - runner.patch_predictions = {} - out = runner.run_all(return_dataframe=True) + out = runner._dict_to_dataframe(runner.patch_predictions, geo=False, parent=False) assert isinstance(out, pd.DataFrame) assert set(out.columns) == set(["image_id", "geometry", "text", "score"]) assert "patch-0-0-800-40-#mapreader_text.png#.png" in out["image_id"].values -def test_deepsolo_convert_to_parent(init_runner): - runner = init_runner - _ = runner.run_all() +def test_deepsolo_convert_to_parent(runner_run_all): + runner = runner_run_all # dict out = runner.convert_to_parent_pixel_bounds() assert isinstance(out, dict) assert "mapreader_text.png" in out.keys() assert isinstance(out["mapreader_text.png"], list) # dataframe - runner.parent_predictions = {} - out = runner.convert_to_parent_pixel_bounds(return_dataframe=True) + out = runner._dict_to_dataframe(runner.parent_predictions, geo=False, parent=True) assert isinstance(out, pd.DataFrame) assert set(out.columns) == set( ["image_id", "patch_id", "geometry", "text", "score"] @@ -147,17 +151,15 @@ def test_deepsolo_convert_to_parent(init_runner): assert "mapreader_text.png" in out["image_id"].values -def test_deepsolo_convert_to_parent_coords(init_runner): - runner = init_runner - _ = runner.run_all() +def test_deepsolo_convert_to_parent_coords(runner_run_all): + runner = runner_run_all # dict out = runner.convert_to_coords() assert isinstance(out, dict) assert "mapreader_text.png" in out.keys() assert isinstance(out["mapreader_text.png"], list) # dataframe - runner.parent_predictions = {} - out = runner.convert_to_coords(return_dataframe=True) + out = runner._dict_to_dataframe(runner.geo_predictions, geo=True, parent=True) assert isinstance(out, gpd.GeoDataFrame) assert set(out.columns) == set( ["image_id", "patch_id", "geometry", "crs", "text", "score"] @@ -180,12 +182,12 @@ def test_deepsolo_deduplicate(sample_dir, tmp_path): _ = runner.run_all() out = runner.convert_to_parent_pixel_bounds(deduplicate=False) len_before = len(out["mapreader_text.png"]) - runner.patch_predictions = {} + runner.parent_predictions = {} out_07 = runner.convert_to_parent_pixel_bounds(deduplicate=True) len_07 = len(out_07["mapreader_text.png"]) print(len_before, len_07) assert len_before >= len_07 - runner.patch_predictions = {} + runner.parent_predictions = {} out_05 = runner.convert_to_parent_pixel_bounds(deduplicate=True, min_ioa=0.5) len_05 = len(out_05["mapreader_text.png"]) print(len_before, len_05) @@ -203,9 +205,8 @@ def test_deepsolo_run_on_image(init_runner): assert isinstance(out["instances"], Instances) -def test_deepsolo_save_to_geojson(init_runner, tmp_path): - runner = init_runner - _ = runner.run_all() +def test_deepsolo_save_to_geojson(runner_run_all, tmp_path): + runner = runner_run_all _ = runner.convert_to_coords() runner.save_to_geojson(f"{tmp_path}/text.geojson") assert os.path.exists(f"{tmp_path}/text.geojson") diff --git a/test_text_spotting/test_dptext_runner.py b/test_text_spotting/test_dptext_runner.py index 5dae1041..878abdab 100644 --- a/test_text_spotting/test_dptext_runner.py +++ b/test_text_spotting/test_dptext_runner.py @@ -23,12 +23,12 @@ ) -@pytest.fixture +@pytest.fixture(scope="session") def sample_dir(): return pathlib.Path(__file__).resolve().parent.parent / "tests" / "sample_files" -@pytest.fixture +@pytest.fixture(scope="session") def init_dataframes(sample_dir, tmp_path): """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. Returns @@ -44,7 +44,7 @@ def init_dataframes(sample_dir, tmp_path): return parent_df, patch_df -@pytest.fixture +@pytest.fixture(scope="session") def init_runner(init_dataframes): parent_df, patch_df = init_dataframes runner = DPTextDETRRunner( @@ -55,6 +55,13 @@ def init_runner(init_dataframes): return runner +@pytest.fixture(scope="session") +def runner_run_all(init_runner): + runner = init_runner + _ = runner.run_all() + return runner + + def test_dptext_init(init_dataframes): parent_df, patch_df = init_dataframes runner = DPTextDETRRunner( @@ -122,40 +129,35 @@ def test_dptext_run_all(init_runner): assert "patch-0-0-800-40-#mapreader_text.png#.png" in out.keys() assert isinstance(out["patch-0-0-800-40-#mapreader_text.png#.png"], list) # dataframe - runner.patch_predictions = {} - out = runner.run_all(return_dataframe=True) + out = runner._dict_to_dataframe(runner.patch_predictions, geo=False, parent=False) assert isinstance(out, pd.DataFrame) assert set(out.columns) == set(["image_id", "geometry", "score"]) assert "patch-0-0-800-40-#mapreader_text.png#.png" in out["image_id"].values -def test_dptext_convert_to_parent(init_runner): - runner = init_runner - _ = runner.run_all() +def test_dptext_convert_to_parent(runner_run_all): + runner = runner_run_all # dict out = runner.convert_to_parent_pixel_bounds() assert isinstance(out, dict) assert "mapreader_text.png" in out.keys() assert isinstance(out["mapreader_text.png"], list) # dataframe - runner.parent_predictions = {} - out = runner.convert_to_parent_pixel_bounds(return_dataframe=True) + out = runner._dict_to_dataframe(runner.parent_predictions, geo=False, parent=True) assert isinstance(out, pd.DataFrame) assert set(out.columns) == set(["image_id", "patch_id", "geometry", "score"]) assert "mapreader_text.png" in out["image_id"].values -def test_dptext_convert_to_parent_coords(init_runner): - runner = init_runner - _ = runner.run_all() +def test_dptext_convert_to_parent_coords(runner_run_all): + runner = runner_run_all # dict out = runner.convert_to_coords() assert isinstance(out, dict) assert "mapreader_text.png" in out.keys() assert isinstance(out["mapreader_text.png"], list) # dataframe - runner.parent_predictions = {} - out = runner.convert_to_coords(return_dataframe=True) + out = runner._dict_to_dataframe(runner.geo_predictions, geo=True, parent=True) assert isinstance(out, gpd.GeoDataFrame) assert set(out.columns) == set(["image_id", "patch_id", "geometry", "crs", "score"]) assert "mapreader_text.png" in out["image_id"].values @@ -176,12 +178,12 @@ def test_dptext_deduplicate(sample_dir, tmp_path): _ = runner.run_all() out = runner.convert_to_parent_pixel_bounds(deduplicate=False) len_before = len(out["mapreader_text.png"]) - runner.patch_predictions = {} + runner.parent_predictions = {} out_07 = runner.convert_to_parent_pixel_bounds(deduplicate=True) len_07 = len(out_07["mapreader_text.png"]) print(len_before, len_07) assert len_before >= len_07 - runner.patch_predictions = {} + runner.parent_predictions = {} out_05 = runner.convert_to_parent_pixel_bounds(deduplicate=True, min_ioa=0.5) len_05 = len(out_05["mapreader_text.png"]) print(len_before, len_05) @@ -199,9 +201,8 @@ def test_dptext_run_on_image(init_runner): assert isinstance(out["instances"], Instances) -def test_dptext_save_to_geojson(init_runner, tmp_path): - runner = init_runner - _ = runner.run_all() +def test_dptext_save_to_geojson(runner_run_all, tmp_path): + runner = runner_run_all _ = runner.convert_to_coords() runner.save_to_geojson(f"{tmp_path}/text.geojson") assert os.path.exists(f"{tmp_path}/text.geojson") diff --git a/test_text_spotting/test_maptext_runner.py b/test_text_spotting/test_maptext_runner.py index 64ae00d9..1a6bbce7 100644 --- a/test_text_spotting/test_maptext_runner.py +++ b/test_text_spotting/test_maptext_runner.py @@ -23,12 +23,12 @@ ) -@pytest.fixture +@pytest.fixture(scope="session") def sample_dir(): return pathlib.Path(__file__).resolve().parent.parent / "tests" / "sample_files" -@pytest.fixture +@pytest.fixture(scope="session") def init_dataframes(sample_dir, tmp_path): """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. Returns @@ -44,7 +44,7 @@ def init_dataframes(sample_dir, tmp_path): return parent_df, patch_df -@pytest.fixture +@pytest.fixture(scope="session") def init_runner(init_dataframes): parent_df, patch_df = init_dataframes runner = MapTextRunner( @@ -55,6 +55,13 @@ def init_runner(init_dataframes): return runner +@pytest.fixture(scope="session") +def runner_run_all(init_runner): + runner = init_runner + _ = runner.run_all() + return runner + + def test_maptext_init(init_dataframes): parent_df, patch_df = init_dataframes runner = MapTextRunner( @@ -122,24 +129,21 @@ def test_maptext_run_all(init_runner): assert "patch-0-0-800-40-#mapreader_text.png#.png" in out.keys() assert isinstance(out["patch-0-0-800-40-#mapreader_text.png#.png"], list) # dataframe - runner.patch_predictions = {} - out = runner.run_all(return_dataframe=True) + out = runner._dict_to_dataframe(runner.patch_predictions, geo=False, parent=False) assert isinstance(out, pd.DataFrame) assert set(out.columns) == set(["image_id", "geometry", "text", "score"]) assert "patch-0-0-800-40-#mapreader_text.png#.png" in out["image_id"].values -def test_maptext_convert_to_parent(init_runner): - runner = init_runner - _ = runner.run_all() +def test_maptext_convert_to_parent(runner_run_all): + runner = runner_run_all # dict out = runner.convert_to_parent_pixel_bounds() assert isinstance(out, dict) assert "mapreader_text.png" in out.keys() assert isinstance(out["mapreader_text.png"], list) # dataframe - runner.parent_predictions = {} - out = runner.convert_to_parent_pixel_bounds(return_dataframe=True) + out = runner._dict_to_dataframe(runner.patch_predictions, geo=False, parent=True) assert isinstance(out, pd.DataFrame) assert set(out.columns) == set( ["image_id", "patch_id", "geometry", "text", "score"] @@ -147,17 +151,15 @@ def test_maptext_convert_to_parent(init_runner): assert "mapreader_text.png" in out["image_id"].values -def test_maptext_convert_to_parent_coords(init_runner): - runner = init_runner - _ = runner.run_all() +def test_maptext_convert_to_parent_coords(runner_run_all): + runner = runner_run_all # dict out = runner.convert_to_coords() assert isinstance(out, dict) assert "mapreader_text.png" in out.keys() assert isinstance(out["mapreader_text.png"], list) # dataframe - runner.parent_predictions = {} - out = runner.convert_to_coords(return_dataframe=True) + out = runner._dict_to_dataframe(runner.parent_predictions, geo=True, parent=True) assert isinstance(out, gpd.GeoDataFrame) assert set(out.columns) == set( ["image_id", "patch_id", "geometry", "crs", "text", "score"] @@ -180,12 +182,12 @@ def test_maptext_deduplicate(sample_dir, tmp_path): _ = runner.run_all() out = runner.convert_to_parent_pixel_bounds(deduplicate=False) len_before = len(out["mapreader_text.png"]) - runner.patch_predictions = {} + runner.parent_predictions = {} out_07 = runner.convert_to_parent_pixel_bounds(deduplicate=True) len_07 = len(out_07["mapreader_text.png"]) print(len_before, len_07) assert len_before >= len_07 - runner.patch_predictions = {} + runner.parent_predictions = {} out_05 = runner.convert_to_parent_pixel_bounds(deduplicate=True, min_ioa=0.5) len_05 = len(out_05["mapreader_text.png"]) print(len_before, len_05) @@ -203,9 +205,8 @@ def test_maptext_run_on_image(init_runner): assert isinstance(out["instances"], Instances) -def test_maptext_save_to_geojson(init_runner, tmp_path): - runner = init_runner - _ = runner.run_all() +def test_maptext_save_to_geojson(runner_run_all, tmp_path): + runner = runner_run_all _ = runner.convert_to_coords() runner.save_to_geojson(f"{tmp_path}/text.geojson") assert os.path.exists(f"{tmp_path}/text.geojson") From 0e2bc769be0d84f5acbf316afa8b34d7de579f36 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Fri, 6 Sep 2024 16:37:32 +0100 Subject: [PATCH 25/28] fix tmp path factory --- test_text_spotting/test_deepsolo_runner.py | 5 +++-- test_text_spotting/test_dptext_runner.py | 5 +++-- test_text_spotting/test_maptext_runner.py | 9 +++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/test_text_spotting/test_deepsolo_runner.py b/test_text_spotting/test_deepsolo_runner.py index 2b4c3922..f9cd60e6 100644 --- a/test_text_spotting/test_deepsolo_runner.py +++ b/test_text_spotting/test_deepsolo_runner.py @@ -19,7 +19,7 @@ ADET_PATH = ( pathlib.Path("./DeepSolo/").resolve() if os.getenv("GITHUB_ACTIONS") == "true" - else pathlib.Path(adet.__path__[0]).resolve().parent + else pathlib.Path(os.getenv("ADET_PATH")).resolve() ) @@ -29,13 +29,14 @@ def sample_dir(): @pytest.fixture(scope="session") -def init_dataframes(sample_dir, tmp_path): +def init_dataframes(sample_dir, tmp_path_factory): """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. Returns ------- tuple path to parent and patch dataframes """ + tmp_path = tmp_path_factory.mktemp("patches") maps = MapImages(f"{sample_dir}/mapreader_text.png") maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") maps.patchify_all(patch_size=800, path_save=tmp_path) diff --git a/test_text_spotting/test_dptext_runner.py b/test_text_spotting/test_dptext_runner.py index 878abdab..d228c97e 100644 --- a/test_text_spotting/test_dptext_runner.py +++ b/test_text_spotting/test_dptext_runner.py @@ -19,7 +19,7 @@ ADET_PATH = ( pathlib.Path("./DPText-DETR/").resolve() if os.getenv("GITHUB_ACTIONS") == "true" - else pathlib.Path(adet.__path__[0]).resolve().parent + else pathlib.Path(os.getenv("ADET_PATH")).resolve() ) @@ -29,13 +29,14 @@ def sample_dir(): @pytest.fixture(scope="session") -def init_dataframes(sample_dir, tmp_path): +def init_dataframes(sample_dir, tmp_path_factory): """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. Returns ------- tuple path to parent and patch dataframes """ + tmp_path = tmp_path_factory.mktemp("patches") maps = MapImages(f"{sample_dir}/mapreader_text.png") maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") maps.patchify_all(patch_size=800, path_save=tmp_path) diff --git a/test_text_spotting/test_maptext_runner.py b/test_text_spotting/test_maptext_runner.py index 1a6bbce7..ee9a2547 100644 --- a/test_text_spotting/test_maptext_runner.py +++ b/test_text_spotting/test_maptext_runner.py @@ -19,7 +19,7 @@ ADET_PATH = ( pathlib.Path("./MapTextPipeline/").resolve() if os.getenv("GITHUB_ACTIONS") == "true" - else pathlib.Path(adet.__path__[0]).resolve().parent + else pathlib.Path(os.getenv("ADET_PATH")).resolve() ) @@ -29,13 +29,14 @@ def sample_dir(): @pytest.fixture(scope="session") -def init_dataframes(sample_dir, tmp_path): +def init_dataframes(sample_dir, tmp_path_factory): """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. Returns ------- tuple path to parent and patch dataframes """ + tmp_path = tmp_path_factory.mktemp("patches") maps = MapImages(f"{sample_dir}/mapreader_text.png") maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") maps.patchify_all(patch_size=800, path_save=tmp_path) @@ -143,7 +144,7 @@ def test_maptext_convert_to_parent(runner_run_all): assert "mapreader_text.png" in out.keys() assert isinstance(out["mapreader_text.png"], list) # dataframe - out = runner._dict_to_dataframe(runner.patch_predictions, geo=False, parent=True) + out = runner._dict_to_dataframe(runner.parent_predictions, geo=False, parent=True) assert isinstance(out, pd.DataFrame) assert set(out.columns) == set( ["image_id", "patch_id", "geometry", "text", "score"] @@ -159,7 +160,7 @@ def test_maptext_convert_to_parent_coords(runner_run_all): assert "mapreader_text.png" in out.keys() assert isinstance(out["mapreader_text.png"], list) # dataframe - out = runner._dict_to_dataframe(runner.parent_predictions, geo=True, parent=True) + out = runner._dict_to_dataframe(runner.geo_predictions, geo=True, parent=True) assert isinstance(out, gpd.GeoDataFrame) assert set(out.columns) == set( ["image_id", "patch_id", "geometry", "crs", "text", "score"] From d89eeaf41e3b6660a2225a84d9154cc1c6c48093 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Mon, 9 Sep 2024 09:15:43 +0100 Subject: [PATCH 26/28] force reinstall for deepsolo/maptextpiepline --- .github/workflows/mr_ci_text_spotting.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mr_ci_text_spotting.yml b/.github/workflows/mr_ci_text_spotting.yml index 7efe2476..7e40217c 100644 --- a/.github/workflows/mr_ci_text_spotting.yml +++ b/.github/workflows/mr_ci_text_spotting.yml @@ -69,7 +69,7 @@ jobs: - name: Install DeepSolo run: | git clone https://github.com/maps-as-data/DeepSolo.git - python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' # Install DeepSolo + python -m pip install 'git+https://github.com/maps-as-data/DeepSolo.git' --force-reinstall --no-deps # Install DeepSolo python -m pip install numpy==1.26.4 wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth @@ -80,7 +80,7 @@ jobs: - name: Install MapTextPipeline run: | git clone https://github.com/maps-as-data/MapTextPipeline.git - python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' # Install MapTextPipeline + python -m pip install 'git+https://github.com/maps-as-data/MapTextPipeline.git' --force-reinstall --no-deps # Install MapTextPipeline python -m pip install "numpy<2.0.0" wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth From 714f47232b81e64bd1885cd4a8f01e8d062bb037 Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Mon, 9 Sep 2024 14:36:29 +0100 Subject: [PATCH 27/28] mock model response --- test_text_spotting/test_deepsolo_runner.py | 36 +++++++++++------- test_text_spotting/test_dptext_runner.py | 36 +++++++++++------- test_text_spotting/test_maptext_runner.py | 36 +++++++++++------- .../patch-0-0-800-40-deepsolo-pred.pkl | Bin 0 -> 34261 bytes .../patch-0-0-800-40-dptext-detr-pred.pkl | Bin 0 -> 2357 bytes .../patch-0-0-800-40-maptext-pred.pkl | Bin 0 -> 64822 bytes 6 files changed, 69 insertions(+), 39 deletions(-) create mode 100644 tests/sample_files/patch-0-0-800-40-deepsolo-pred.pkl create mode 100644 tests/sample_files/patch-0-0-800-40-dptext-detr-pred.pkl create mode 100644 tests/sample_files/patch-0-0-800-40-maptext-pred.pkl diff --git a/test_text_spotting/test_deepsolo_runner.py b/test_text_spotting/test_deepsolo_runner.py index f9cd60e6..c62a79f6 100644 --- a/test_text_spotting/test_deepsolo_runner.py +++ b/test_text_spotting/test_deepsolo_runner.py @@ -2,6 +2,7 @@ import os import pathlib +import pickle import adet import geopandas as gpd @@ -23,20 +24,19 @@ ) -@pytest.fixture(scope="session") +@pytest.fixture def sample_dir(): return pathlib.Path(__file__).resolve().parent.parent / "tests" / "sample_files" -@pytest.fixture(scope="session") -def init_dataframes(sample_dir, tmp_path_factory): +@pytest.fixture +def init_dataframes(sample_dir, tmp_path): """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. Returns ------- tuple path to parent and patch dataframes """ - tmp_path = tmp_path_factory.mktemp("patches") maps = MapImages(f"{sample_dir}/mapreader_text.png") maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") maps.patchify_all(patch_size=800, path_save=tmp_path) @@ -45,7 +45,17 @@ def init_dataframes(sample_dir, tmp_path_factory): return parent_df, patch_df -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") +def mock_response(monkeypatch, sample_dir): + def mock_pred(self, *args, **kwargs): + with open(f"{sample_dir}/patch-0-0-800-40-deepsolo-pred.pkl", "rb") as f: + outputs = pickle.load(f) + return outputs + + monkeypatch.setattr(DefaultPredictor, "__call__", mock_pred) + + +@pytest.fixture def init_runner(init_dataframes): parent_df, patch_df = init_dataframes runner = DeepSoloRunner( @@ -56,8 +66,8 @@ def init_runner(init_dataframes): return runner -@pytest.fixture(scope="session") -def runner_run_all(init_runner): +@pytest.fixture +def runner_run_all(init_runner, mock_response): runner = init_runner _ = runner.run_all() return runner @@ -122,7 +132,7 @@ def test_deepsolo_init_tsv(init_dataframes, tmp_path): assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) -def test_deepsolo_run_all(init_runner): +def test_deepsolo_run_all(init_runner, mock_response): runner = init_runner # dict out = runner.run_all() @@ -136,7 +146,7 @@ def test_deepsolo_run_all(init_runner): assert "patch-0-0-800-40-#mapreader_text.png#.png" in out["image_id"].values -def test_deepsolo_convert_to_parent(runner_run_all): +def test_deepsolo_convert_to_parent(runner_run_all, mock_response): runner = runner_run_all # dict out = runner.convert_to_parent_pixel_bounds() @@ -152,7 +162,7 @@ def test_deepsolo_convert_to_parent(runner_run_all): assert "mapreader_text.png" in out["image_id"].values -def test_deepsolo_convert_to_parent_coords(runner_run_all): +def test_deepsolo_convert_to_parent_coords(runner_run_all, mock_response): runner = runner_run_all # dict out = runner.convert_to_coords() @@ -169,7 +179,7 @@ def test_deepsolo_convert_to_parent_coords(runner_run_all): assert out.crs == runner.parent_df.crs -def test_deepsolo_deduplicate(sample_dir, tmp_path): +def test_deepsolo_deduplicate(sample_dir, tmp_path, mock_response): maps = MapImages(f"{sample_dir}/mapreader_text.png") maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") maps.patchify_all(patch_size=800, path_save=tmp_path, overlap=0.5) @@ -196,7 +206,7 @@ def test_deepsolo_deduplicate(sample_dir, tmp_path): assert len_07 >= len_05 -def test_deepsolo_run_on_image(init_runner): +def test_deepsolo_run_on_image(init_runner, mock_response): runner = init_runner out = runner.run_on_image( runner.patch_df.iloc[0]["image_path"], return_outputs=True @@ -206,7 +216,7 @@ def test_deepsolo_run_on_image(init_runner): assert isinstance(out["instances"], Instances) -def test_deepsolo_save_to_geojson(runner_run_all, tmp_path): +def test_deepsolo_save_to_geojson(runner_run_all, tmp_path, mock_response): runner = runner_run_all _ = runner.convert_to_coords() runner.save_to_geojson(f"{tmp_path}/text.geojson") diff --git a/test_text_spotting/test_dptext_runner.py b/test_text_spotting/test_dptext_runner.py index d228c97e..5006a129 100644 --- a/test_text_spotting/test_dptext_runner.py +++ b/test_text_spotting/test_dptext_runner.py @@ -2,6 +2,7 @@ import os import pathlib +import pickle import adet import geopandas as gpd @@ -23,20 +24,19 @@ ) -@pytest.fixture(scope="session") +@pytest.fixture def sample_dir(): return pathlib.Path(__file__).resolve().parent.parent / "tests" / "sample_files" -@pytest.fixture(scope="session") -def init_dataframes(sample_dir, tmp_path_factory): +@pytest.fixture +def init_dataframes(sample_dir, tmp_path): """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. Returns ------- tuple path to parent and patch dataframes """ - tmp_path = tmp_path_factory.mktemp("patches") maps = MapImages(f"{sample_dir}/mapreader_text.png") maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") maps.patchify_all(patch_size=800, path_save=tmp_path) @@ -45,7 +45,17 @@ def init_dataframes(sample_dir, tmp_path_factory): return parent_df, patch_df -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") +def mock_response(monkeypatch, sample_dir): + def mock_pred(self, *args, **kwargs): + with open(f"{sample_dir}/patch-0-0-800-40-dptext-detr-pred.pkl", "rb") as f: + outputs = pickle.load(f) + return outputs + + monkeypatch.setattr(DefaultPredictor, "__call__", mock_pred) + + +@pytest.fixture def init_runner(init_dataframes): parent_df, patch_df = init_dataframes runner = DPTextDETRRunner( @@ -56,8 +66,8 @@ def init_runner(init_dataframes): return runner -@pytest.fixture(scope="session") -def runner_run_all(init_runner): +@pytest.fixture +def runner_run_all(init_runner, mock_response): runner = init_runner _ = runner.run_all() return runner @@ -122,7 +132,7 @@ def test_dptext_init_tsv(init_dataframes, tmp_path): assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) -def test_dptext_run_all(init_runner): +def test_dptext_run_all(init_runner, mock_response): runner = init_runner # dict out = runner.run_all() @@ -136,7 +146,7 @@ def test_dptext_run_all(init_runner): assert "patch-0-0-800-40-#mapreader_text.png#.png" in out["image_id"].values -def test_dptext_convert_to_parent(runner_run_all): +def test_dptext_convert_to_parent(runner_run_all, mock_response): runner = runner_run_all # dict out = runner.convert_to_parent_pixel_bounds() @@ -150,7 +160,7 @@ def test_dptext_convert_to_parent(runner_run_all): assert "mapreader_text.png" in out["image_id"].values -def test_dptext_convert_to_parent_coords(runner_run_all): +def test_dptext_convert_to_parent_coords(runner_run_all, mock_response): runner = runner_run_all # dict out = runner.convert_to_coords() @@ -165,7 +175,7 @@ def test_dptext_convert_to_parent_coords(runner_run_all): assert out.crs == runner.parent_df.crs -def test_dptext_deduplicate(sample_dir, tmp_path): +def test_dptext_deduplicate(sample_dir, tmp_path, mock_response): maps = MapImages(f"{sample_dir}/mapreader_text.png") maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") maps.patchify_all(patch_size=800, path_save=tmp_path, overlap=0.5) @@ -192,7 +202,7 @@ def test_dptext_deduplicate(sample_dir, tmp_path): assert len_07 >= len_05 -def test_dptext_run_on_image(init_runner): +def test_dptext_run_on_image(init_runner, mock_response): runner = init_runner out = runner.run_on_image( runner.patch_df.iloc[0]["image_path"], return_outputs=True @@ -202,7 +212,7 @@ def test_dptext_run_on_image(init_runner): assert isinstance(out["instances"], Instances) -def test_dptext_save_to_geojson(runner_run_all, tmp_path): +def test_dptext_save_to_geojson(runner_run_all, tmp_path, mock_response): runner = runner_run_all _ = runner.convert_to_coords() runner.save_to_geojson(f"{tmp_path}/text.geojson") diff --git a/test_text_spotting/test_maptext_runner.py b/test_text_spotting/test_maptext_runner.py index ee9a2547..9690bcc9 100644 --- a/test_text_spotting/test_maptext_runner.py +++ b/test_text_spotting/test_maptext_runner.py @@ -2,6 +2,7 @@ import os import pathlib +import pickle import adet import geopandas as gpd @@ -23,20 +24,19 @@ ) -@pytest.fixture(scope="session") +@pytest.fixture def sample_dir(): return pathlib.Path(__file__).resolve().parent.parent / "tests" / "sample_files" -@pytest.fixture(scope="session") -def init_dataframes(sample_dir, tmp_path_factory): +@pytest.fixture +def init_dataframes(sample_dir, tmp_path): """Initializes MapImages object (with metadata from csv and patches) and creates parent and patch dataframes. Returns ------- tuple path to parent and patch dataframes """ - tmp_path = tmp_path_factory.mktemp("patches") maps = MapImages(f"{sample_dir}/mapreader_text.png") maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") maps.patchify_all(patch_size=800, path_save=tmp_path) @@ -45,7 +45,17 @@ def init_dataframes(sample_dir, tmp_path_factory): return parent_df, patch_df -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") +def mock_response(monkeypatch, sample_dir): + def mock_pred(self, *args, **kwargs): + with open(f"{sample_dir}/patch-0-0-800-40-maptext-pred.pkl", "rb") as f: + outputs = pickle.load(f) + return outputs + + monkeypatch.setattr(DefaultPredictor, "__call__", mock_pred) + + +@pytest.fixture def init_runner(init_dataframes): parent_df, patch_df = init_dataframes runner = MapTextRunner( @@ -56,8 +66,8 @@ def init_runner(init_dataframes): return runner -@pytest.fixture(scope="session") -def runner_run_all(init_runner): +@pytest.fixture +def runner_run_all(init_runner, mock_response): runner = init_runner _ = runner.run_all() return runner @@ -122,7 +132,7 @@ def test_maptext_init_tsv(init_dataframes, tmp_path): assert isinstance(runner.patch_df.iloc[0]["coordinates"], tuple) -def test_maptext_run_all(init_runner): +def test_maptext_run_all(init_runner, mock_response): runner = init_runner # dict out = runner.run_all() @@ -136,7 +146,7 @@ def test_maptext_run_all(init_runner): assert "patch-0-0-800-40-#mapreader_text.png#.png" in out["image_id"].values -def test_maptext_convert_to_parent(runner_run_all): +def test_maptext_convert_to_parent(runner_run_all, mock_response): runner = runner_run_all # dict out = runner.convert_to_parent_pixel_bounds() @@ -152,7 +162,7 @@ def test_maptext_convert_to_parent(runner_run_all): assert "mapreader_text.png" in out["image_id"].values -def test_maptext_convert_to_parent_coords(runner_run_all): +def test_maptext_convert_to_parent_coords(runner_run_all, mock_response): runner = runner_run_all # dict out = runner.convert_to_coords() @@ -169,7 +179,7 @@ def test_maptext_convert_to_parent_coords(runner_run_all): assert out.crs == runner.parent_df.crs -def test_maptext_deduplicate(sample_dir, tmp_path): +def test_maptext_deduplicate(sample_dir, tmp_path, mock_response): maps = MapImages(f"{sample_dir}/mapreader_text.png") maps.add_metadata(f"{sample_dir}/mapreader_text_metadata.csv") maps.patchify_all(patch_size=800, path_save=tmp_path, overlap=0.5) @@ -196,7 +206,7 @@ def test_maptext_deduplicate(sample_dir, tmp_path): assert len_07 >= len_05 -def test_maptext_run_on_image(init_runner): +def test_maptext_run_on_image(init_runner, mock_response): runner = init_runner out = runner.run_on_image( runner.patch_df.iloc[0]["image_path"], return_outputs=True @@ -206,7 +216,7 @@ def test_maptext_run_on_image(init_runner): assert isinstance(out["instances"], Instances) -def test_maptext_save_to_geojson(runner_run_all, tmp_path): +def test_maptext_save_to_geojson(runner_run_all, tmp_path, mock_response): runner = runner_run_all _ = runner.convert_to_coords() runner.save_to_geojson(f"{tmp_path}/text.geojson") diff --git a/tests/sample_files/patch-0-0-800-40-deepsolo-pred.pkl b/tests/sample_files/patch-0-0-800-40-deepsolo-pred.pkl new file mode 100644 index 0000000000000000000000000000000000000000..97aadcd864ef26dae9d4b63b3856f76e7f0e4ab6 GIT binary patch literal 34261 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ëIJSaUCc9bqh6gSv8Fwf+#eoBgHtPT!3b5F!8tuWmWsz+ zlhMttrfA{nrN$W9XAFq?*sytjZD3);2G;L#8g$-x z$F8kRV1Cw@AuDg4B;k7^9P7(zMyMxWpV=Q3+*3(*<1);6CB}E#qtR`Q8Fid?5Z(5v z$IHLIP~Q8wh<$bWsaH-g{o!pq@_4Up&5LM>WWN*j@4J~Rbw3qp%M8WPG1p;NWnb{B z+Qf|Or(n^&-L@a~4zix>i&%y6I$Ik{f1BtRm9||oJ8fr))L9l}+HRk!X*+VkK~evt zYO&?RC;4O~3g&yuX=ZfUsOul>=*72nbUizX&R3=IAp3Zc{mMD?N9QT;a@s=1 z3DLY`Zwr65IURMb>59HLm5RqH`wJ>#-35)Wk*Me%f@j;1HYy#1Vy~}oaA7vlnF90PFT)+b)o^;wXw37tWHU?M3r^-Wq0XIW7=OJ3ANoD#P7lt3`PvrS z)LP;{&wS_d!Ha0toHKCSW-!o}Tr6F?im_^M7TBqPOa9q0U+;WTOlBP9f-FSc)(~l# zZeXvBvtV4axhU$#aF9E?m!(HU;$riqcHfWev3r<$0N-aB@-)XZ+@W)um)1z1F{}0H zhfD@O3mnI9Sj2*P)hYav@5A|&z7%e1#zPd0sCTwBVJ_g{GXE#PIZ#80p7xZ&jPr5r zQ9Ww@_64s8@5SD?6wvACI@;ZSg>{w~qA+j+KHqQ#?#Sw5#pp$##0SCi+GWsly)jI% z>jBMEE?Ly#`>_ zEFU^n^d2BPk~F^$ptXyW;IG$ZXwv?O4O7jy&FNWq{qa%M9uotD`o}>1O;c>}p2otW zuHeubVr>a8Sm%pAs2?E*x>d(

3#@k=D&VI_k2SkDoB(LD?+(a~jroc-sBo1MOB7 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 X?7uFj?Afznx6%Irhlt-) literal 0 HcmV?d00001 diff --git a/tests/sample_files/patch-0-0-800-40-dptext-detr-pred.pkl b/tests/sample_files/patch-0-0-800-40-dptext-detr-pred.pkl new file mode 100644 index 0000000000000000000000000000000000000000..c4e19624d8a81a5ff0bd050d0ca13acc02337b53 GIT binary patch literal 2357 zcmdUvdr(tX9>?X^oIX5;i6z_lAd- zBD+cvQ(?=kwNx2vF~bODUDoor2@$ZBmby;M+SaXNTWw2uY<-L?M11T;wvMxp=|BCa zzqxbIoZtP<_nzP9_nWg*@@jmj;6aqJ?of-3W^!!?6V1Bk8%>PKz&P!;WlEZHx(tlV zX`+>nC_*zIs_WQA6>J$B=a%U$+j8?wdfKwx#I8Lq;h##@9ircAFllaRH@wyJ4#9s3g}KUqH%)M5a=GSQ)vq(xyX7{*dJOwM?Tpj`sUbfz8G&xO1 zVlgm0%SBl%!|UB)g?vtLu;$V<&)6IhXO5y$e-4<}@SXS{l#`MZlT<3zk5m3Z_y}db zQ9nuiXD7@tKhU04Xt$Q;PxoVnbP+#oKkI=`SdsWB>a_~t|C{J(CC^&H&(?G#_9qkL zpa_hgq>#>$FtGQgkx5k~6n1Jz;l0P;<{2Qlf0sl3048DYM}uZyHQ9P30#YY4NWsk@ zFx<@`J1)KhFI`zp%ujUycMg;K7dhzb0#e8M;1y0onqTgQ41XF?JNyvanL?JNoCMaH zOr+UwaLH;0_LX9_Kh_dyc=<|WO zB!1{Rx@VtHt{s>_!7=kmwcZDLHFL?G8yt+Ejv;gUI5;GFf*6N5n3^Xi?piOrx-N_d;j#CPu+I$+v=82(d@fqA7C4}^tL3}D@ z3Z1_28E$yJ?nVJ6w?21zWD^adtZgC7*A?D>M9%8j^`OwEAH|Od5XWY7cZ2c~J6;L2#4b*b*-JEAAc?$=J>z<^H=ewcUcoCXs^+AcU1bk1Pg6y>I zNHOpMNaR&`-v0ZjwY>&=`bF?u&VKyqcrb)lAHrKD5;(WB5x;vYfa=?tvFy?~k~3{s z+W8Gy+17=3Wlo@!oG$$AHXr2BZCEwb0g7GCxNl`U1RrU{mvtSm(QycG`nD5>%lBjD z97a%%e>ba-$9DK&`U+2yfh?h8SHFT|7IZidb55wp!LNLvekO_hrJMuX`K3g%rVI9|l%(n? z2i(zkqJD#e;)StfOLsSn*DoMDx_aPt?|hxE|KI2C^~fNE0DQv2FPu-r33sp()i z77$@Yz62W1MB)txC(%3JdAMQYFgh$=jJ=*OQGSaWN3>r>1vzSL7I(u$?P7eQn}b^C zJj_OSLVbE9ZiwW-b4!SgzvbZ5=lkWa?& z`nRXhhyEM*Y?&CwE(Gx0kAxuA2}sV%w~>5QOct&iL2XMyNY>uVNY^VR5r6s~<-aB+ z>R}(WCWVjvR#C}n6yXWCO?Q-SO%6|dMU|>}M literal 0 HcmV?d00001 diff --git a/tests/sample_files/patch-0-0-800-40-maptext-pred.pkl b/tests/sample_files/patch-0-0-800-40-maptext-pred.pkl new file mode 100644 index 0000000000000000000000000000000000000000..f8ec12e2dc8d9063ac2045751246c02b0dc5c3e2 GIT binary patch literal 64822 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 z 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 literal 0 HcmV?d00001 From ea06538ffd1abfc380492b66b7b4be8032ce224e Mon Sep 17 00:00:00 2001 From: Rosie Wood Date: Thu, 12 Sep 2024 13:56:19 +0100 Subject: [PATCH 28/28] add docs, update changelog --- CHANGELOG.md | 1 + .../developers-corner/running-tests.rst | 45 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e72137e0..c828444e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ _ADD NEW CHANGES HERE_ - All file loading methods now support `pathlib.Path` and `gpd.GeoDataFrame` objects as input ([#495](https://github.com/maps-as-data/MapReader/pull/495)) - Loading of dataframes from GeoJSON files now supported in many file loading methods (e.g. `add_metadata`, `Annotator.__init__`, `AnnotationsLoader.load`, etc.) ([#495](https://github.com/maps-as-data/MapReader/pull/495)) - `load_frames.py` added to `mapreader.utils`. This has functions for loading from various file formats (e.g. CSV, Excel, GeoJSON, etc.) and converting to GeoDataFrames ([#495](https://github.com/maps-as-data/MapReader/pull/495)) +- Added tests for text spotting code ([#500](https://github.com/maps-as-data/MapReader/pull/500)) ### Changed diff --git a/docs/source/community-and-contributions/contribution-guide/developers-corner/running-tests.rst b/docs/source/community-and-contributions/contribution-guide/developers-corner/running-tests.rst index db20c10a..4b885ef4 100644 --- a/docs/source/community-and-contributions/contribution-guide/developers-corner/running-tests.rst +++ b/docs/source/community-and-contributions/contribution-guide/developers-corner/running-tests.rst @@ -1,9 +1,9 @@ Running tests ============= -To run the tests for MapReader, you will need to have installed the **dev dependencies** as described above. +To run the tests for MapReader, you will need to have installed the **dev dependencies** (as described :doc:`here `. -Also, if you have followed the "Install from PyPI" instructions, you will need to clone the MapReader repository to access the tests. i.e.: +.. note:: If you have followed the "Install from PyPI" instructions, you will also need to clone the MapReader repository to access the tests. i.e.: .. code-block:: bash @@ -18,3 +18,44 @@ You can then run the tests using from the root of the MapReader directory using python -m pytest -v If all tests pass, this means that MapReader has been installed and is working as expected. + +Testing text spotting +--------------------- + +The tests for the text spotting code are separated from the main tests due to dependency conflicts. + +You will only be able to run the text spotting tests for the text spotting framework (DPTextDETR, DeepSolo or MapTextPipeline) you have installed. + +For DPTextDETR, use the following commands: + +.. code-block:: bash + + cd path/to/MapReader # change this to your path, e.g. cd ~/MapReader + conda activate mapreader + export ADET_PATH=path/to/DPTextDETR # change this to the path where you have saved the DPTextDETR repository + wget https://huggingface.co/rwood-97/DPText_DETR_ArT_R_50_poly/resolve/main/art_final.pth # download the model weights + python -m pytest -v tests_text_spotting/test_dptext_runner.py + + +For DeepSolo: + +.. code-block:: bash + + cd path/to/MapReader # change this to your path, e.g. cd ~/MapReader + conda activate mapreader + export ADET_PATH=path/to/DeepSolo # change this to the path where you have saved the DeepSolo repository + wget https://huggingface.co/rwood-97/DeepSolo_ic15_res50/resolve/main/ic15_res50_finetune_synth-tt-mlt-13-15-textocr.pth # download the model weights + python -m pytest -v tests_text_spotting/test_deepsolo_runner.py + +For MapTextPipeline: + +.. code-block:: bash + + cd path/to/MapReader # change this to your path, e.g. cd ~/MapReader + conda activate mapreader + export ADET_PATH=path/to/MapTextPipeline # change this to the path where you have saved the MapTextPipeline repository + wget https://huggingface.co/rwood-97/MapTextPipeline_rumsey/resolve/main/rumsey-finetune.pth # download the model weights + python -m pytest -v tests_text_spotting/test_maptext_runner.py + + +If all tests pass, this means that the text spotting framework has been installed and is working as expected.