mapreader 1.3.0__tar.gz → 1.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mapreader-1.3.0 → mapreader-1.3.2}/PKG-INFO +4 -3
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/__init__.py +5 -0
- mapreader-1.3.2/mapreader/__main__.py +6 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/_version.py +3 -3
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/annotate/annotator.py +33 -27
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/classify/classifier.py +5 -5
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/classify/load_annotations.py +9 -2
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/download/sheet_downloader.py +74 -27
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/download/tile_merging.py +22 -8
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/load/images.py +4 -3
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/process/post_process.py +40 -23
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader.egg-info/PKG-INFO +4 -3
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader.egg-info/SOURCES.txt +1 -0
- mapreader-1.3.2/mapreader.egg-info/entry_points.txt +2 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader.egg-info/requires.txt +3 -2
- {mapreader-1.3.0 → mapreader-1.3.2}/setup.py +4 -3
- {mapreader-1.3.0 → mapreader-1.3.2}/tests/test_annotator.py +4 -4
- mapreader-1.3.2/tests/test_post_processing.py +171 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/tests/test_sheet_downloader.py +152 -51
- mapreader-1.3.0/mapreader.egg-info/entry_points.txt +0 -2
- mapreader-1.3.0/tests/test_post_processing.py +0 -152
- {mapreader-1.3.0 → mapreader-1.3.2}/LICENSE +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/README.md +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/annotate/__init__.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/annotate/utils.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/classify/__init__.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/classify/custom_models.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/classify/datasets.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/download/__init__.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/download/data_structures.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/download/downloader.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/download/downloader_utils.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/download/tile_loading.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/load/__init__.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/load/geo_utils.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/load/loader.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/process/__init__.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/process/process.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/spot_text/__init__.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/spot_text/deepsolo_runner.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/spot_text/dptext_detr_runner.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/utils/__init__.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/utils/compute_and_save_stats.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader/utils/slice_parallel.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader.egg-info/dependency_links.txt +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader.egg-info/not-zip-safe +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/mapreader.egg-info/top_level.txt +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/setup.cfg +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/tests/test_geo_pipeline.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/tests/test_import.py +0 -0
- {mapreader-1.3.0 → mapreader-1.3.2}/versioneer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mapreader
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: A computer vision pipeline for the semantic exploration of maps/images at scale
|
|
5
5
|
Home-page: https://github.com/Living-with-machines/MapReader
|
|
6
6
|
Download-URL: https://github.com/Living-with-machines/MapReader/archive/refs/heads/main.zip
|
|
@@ -29,7 +29,7 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
License-File: LICENSE
|
|
30
30
|
Requires-Dist: matplotlib<4.0.0,>=3.5.0
|
|
31
31
|
Requires-Dist: numpy<2.0.0,>=1.21.5
|
|
32
|
-
Requires-Dist: pandas
|
|
32
|
+
Requires-Dist: pandas>=2.0.0
|
|
33
33
|
Requires-Dist: pyproj<4.0.0,>=3.2.0
|
|
34
34
|
Requires-Dist: azure-storage-blob<13.0.0,>=12.9.0
|
|
35
35
|
Requires-Dist: aiohttp<4.0.0,>=3.8.1
|
|
@@ -53,10 +53,11 @@ Requires-Dist: simplekml<2.0.0,>=1.3.6
|
|
|
53
53
|
Requires-Dist: versioneer>=0.28
|
|
54
54
|
Requires-Dist: tqdm<5.0.0
|
|
55
55
|
Requires-Dist: torchinfo<2.0.0
|
|
56
|
-
Requires-Dist: openpyxl<4.0.0
|
|
56
|
+
Requires-Dist: openpyxl<4.0.0,>=3.1.2
|
|
57
57
|
Requires-Dist: geopandas<1.0.0
|
|
58
58
|
Requires-Dist: pyogrio>=0.7.2
|
|
59
59
|
Requires-Dist: cartopy>=0.22.0
|
|
60
|
+
Requires-Dist: joblib>=1.4.0
|
|
60
61
|
Provides-Extra: dev
|
|
61
62
|
Requires-Dist: pytest<9.0.0; extra == "dev"
|
|
62
63
|
Requires-Dist: pytest-cov<6.0.0,>=4.1.0; extra == "dev"
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2024-
|
|
11
|
+
"date": "2024-05-15T11:10:51+0100",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "1.3.
|
|
14
|
+
"full-revisionid": "282b8733c2c83412b979c2c234adda1432691862",
|
|
15
|
+
"version": "1.3.2"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -27,7 +27,7 @@ _CENTER_LAYOUT = widgets.Layout(
|
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class Annotator
|
|
30
|
+
class Annotator:
|
|
31
31
|
"""
|
|
32
32
|
Annotator class for annotating patches with labels.
|
|
33
33
|
|
|
@@ -227,28 +227,29 @@ class Annotator(pd.DataFrame):
|
|
|
227
227
|
delimiter=delimiter,
|
|
228
228
|
)
|
|
229
229
|
|
|
230
|
-
# initiate as a DataFrame
|
|
231
|
-
super().__init__(patch_df)
|
|
232
|
-
|
|
233
230
|
## pixel_bounds = x0, y0, x1, y1
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
231
|
+
patch_df["min_x"] = patch_df["pixel_bounds"].apply(lambda x: x[0])
|
|
232
|
+
patch_df["min_y"] = patch_df["pixel_bounds"].apply(lambda x: x[1])
|
|
233
|
+
patch_df["max_x"] = patch_df["pixel_bounds"].apply(lambda x: x[2])
|
|
234
|
+
patch_df["max_y"] = patch_df["pixel_bounds"].apply(lambda x: x[3])
|
|
238
235
|
|
|
239
236
|
# Sort by sortby column if provided
|
|
240
237
|
if isinstance(sortby, str):
|
|
241
|
-
if sortby in
|
|
238
|
+
if sortby in patch_df.columns:
|
|
242
239
|
self._sortby = sortby
|
|
243
240
|
self._ascending = ascending
|
|
244
241
|
else:
|
|
245
|
-
raise ValueError(
|
|
242
|
+
raise ValueError(
|
|
243
|
+
f"[ERROR] {sortby} is not a column in the patch DataFrame."
|
|
244
|
+
)
|
|
246
245
|
elif sortby is not None:
|
|
247
246
|
raise ValueError("[ERROR] ``sortby`` must be a string or None.")
|
|
248
247
|
else:
|
|
249
248
|
self._sortby = None
|
|
250
249
|
self._ascending = True
|
|
251
250
|
|
|
251
|
+
self.patch_df = patch_df
|
|
252
|
+
|
|
252
253
|
self._labels = labels
|
|
253
254
|
self.label_col = label_col
|
|
254
255
|
self.patch_paths_col = patch_paths_col
|
|
@@ -299,6 +300,9 @@ class Annotator(pd.DataFrame):
|
|
|
299
300
|
# Setup queue
|
|
300
301
|
self._queue = []
|
|
301
302
|
|
|
303
|
+
def __len__(self):
|
|
304
|
+
return len(self.patch_df)
|
|
305
|
+
|
|
302
306
|
@staticmethod
|
|
303
307
|
def _load_dataframes(
|
|
304
308
|
patch_paths: str | None = None,
|
|
@@ -499,7 +503,7 @@ class Annotator(pd.DataFrame):
|
|
|
499
503
|
|
|
500
504
|
return True
|
|
501
505
|
|
|
502
|
-
queue_df = self.copy(deep=True)
|
|
506
|
+
queue_df = self.patch_df.copy(deep=True)
|
|
503
507
|
queue_df = queue_df[queue_df[self.label_col].isna()] # only unlabelled
|
|
504
508
|
queue_df["eligible"] = queue_df.apply(check_eligibility, axis=1)
|
|
505
509
|
|
|
@@ -562,20 +566,20 @@ class Annotator(pd.DataFrame):
|
|
|
562
566
|
|
|
563
567
|
ix = self._queue[self.current_index]
|
|
564
568
|
|
|
565
|
-
min_x = self.at[ix, "min_x"]
|
|
566
|
-
min_y = self.at[ix, "min_y"]
|
|
569
|
+
min_x = self.patch_df.at[ix, "min_x"]
|
|
570
|
+
min_y = self.patch_df.at[ix, "min_y"]
|
|
567
571
|
|
|
568
572
|
# cannot assume all patches are same size
|
|
569
573
|
try:
|
|
570
|
-
height, width, _ = self.at[ix, "shape"]
|
|
574
|
+
height, width, _ = self.patch_df.at[ix, "shape"]
|
|
571
575
|
except KeyError:
|
|
572
|
-
im_path = self.at[ix, self.patch_paths_col]
|
|
576
|
+
im_path = self.patch_df.at[ix, self.patch_paths_col]
|
|
573
577
|
im = Image.open(im_path)
|
|
574
578
|
height = im.height
|
|
575
579
|
width = im.width
|
|
576
580
|
|
|
577
|
-
current_parent = self.at[ix, "parent_id"]
|
|
578
|
-
parent_frame = self.query(f"parent_id=='{current_parent}'")
|
|
581
|
+
current_parent = self.patch_df.at[ix, "parent_id"]
|
|
582
|
+
parent_frame = self.patch_df.query(f"parent_id=='{current_parent}'")
|
|
579
583
|
|
|
580
584
|
deltas = list(range(-self.surrounding, self.surrounding + 1))
|
|
581
585
|
y_and_x = list(
|
|
@@ -770,7 +774,7 @@ class Annotator(pd.DataFrame):
|
|
|
770
774
|
|
|
771
775
|
ix = self._queue[self.current_index]
|
|
772
776
|
|
|
773
|
-
img_path = self.at[ix, self.patch_paths_col]
|
|
777
|
+
img_path = self.patch_df.at[ix, self.patch_paths_col]
|
|
774
778
|
|
|
775
779
|
self.render()
|
|
776
780
|
return self.previous_index, self.current_index, img_path
|
|
@@ -794,7 +798,7 @@ class Annotator(pd.DataFrame):
|
|
|
794
798
|
|
|
795
799
|
ix = self._queue[self.current_index]
|
|
796
800
|
|
|
797
|
-
img_path = self.at[ix, self.patch_paths_col]
|
|
801
|
+
img_path = self.patch_df.at[ix, self.patch_paths_col]
|
|
798
802
|
|
|
799
803
|
self.render()
|
|
800
804
|
return self.previous_index, self.current_index, img_path
|
|
@@ -826,7 +830,7 @@ class Annotator(pd.DataFrame):
|
|
|
826
830
|
# disable skip button when at last example
|
|
827
831
|
button.disabled = self.current_index >= len(self) - 1
|
|
828
832
|
elif button.description != "submit":
|
|
829
|
-
if self.at[ix, self.label_col] == button.description:
|
|
833
|
+
if self.patch_df.at[ix, self.label_col] == button.description:
|
|
830
834
|
button.icon = "check"
|
|
831
835
|
else:
|
|
832
836
|
button.icon = ""
|
|
@@ -842,8 +846,8 @@ class Annotator(pd.DataFrame):
|
|
|
842
846
|
else:
|
|
843
847
|
display(image.convert("RGB"))
|
|
844
848
|
add_ins = []
|
|
845
|
-
if "url" in self.loc[ix].keys():
|
|
846
|
-
url = self.at[ix, "url"]
|
|
849
|
+
if "url" in self.patch_df.loc[ix].keys():
|
|
850
|
+
url = self.patch_df.at[ix, "url"]
|
|
847
851
|
text = f'<p><a href="{url}" target="_blank">Click to see entire map.</a></p>'
|
|
848
852
|
add_ins += [widgets.HTML(text)]
|
|
849
853
|
|
|
@@ -881,7 +885,7 @@ class Annotator(pd.DataFrame):
|
|
|
881
885
|
PIL.Image
|
|
882
886
|
A PIL.Image object of the image at the given index.
|
|
883
887
|
"""
|
|
884
|
-
image_path = self.at[ix, self.patch_paths_col]
|
|
888
|
+
image_path = self.patch_df.at[ix, self.patch_paths_col]
|
|
885
889
|
image = Image.open(image_path)
|
|
886
890
|
|
|
887
891
|
if self.resize_to is not None:
|
|
@@ -907,7 +911,7 @@ class Annotator(pd.DataFrame):
|
|
|
907
911
|
"""
|
|
908
912
|
# ix = self.iloc[self.current_index].name
|
|
909
913
|
ix = self._queue[self.current_index]
|
|
910
|
-
self.at[ix, self.label_col] = annotation
|
|
914
|
+
self.patch_df.at[ix, self.label_col] = annotation
|
|
911
915
|
if self.auto_save:
|
|
912
916
|
self._auto_save()
|
|
913
917
|
self._next_example()
|
|
@@ -950,7 +954,9 @@ class Annotator(pd.DataFrame):
|
|
|
950
954
|
A dataframe containing the labelled images and their associated
|
|
951
955
|
label index.
|
|
952
956
|
"""
|
|
953
|
-
filtered_df = self[self[self.label_col].notna()].copy(
|
|
957
|
+
filtered_df = self.patch_df[self.patch_df[self.label_col].notna()].copy(
|
|
958
|
+
deep=True
|
|
959
|
+
)
|
|
954
960
|
|
|
955
961
|
# force image_id to be index (incase of integer index)
|
|
956
962
|
# TODO: Force all indices to be integers so this is not needed
|
|
@@ -973,8 +979,8 @@ class Annotator(pd.DataFrame):
|
|
|
973
979
|
|
|
974
980
|
@property
|
|
975
981
|
def filtered(self) -> pd.DataFrame:
|
|
976
|
-
_filter = ~self[self.label_col].isna()
|
|
977
|
-
return self[_filter]
|
|
982
|
+
_filter = ~self.patch_df[self.label_col].isna()
|
|
983
|
+
return self.patch_df[_filter]
|
|
978
984
|
|
|
979
985
|
def render_complete(self):
|
|
980
986
|
"""
|
|
@@ -1367,7 +1367,7 @@ Use ``add_criterion`` to define one."
|
|
|
1367
1367
|
def _initialize_model(
|
|
1368
1368
|
self,
|
|
1369
1369
|
model_name: str,
|
|
1370
|
-
|
|
1370
|
+
weights: str | None = "DEFAULT",
|
|
1371
1371
|
last_layer_num_classes: str | int | None = "default",
|
|
1372
1372
|
) -> tuple[Any, int, bool]:
|
|
1373
1373
|
"""
|
|
@@ -1378,8 +1378,9 @@ Use ``add_criterion`` to define one."
|
|
|
1378
1378
|
----------
|
|
1379
1379
|
model_name : str
|
|
1380
1380
|
Name of a PyTorch model. See https://pytorch.org/vision/0.8/models.html for options.
|
|
1381
|
-
|
|
1382
|
-
|
|
1381
|
+
weights : str, optional
|
|
1382
|
+
Weights to load into the model. If ``"DEFAULT"``, loads the default weights for the chosen model.
|
|
1383
|
+
By default, ``"DEFAULT"``.
|
|
1383
1384
|
last_layer_num_classes : str or int, optional
|
|
1384
1385
|
Number of elements in the last layer. If ``"default"``, sets it to
|
|
1385
1386
|
the number of classes. By default, ``"default"``.
|
|
@@ -1408,8 +1409,7 @@ Use ``add_criterion`` to define one."
|
|
|
1408
1409
|
|
|
1409
1410
|
# Initialize these variables which will be set in this if statement.
|
|
1410
1411
|
# Each of these variables is model specific.
|
|
1411
|
-
model_dw = models.
|
|
1412
|
-
model_dw = model_dw(pretrained)
|
|
1412
|
+
model_dw = models.get_model(model_name, weights=weights)
|
|
1413
1413
|
input_size = (224, 224)
|
|
1414
1414
|
is_inception = False
|
|
1415
1415
|
|
|
@@ -395,7 +395,9 @@ Please check your image paths and update them if necessary.'
|
|
|
395
395
|
)
|
|
396
396
|
iter_ids.append(annots2review.iloc[image_idx].name)
|
|
397
397
|
# Add to reviewed
|
|
398
|
-
self.reviewed =
|
|
398
|
+
self.reviewed = pd.concat(
|
|
399
|
+
[self.reviewed, annots2review.iloc[image_idx : image_idx + 1]]
|
|
400
|
+
)
|
|
399
401
|
try:
|
|
400
402
|
self.reviewed.drop_duplicates(subset=[deduplicate_col])
|
|
401
403
|
except Exception:
|
|
@@ -416,9 +418,14 @@ Please check your image paths and update them if necessary.'
|
|
|
416
418
|
]:
|
|
417
419
|
list_input_ids = user_input_ids.split(",")
|
|
418
420
|
print(
|
|
419
|
-
f"[INFO] Options for labels
|
|
421
|
+
f"[INFO] Options for labels:{list(self.annotations[self.label_col].unique())}"
|
|
420
422
|
)
|
|
421
423
|
input_label = input("Enter new label: ")
|
|
424
|
+
if input_label not in list(self.annotations[self.label_col].unique()):
|
|
425
|
+
print(
|
|
426
|
+
f'[ERROR] Label "{input_label}" not found in the annotations. Please enter a valid label.'
|
|
427
|
+
)
|
|
428
|
+
continue
|
|
422
429
|
|
|
423
430
|
for input_id in list_input_ids:
|
|
424
431
|
input_id = int(input_id)
|
|
@@ -533,7 +533,7 @@ class SheetDownloader:
|
|
|
533
533
|
"""
|
|
534
534
|
self.merger = TileMerger(output_folder=f"{path_save}/")
|
|
535
535
|
|
|
536
|
-
def _check_map_sheet_exists(self, feature: dict) -> bool:
|
|
536
|
+
def _check_map_sheet_exists(self, feature: dict, metadata_fname) -> bool:
|
|
537
537
|
"""
|
|
538
538
|
Checks if a map sheet is already saved.
|
|
539
539
|
|
|
@@ -544,51 +544,87 @@ class SheetDownloader:
|
|
|
544
544
|
Returns
|
|
545
545
|
-------
|
|
546
546
|
bool
|
|
547
|
-
|
|
547
|
+
img_path if file exists, False if not.
|
|
548
548
|
"""
|
|
549
|
-
map_name = str("map_" + feature["properties"]["IMAGE"])
|
|
550
549
|
path_save = self.merger.output_folder
|
|
551
|
-
|
|
550
|
+
|
|
551
|
+
try:
|
|
552
|
+
# get image id with same coords in metadata
|
|
553
|
+
existing_metadata_df = pd.read_csv(
|
|
554
|
+
f"{path_save}{metadata_fname}", sep=",", index_col=0
|
|
555
|
+
)
|
|
556
|
+
except FileNotFoundError:
|
|
557
|
+
return False
|
|
558
|
+
|
|
559
|
+
polygon = get_polygon_from_grid_bb(feature["grid_bb"])
|
|
560
|
+
if str(polygon.bounds) in existing_metadata_df["coordinates"].values:
|
|
561
|
+
image_id = existing_metadata_df[
|
|
562
|
+
existing_metadata_df["coordinates"] == str(polygon.bounds)
|
|
563
|
+
].iloc[0]["name"]
|
|
564
|
+
else:
|
|
565
|
+
return False # coordinates not in metadata means image doesn't exist
|
|
566
|
+
|
|
567
|
+
if os.path.exists(f"{path_save}{image_id}"):
|
|
552
568
|
try:
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
)
|
|
557
|
-
return True
|
|
569
|
+
# check image is valid
|
|
570
|
+
mpimg.imread(f"{path_save}{image_id}")
|
|
571
|
+
return image_id
|
|
558
572
|
except OSError:
|
|
559
573
|
return False
|
|
560
574
|
return False
|
|
561
575
|
|
|
562
|
-
def _download_map(
|
|
576
|
+
def _download_map(
|
|
577
|
+
self,
|
|
578
|
+
feature: dict,
|
|
579
|
+
existing_id: str | bool,
|
|
580
|
+
download_in_parallel: bool = True,
|
|
581
|
+
overwrite: bool = False,
|
|
582
|
+
) -> str | bool:
|
|
563
583
|
"""
|
|
564
584
|
Downloads a single map sheet and saves as png file.
|
|
565
585
|
|
|
566
586
|
Parameters
|
|
567
587
|
----------
|
|
568
588
|
feature : dict
|
|
589
|
+
The feature for which to download the map sheet.
|
|
590
|
+
existing_id : str | bool
|
|
591
|
+
The existing image id if the map sheet already exists.
|
|
592
|
+
download_in_parallel : bool, optional
|
|
593
|
+
Whether to download tiles in parallel, by default ``True``.
|
|
594
|
+
overwrite : bool, optional
|
|
595
|
+
Whether to overwrite existing maps, by default ``False``.
|
|
569
596
|
|
|
570
597
|
Returns
|
|
571
598
|
-------
|
|
572
|
-
bool
|
|
573
|
-
|
|
599
|
+
str or bool
|
|
600
|
+
image path if map was downloaded successfully, False if not.
|
|
574
601
|
"""
|
|
575
|
-
map_name = str("map_" + feature["properties"]["IMAGE"])
|
|
576
602
|
self.downloader.download_tiles(
|
|
577
603
|
feature["grid_bb"], download_in_parallel=download_in_parallel
|
|
578
604
|
)
|
|
579
|
-
|
|
580
|
-
if
|
|
581
|
-
|
|
605
|
+
|
|
606
|
+
if existing_id is False:
|
|
607
|
+
map_name = f"map_{feature['properties']['IMAGE']}"
|
|
608
|
+
else:
|
|
609
|
+
map_name = existing_id[:-4] # remove file extension (assuming .png)
|
|
610
|
+
|
|
611
|
+
img_path = self.merger.merge(
|
|
612
|
+
feature["grid_bb"], file_name=map_name, overwrite=overwrite
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
if img_path is not False:
|
|
616
|
+
print(f'[INFO] Downloaded "{img_path}"')
|
|
582
617
|
else:
|
|
583
|
-
print(f'[WARNING] Download of "{
|
|
618
|
+
print(f'[WARNING] Download of "{img_path}" was unsuccessful.')
|
|
584
619
|
|
|
585
620
|
shutil.rmtree(DEFAULT_TEMP_FOLDER)
|
|
586
|
-
return
|
|
621
|
+
return img_path
|
|
587
622
|
|
|
588
623
|
def _save_metadata(
|
|
589
624
|
self,
|
|
590
625
|
feature: dict,
|
|
591
626
|
out_filepath: str,
|
|
627
|
+
img_path: str,
|
|
592
628
|
metadata_to_save: dict | None = None,
|
|
593
629
|
**kwargs: dict | None,
|
|
594
630
|
) -> None:
|
|
@@ -602,6 +638,8 @@ class SheetDownloader:
|
|
|
602
638
|
The feature for which to extract the metadata from
|
|
603
639
|
out_filepath : str
|
|
604
640
|
The path to save metadata csv.
|
|
641
|
+
img_path : str
|
|
642
|
+
The path to the downloaded map sheet.
|
|
605
643
|
metadata_to_save : dict, optional
|
|
606
644
|
A dictionary containing column names (str) and metadata keys (str or list) to save to metadata csv.
|
|
607
645
|
Multilayer keys should be passed as a list, i.e. ["key1","key2"] will search for ``self.features[i]["key1"]["key2"]``.
|
|
@@ -631,7 +669,7 @@ class SheetDownloader:
|
|
|
631
669
|
metadata_dict = {col: None for col in metadata_cols}
|
|
632
670
|
|
|
633
671
|
# get default metadata
|
|
634
|
-
metadata_dict["name"] =
|
|
672
|
+
metadata_dict["name"] = os.path.basename(img_path)
|
|
635
673
|
metadata_dict["url"] = str(feature["properties"]["IMAGEURL"])
|
|
636
674
|
if not self.published_dates:
|
|
637
675
|
date_col = kwargs.get("date_col", None)
|
|
@@ -705,16 +743,25 @@ class SheetDownloader:
|
|
|
705
743
|
"""
|
|
706
744
|
|
|
707
745
|
for feature in tqdm(features):
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
746
|
+
existing_id = self._check_map_sheet_exists(feature, metadata_fname)
|
|
747
|
+
if (
|
|
748
|
+
not overwrite and existing_id is not False
|
|
749
|
+
): # if map already exists and overwrite is False then skip
|
|
750
|
+
print(f'[INFO] "{existing_id}" already exists. Skipping download.')
|
|
751
|
+
continue
|
|
752
|
+
img_path = self._download_map(
|
|
753
|
+
feature,
|
|
754
|
+
existing_id,
|
|
755
|
+
download_in_parallel=download_in_parallel,
|
|
756
|
+
overwrite=overwrite,
|
|
713
757
|
)
|
|
714
|
-
if
|
|
758
|
+
if img_path is not False:
|
|
715
759
|
metadata_path = f"{path_save}/{metadata_fname}"
|
|
716
760
|
self._save_metadata(
|
|
717
|
-
feature=feature,
|
|
761
|
+
feature=feature,
|
|
762
|
+
out_filepath=metadata_path,
|
|
763
|
+
img_path=img_path,
|
|
764
|
+
**kwargs,
|
|
718
765
|
)
|
|
719
766
|
|
|
720
767
|
def download_all_map_sheets(
|
|
@@ -1242,7 +1289,6 @@ Try passing coordinates (min_x, max_x, min_y, max_y) instead or leave blank to a
|
|
|
1242
1289
|
pass
|
|
1243
1290
|
|
|
1244
1291
|
for feature in features:
|
|
1245
|
-
text_id = feature["wfs_id_no"]
|
|
1246
1292
|
coords = np.array(feature["geometry"]["coordinates"][0][0])
|
|
1247
1293
|
|
|
1248
1294
|
# Plot coordinates
|
|
@@ -1255,6 +1301,7 @@ Try passing coordinates (min_x, max_x, min_y, max_y) instead or leave blank to a
|
|
|
1255
1301
|
)
|
|
1256
1302
|
|
|
1257
1303
|
if add_id:
|
|
1304
|
+
text_id = feature["wfs_id_no"]
|
|
1258
1305
|
plt.text(
|
|
1259
1306
|
np.mean(coords[:, 0]) - 0.15,
|
|
1260
1307
|
np.mean(coords[:, 1]) - 0.05,
|
|
@@ -142,7 +142,12 @@ class TileMerger:
|
|
|
142
142
|
tile_size = img_size[0]
|
|
143
143
|
return tile_size
|
|
144
144
|
|
|
145
|
-
def merge(
|
|
145
|
+
def merge(
|
|
146
|
+
self,
|
|
147
|
+
grid_bb: GridBoundingBox,
|
|
148
|
+
file_name: str | None = None,
|
|
149
|
+
overwrite: bool = False,
|
|
150
|
+
) -> str | bool:
|
|
146
151
|
"""Merges cells contained within GridBoundingBox.
|
|
147
152
|
|
|
148
153
|
Parameters
|
|
@@ -151,11 +156,13 @@ class TileMerger:
|
|
|
151
156
|
GridBoundingBox containing tiles to merge
|
|
152
157
|
file_name : Union[str, None], optional
|
|
153
158
|
Name to use when saving map
|
|
154
|
-
|
|
159
|
+
If None, default name will be used, by default None
|
|
160
|
+
overwrite : bool, optional
|
|
161
|
+
Whether or not to overwrite existing files, by default False
|
|
155
162
|
Returns
|
|
156
163
|
-------
|
|
157
|
-
bool
|
|
158
|
-
|
|
164
|
+
str or bool
|
|
165
|
+
out path if file has successfully downloaded, False if not.
|
|
159
166
|
"""
|
|
160
167
|
os.makedirs(self.output_folder, exist_ok=True)
|
|
161
168
|
|
|
@@ -191,11 +198,18 @@ class TileMerger:
|
|
|
191
198
|
file_name = self._get_output_name(grid_bb)
|
|
192
199
|
|
|
193
200
|
out_path = f"{self.output_folder}{file_name}.{self.img_output_format[0]}"
|
|
201
|
+
if not overwrite:
|
|
202
|
+
i = 1
|
|
203
|
+
while os.path.exists(out_path):
|
|
204
|
+
out_path = (
|
|
205
|
+
f"{self.output_folder}{file_name}_{i}.{self.img_output_format[0]}"
|
|
206
|
+
)
|
|
207
|
+
i += 1
|
|
194
208
|
merged_image.save(out_path, self.img_output_format[1])
|
|
195
|
-
success =
|
|
196
|
-
if success:
|
|
197
|
-
logger.info(f"Merge successful! The image has been stored at '{out_path}'")
|
|
198
|
-
else:
|
|
209
|
+
success = out_path if os.path.exists(out_path) else False
|
|
210
|
+
if success is False:
|
|
199
211
|
logger.warning(f"Merge unsuccessful! '{out_path}' not saved.")
|
|
212
|
+
else:
|
|
213
|
+
logger.info(f"Merge successful! The image has been stored at '{out_path}'")
|
|
200
214
|
|
|
201
215
|
return success
|
|
@@ -1392,7 +1392,6 @@ See https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes for mor
|
|
|
1392
1392
|
min_x, min_y, max_x, max_y = self.patches[patch]["pixel_bounds"]
|
|
1393
1393
|
if width != max_x - min_x:
|
|
1394
1394
|
width = max_x - min_x
|
|
1395
|
-
img = img.crop((0, 0, width, height))
|
|
1396
1395
|
if height != max_y - min_y:
|
|
1397
1396
|
height = max_y - min_y
|
|
1398
1397
|
img = img.crop((0, 0, width, height))
|
|
@@ -1400,22 +1399,24 @@ See https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes for mor
|
|
|
1400
1399
|
bands = img.getbands()
|
|
1401
1400
|
|
|
1402
1401
|
if calc_mean:
|
|
1403
|
-
if
|
|
1402
|
+
if "mean_pixel" in patch_keys:
|
|
1404
1403
|
calc_mean = False
|
|
1405
1404
|
if calc_std:
|
|
1406
|
-
if
|
|
1405
|
+
if "std_pixel" in patch_keys:
|
|
1407
1406
|
calc_std = False
|
|
1408
1407
|
|
|
1409
1408
|
img_stat = ImageStat.Stat(img)
|
|
1410
1409
|
|
|
1411
1410
|
if calc_mean:
|
|
1412
1411
|
img_mean = img_stat.mean
|
|
1412
|
+
self.patches[patch]["mean_pixel"] = np.mean(img_mean) / 255
|
|
1413
1413
|
for i, band in enumerate(bands):
|
|
1414
1414
|
# Calculate mean pixel values
|
|
1415
1415
|
self.patches[patch][f"mean_pixel_{band}"] = img_mean[i] / 255
|
|
1416
1416
|
|
|
1417
1417
|
if calc_std:
|
|
1418
1418
|
img_std = img_stat.stddev
|
|
1419
|
+
self.patches[patch]["std_pixel"] = np.mean(img_std) / 255
|
|
1419
1420
|
for i, band in enumerate(bands):
|
|
1420
1421
|
# Calculate std pixel values
|
|
1421
1422
|
self.patches[patch][f"std_pixel_{band}"] = img_std[i] / 255
|