pyguidos 2.3.0__py3-none-any.whl
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.
- pyguidos/__init__.py +167 -0
- pyguidos/accounting.py +410 -0
- pyguidos/checks.py +315 -0
- pyguidos/data/CLC2018_corsica_FNF.tif +0 -0
- pyguidos/data/CLC2018_corsica_LandMos.tif +0 -0
- pyguidos/data/GISCO_adm_corsica.gpkg +0 -0
- pyguidos/engine.py +653 -0
- pyguidos/extract_by_polygon.py +242 -0
- pyguidos/fragmentation.py +396 -0
- pyguidos/fragmentation_change.py +507 -0
- pyguidos/land_mosaic.py +690 -0
- pyguidos/rss.py +186 -0
- pyguidos/spa.py +370 -0
- pyguidos/templates/BRG.qml +404 -0
- pyguidos/templates/acc_colormap.txt +256 -0
- pyguidos/templates/acc_templ.txt +43 -0
- pyguidos/templates/frag_change_colormap.txt +256 -0
- pyguidos/templates/frag_change_templ.txt +118 -0
- pyguidos/templates/frag_colormap.txt +256 -0
- pyguidos/templates/frag_templ.txt +53 -0
- pyguidos/templates/lm_103to19.txt +104 -0
- pyguidos/templates/lm_19c_colormap.txt +256 -0
- pyguidos/templates/lm_agr_colormap.txt +256 -0
- pyguidos/templates/lm_ant_colormap.txt +256 -0
- pyguidos/templates/lm_bgr_colormap.txt +256 -0
- pyguidos/templates/lm_dev_colormap.txt +256 -0
- pyguidos/templates/lm_div_colormap.txt +256 -0
- pyguidos/templates/lm_nat_colormap.txt +256 -0
- pyguidos/templates/lm_templ.txt +66 -0
- pyguidos/templates/mspa_colormap.txt +256 -0
- pyguidos/templates/mspa_templ.txt +84 -0
- pyguidos/templates/rss_templ.txt +43 -0
- pyguidos/templates/spa_templ.txt +46 -0
- pyguidos/utils.py +525 -0
- pyguidos-2.3.0.dist-info/METADATA +292 -0
- pyguidos-2.3.0.dist-info/RECORD +40 -0
- pyguidos-2.3.0.dist-info/WHEEL +5 -0
- pyguidos-2.3.0.dist-info/entry_points.txt +2 -0
- pyguidos-2.3.0.dist-info/licenses/LICENSE +287 -0
- pyguidos-2.3.0.dist-info/top_level.txt +1 -0
pyguidos/__init__.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import inspect
|
|
5
|
+
import platform
|
|
6
|
+
import warnings
|
|
7
|
+
import tempfile
|
|
8
|
+
from numba import config
|
|
9
|
+
#import pyproj
|
|
10
|
+
|
|
11
|
+
# Internal paths
|
|
12
|
+
MODULE_ROOT = Path(__file__).resolve().parent
|
|
13
|
+
PROJECT_ROOT = MODULE_ROOT.parent
|
|
14
|
+
TEMPL_DIR = MODULE_ROOT / "templates"
|
|
15
|
+
DATA_DIR = MODULE_ROOT / "data"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Package metadata
|
|
19
|
+
__version__ = "2.3.0"
|
|
20
|
+
__author__ = "Caudullo G. & Vogt P., European Commission, Joint Research Centre"
|
|
21
|
+
|
|
22
|
+
# Global Numba Setup
|
|
23
|
+
def _setup_numba():
|
|
24
|
+
curr_os = platform.system()
|
|
25
|
+
|
|
26
|
+
# --- 1. WINDOWS-SPECIFIC DLL FIX ---
|
|
27
|
+
if curr_os == "Windows" and sys.version_info >= (3, 8):
|
|
28
|
+
# 'Library\bin' folder relative to the running Python interpreter
|
|
29
|
+
venv_base = sys.prefix
|
|
30
|
+
paths_to_check = [
|
|
31
|
+
os.path.join(venv_base, "Library", "bin"), # Conda style
|
|
32
|
+
os.path.join(venv_base, "Scripts"), # Standard venv style
|
|
33
|
+
os.path.join(venv_base, "bin") # Some local setups
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
for path in paths_to_check:
|
|
37
|
+
if os.path.exists(path):
|
|
38
|
+
try:
|
|
39
|
+
os.add_dll_directory(path)
|
|
40
|
+
except Exception:
|
|
41
|
+
pass # Ignore errors if path is already added or inaccessible
|
|
42
|
+
|
|
43
|
+
# --- 2. THREADING LAYER CONFIG ---
|
|
44
|
+
if curr_os == "Linux":
|
|
45
|
+
os.environ['NUMBA_THREADING_LAYER'] = 'omp'
|
|
46
|
+
elif curr_os == "Darwin":
|
|
47
|
+
os.environ['NUMBA_THREADING_LAYER'] = 'workqueue'
|
|
48
|
+
else:
|
|
49
|
+
# On Windows, we prefer TBB if available
|
|
50
|
+
os.environ['NUMBA_THREADING_LAYER'] = 'tbb'
|
|
51
|
+
|
|
52
|
+
# --- 3. GLOBAL CONFIGS ---
|
|
53
|
+
warnings.filterwarnings("ignore", message=".*TBB threading layer.*")
|
|
54
|
+
|
|
55
|
+
# --- 4. CACHE TO TEMP CONFIGS ---
|
|
56
|
+
cache_path = os.path.join(tempfile.gettempdir(), "numba_spatcon_cache")
|
|
57
|
+
if not os.path.exists(cache_path):
|
|
58
|
+
try:
|
|
59
|
+
os.makedirs(cache_path)
|
|
60
|
+
except OSError:
|
|
61
|
+
cache_path = os.path.join(os.getcwd(), ".numba_cache")
|
|
62
|
+
|
|
63
|
+
config.CACHE_DIR = cache_path
|
|
64
|
+
config.RELEASE_GIL = 1
|
|
65
|
+
|
|
66
|
+
# Execute setup upon import
|
|
67
|
+
_setup_numba()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ================================================================================
|
|
71
|
+
# Import Tools and Results
|
|
72
|
+
from .fragmentation import frag, frag_stats
|
|
73
|
+
from .fragmentation_change import frag_change
|
|
74
|
+
from .land_mosaic import landmos, landmos_stats
|
|
75
|
+
from .spa import spa, spa_stats
|
|
76
|
+
from .accounting import acc, acc_stats
|
|
77
|
+
from .rss import rss
|
|
78
|
+
from .extract_by_polygon import extract_by_polygon
|
|
79
|
+
from .utils import citation
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def info(tool: str = None):
|
|
83
|
+
"""
|
|
84
|
+
Displays quick-help and JRC documentation links for pyguidos tools.
|
|
85
|
+
|
|
86
|
+
Usage:
|
|
87
|
+
pg.info() # Lists all available tools
|
|
88
|
+
pg.info('acc') # Shows details and documentation links for Accounting
|
|
89
|
+
"""
|
|
90
|
+
registry = {
|
|
91
|
+
"spa": {
|
|
92
|
+
"title": "Simplified Pattern Analysis (SPA)",
|
|
93
|
+
"desc": "Classifies binary maps into mutually exclusive morphological classes "
|
|
94
|
+
"(Core, Edge, Islet, Loop, Perforation, Linear).",
|
|
95
|
+
"guide": "https://jrc-forest.pages.code.europa.eu/guidos/pyguidos/usage/mspa.html",
|
|
96
|
+
"sheet": "https://forest.jrc.ec.europa.eu/en/activities/lpa/mspa/"
|
|
97
|
+
},
|
|
98
|
+
"frag": {
|
|
99
|
+
"title": "Fragmentation",
|
|
100
|
+
"desc": "Calculates the Fragmentation with Fixed Observation Scale (FOS) approach.",
|
|
101
|
+
"guide": "https://jrc-forest.pages.code.europa.eu/guidos/pyguidos/usage/fragmentation.html",
|
|
102
|
+
"sheet": "https://ies-ows.jrc.ec.europa.eu/gtb/GTB/psheets/GTB-Fragmentation-FADFOS.pdf"
|
|
103
|
+
},
|
|
104
|
+
"frag_change": {
|
|
105
|
+
"title": "Fragmentation change",
|
|
106
|
+
"desc": "Calculates the change of two Fragmentation output tiff files.",
|
|
107
|
+
"guide": "https://jrc-forest.pages.code.europa.eu/guidos/pyguidos/usage/fragmentation_change.html",
|
|
108
|
+
"sheet": "https://ies-ows.jrc.ec.europa.eu/gtb/GTB/psheets/GTB-Fragmentation-FADFOS.pdf"
|
|
109
|
+
},
|
|
110
|
+
"landmos": {
|
|
111
|
+
"title": "Landscape Mosaic",
|
|
112
|
+
"desc": "Tri-modal landscape classification (Agriculture, Natural, Developed).",
|
|
113
|
+
"guide": "https://jrc-forest.pages.code.europa.eu/guidos/pyguidos/usage/landmos.html",
|
|
114
|
+
"sheet": "https://ies-ows.jrc.ec.europa.eu/gtb/GTB/psheets/GTB-Pattern-LM.pdf"
|
|
115
|
+
},
|
|
116
|
+
"acc": {
|
|
117
|
+
"title": "Accounting",
|
|
118
|
+
"desc": "Foreground patch size analysis.",
|
|
119
|
+
"guide": "https://jrc-forest.pages.code.europa.eu/guidos/pyguidos/usage/accounting.html",
|
|
120
|
+
"sheet": "https://ies-ows.jrc.ec.europa.eu/gtb/GTB/psheets/GTB-Objects-Accounting.pdf"
|
|
121
|
+
},
|
|
122
|
+
"rss": {
|
|
123
|
+
"title": "Restoration Status Summary",
|
|
124
|
+
"desc": "Provide patch-based connectivity indices for a binary raster map.",
|
|
125
|
+
"guide": "https://jrc-forest.pages.code.europa.eu/guidos/pyguidos/usage/rss.html",
|
|
126
|
+
"sheet": "https://ies-ows.jrc.ec.europa.eu/gtb/GTB/psheets/GTB-RestorationPlanner.pdf"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if not tool:
|
|
131
|
+
print("\n" + "═"*60)
|
|
132
|
+
print(" pyguidos: Available Analytical Tools")
|
|
133
|
+
print("═"*60)
|
|
134
|
+
for name, data in registry.items():
|
|
135
|
+
print(f" • {name:8} : {data['title']}")
|
|
136
|
+
print("\nType pg.info('tool_name') for detailed links.")
|
|
137
|
+
print("═"*60 + "\n")
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
tool = tool.lower()
|
|
141
|
+
if tool in registry:
|
|
142
|
+
t = registry[tool]
|
|
143
|
+
# Dynamically get the function object from the module
|
|
144
|
+
func = globals().get(tool)
|
|
145
|
+
|
|
146
|
+
print(f"\n{'─'*10} {t['title'].upper()} {'─'*10}")
|
|
147
|
+
print(f"Description: {t['desc']}")
|
|
148
|
+
print(f"User Guide: {t['guide']}")
|
|
149
|
+
print(f"Method Sheet: {t['sheet']}")
|
|
150
|
+
|
|
151
|
+
if func:
|
|
152
|
+
# This shows the exact arguments: e.g., mspa(in_tiff, foreground=2, ...)
|
|
153
|
+
signature = inspect.signature(func)
|
|
154
|
+
print(f"Usage: pg.{tool}{signature}")
|
|
155
|
+
|
|
156
|
+
print(f"\nFull usage: help(pg.{tool})\n")
|
|
157
|
+
|
|
158
|
+
# Exported names
|
|
159
|
+
__all__ = [
|
|
160
|
+
"spa", "spa_stats",
|
|
161
|
+
"frag", "frag_stats", "frag_change",
|
|
162
|
+
"landmos", "landmos_stats",
|
|
163
|
+
"acc", "acc_stats",
|
|
164
|
+
"rss",
|
|
165
|
+
"extract_by_polygon",
|
|
166
|
+
"citation", "info"
|
|
167
|
+
]
|
pyguidos/accounting.py
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import collections
|
|
5
|
+
|
|
6
|
+
import rasterio
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from . import utils
|
|
10
|
+
from . import engine
|
|
11
|
+
from . import checks
|
|
12
|
+
from . import TEMPL_DIR
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ACC_VALUES = [103, 33, 65, 1, 9, 17]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def acc(
|
|
19
|
+
in_tiff,
|
|
20
|
+
thresholds,
|
|
21
|
+
outdir=None,
|
|
22
|
+
statists=True,
|
|
23
|
+
stat_files=True,
|
|
24
|
+
verb=False
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Performs Foreground Patch Size Accounting (ACC) on a binary or
|
|
28
|
+
multi-class raster. Each foreground patch is classified into size
|
|
29
|
+
categories defined by the user-provided thresholds, allowing
|
|
30
|
+
analysis of patch size distribution across the landscape.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
in_tiff : str or Path
|
|
35
|
+
Path to the input GeoTIFF. Must be uint8 with values:
|
|
36
|
+
0 = NoData, 1 = Background, 2 = Foreground.
|
|
37
|
+
Optional: 3 = Background class 2, 4 = Background class 3.
|
|
38
|
+
thresholds : list, tuple or np.ndarray
|
|
39
|
+
Sequence of 1 to 5 unique positive integers defining the patch
|
|
40
|
+
size class boundaries in pixels. For example, [10, 100, 1000]
|
|
41
|
+
creates 4 classes: [1-10], [11-100], [101-1000], [>1000].
|
|
42
|
+
outdir : str or Path, optional
|
|
43
|
+
Directory for output files. Defaults to the input file's directory.
|
|
44
|
+
statists : bool, optional
|
|
45
|
+
If True (default), computes and returns statistics.
|
|
46
|
+
stat_files : bool, optional
|
|
47
|
+
If True (default), writes statistics to a .txt report file.
|
|
48
|
+
verb : bool, optional
|
|
49
|
+
If True, prints progress messages. Default False.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
dict
|
|
54
|
+
Nested dictionary with three keys:
|
|
55
|
+
- 'output paths' (dict or None): paths to generated output files
|
|
56
|
+
('path tif', 'path txt'), or None if outfile=False.
|
|
57
|
+
- 'input stats' (dict): pixel counts for foreground, background,
|
|
58
|
+
missing and special class pixels.
|
|
59
|
+
- 'output stats' (dict): per-class pixel counts and patch counts
|
|
60
|
+
for each accounting size class.
|
|
61
|
+
|
|
62
|
+
Output Files
|
|
63
|
+
------------
|
|
64
|
+
- <in_name>_acc.tif : accounting result
|
|
65
|
+
- <in_name>_acc.txt : statistics report
|
|
66
|
+
"""
|
|
67
|
+
start_time = time.time()
|
|
68
|
+
|
|
69
|
+
# Log
|
|
70
|
+
utils.log_msg(verb, "[ START ] Verifying input raster...")
|
|
71
|
+
|
|
72
|
+
# Validate parametres
|
|
73
|
+
thresholds = checks.validate_acc_params(thresholds)
|
|
74
|
+
|
|
75
|
+
# Initialize Paths and Metadata
|
|
76
|
+
in_tiff = Path(in_tiff)
|
|
77
|
+
outdir = Path(outdir) if outdir else in_tiff.parent
|
|
78
|
+
in_name = in_tiff.stem
|
|
79
|
+
out_name = f"{in_name}_acc"
|
|
80
|
+
info = utils.get_raster_info(in_tiff)
|
|
81
|
+
|
|
82
|
+
# Read the input Geotiff
|
|
83
|
+
with rasterio.open(in_tiff) as src:
|
|
84
|
+
input_data = src.read(1).astype(np.int16)
|
|
85
|
+
|
|
86
|
+
# Get the pixel counting
|
|
87
|
+
input_pxl_freq = utils.get_pxl_freq(input_data)
|
|
88
|
+
|
|
89
|
+
# Input Geotiff validations
|
|
90
|
+
checks.validate_fmap_input(list(input_pxl_freq.keys()), info["bands"], info['dtype'], allow_34=True)
|
|
91
|
+
|
|
92
|
+
# Log
|
|
93
|
+
utils.log_msg(verb, "[ OK ] Input raster verified.")
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
# Log
|
|
97
|
+
utils.log_msg(verb, "[ START ] Computing Accounting...")
|
|
98
|
+
|
|
99
|
+
# Get patch size frequencies
|
|
100
|
+
labeled_array, lab_pxl_freq = engine.labelling_array(input_data, 2)
|
|
101
|
+
|
|
102
|
+
# Create a lookup array for high-speed mapping
|
|
103
|
+
max_id = max(lab_pxl_freq.keys())
|
|
104
|
+
lookup = np.zeros(int(max_id) + 1, dtype=np.uint32)
|
|
105
|
+
|
|
106
|
+
# Fill the lookup table
|
|
107
|
+
for patch_id, pixel_count in lab_pxl_freq.items():
|
|
108
|
+
# Skip the Background (0)
|
|
109
|
+
if patch_id == 0:
|
|
110
|
+
lookup[patch_id] = 0 # Keep Background as 0
|
|
111
|
+
continue
|
|
112
|
+
lookup[patch_id] = get_class(pixel_count, thresholds)
|
|
113
|
+
|
|
114
|
+
# Reclassification
|
|
115
|
+
reclass_array = lookup[labeled_array.astype(np.int32)]
|
|
116
|
+
reclass_array = reclass_array.astype(np.uint8, casting='unsafe')
|
|
117
|
+
|
|
118
|
+
# Mapping logic (NoData, Special codes)
|
|
119
|
+
choices = np.array([129, 105, 176], dtype=np.uint8)
|
|
120
|
+
out_array = np.select(
|
|
121
|
+
[input_data == 0, input_data == 3, input_data == 4],
|
|
122
|
+
choices,
|
|
123
|
+
default=reclass_array
|
|
124
|
+
).astype(np.uint8, casting='unsafe')
|
|
125
|
+
|
|
126
|
+
# Save Final Geotiff with Palette and Tags
|
|
127
|
+
thresh_list = ",".join([str(x) for x in thresholds])
|
|
128
|
+
weblink = "https://forest.jrc.ec.europa.eu/en/activities/lpa/"
|
|
129
|
+
tag_descr = f"GTB_ACC, <{thresh_list}>, {weblink}"
|
|
130
|
+
cmap_path = TEMPL_DIR / "acc_colormap.txt"
|
|
131
|
+
out_tiff = outdir / f"{out_name}.tif"
|
|
132
|
+
utils.save_output_geotiff(out_tiff, out_array, info['profile'], cmap_path, tag_descr)
|
|
133
|
+
|
|
134
|
+
# Log
|
|
135
|
+
utils.log_msg(verb, "[ OK ] Accounting computed.")
|
|
136
|
+
|
|
137
|
+
# Statistics and Reporting
|
|
138
|
+
stats_dict = None
|
|
139
|
+
if statists:
|
|
140
|
+
# Log
|
|
141
|
+
utils.log_msg(verb, "[ START ] Generating statistics...")
|
|
142
|
+
|
|
143
|
+
minfo = utils.get_raster_info(out_tiff)
|
|
144
|
+
acc_pxl_freq = utils.get_pxl_freq(out_array)
|
|
145
|
+
stats_dict = _get_acc_stats(acc_freq = acc_pxl_freq,
|
|
146
|
+
lab_freq = lab_pxl_freq,
|
|
147
|
+
tiff_info = minfo,
|
|
148
|
+
outfile=stat_files,
|
|
149
|
+
out_name=out_name,
|
|
150
|
+
out_dir=outdir,
|
|
151
|
+
source_tiff=in_tiff)
|
|
152
|
+
|
|
153
|
+
# Computational time
|
|
154
|
+
time_str = utils.running_time(start_time, time.time())
|
|
155
|
+
if statists:
|
|
156
|
+
txt_file = outdir / f'{out_name}.txt'
|
|
157
|
+
utils.update_time_line(txt_file, time_str)
|
|
158
|
+
utils.log_msg(verb, "[ OK ] Statistics complete and files saved.")
|
|
159
|
+
|
|
160
|
+
# Log
|
|
161
|
+
utils.log_msg(verb, f"\n>>> Accounting task finished in {time_str}")
|
|
162
|
+
|
|
163
|
+
return stats_dict
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
print(f"Error during run: {e}")
|
|
167
|
+
raise # Still show the error
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def acc_stats(acc_tiff, stat_files=True, outdir=None, source_tiff=None):
|
|
172
|
+
"""
|
|
173
|
+
Computes statistics for an existing Accounting GeoTIFF. Can be
|
|
174
|
+
called independently on a previously generated accounting output.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
acc_tiff : str or Path
|
|
179
|
+
Path to the accounting result GeoTIFF. Must contain a valid GTB_ACC
|
|
180
|
+
metadata tag in the TIFFTAG_IMAGEDESCRIPTION field.
|
|
181
|
+
stat_files : bool, optional
|
|
182
|
+
If True (default), writes statistics to a .txt report file.
|
|
183
|
+
outdir : str or Path, optional
|
|
184
|
+
Directory for output files. Defaults to the input file's directory.
|
|
185
|
+
source_tiff : str or Path, optional
|
|
186
|
+
Path to the original input GeoTIFF used to generate the accounting
|
|
187
|
+
result. Used only to report the source filename in the statistics
|
|
188
|
+
report. Default None.
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
dict
|
|
193
|
+
Nested dictionary with three keys:
|
|
194
|
+
- 'output paths' (dict or None): paths to generated output files
|
|
195
|
+
('path tif', 'path txt'), or None if outfile=False.
|
|
196
|
+
- 'input stats' (dict): pixel counts for foreground, background,
|
|
197
|
+
missing and special class pixels.
|
|
198
|
+
- 'output stats' (dict): per-class pixel counts and patch counts
|
|
199
|
+
for each accounting size class.
|
|
200
|
+
|
|
201
|
+
Note: 'output paths' is None when outfile=False. All other keys
|
|
202
|
+
are always populated regardless of outfile.
|
|
203
|
+
|
|
204
|
+
Output Files
|
|
205
|
+
------------
|
|
206
|
+
- <acc_tiff_stem>.txt : statistics report
|
|
207
|
+
"""
|
|
208
|
+
start_time_stat = time.time()
|
|
209
|
+
|
|
210
|
+
# Read metadata
|
|
211
|
+
acc_tiff = Path(acc_tiff)
|
|
212
|
+
minfo = utils.get_raster_info(acc_tiff)
|
|
213
|
+
if minfo["tag"] is None:
|
|
214
|
+
sys.exit("ERROR: No valid GuidosToolbox metadata found in the input Geotiff")
|
|
215
|
+
|
|
216
|
+
# Check input tag with used tool and parametres
|
|
217
|
+
tool_params = utils.get_tool_parameters(minfo["tag"])
|
|
218
|
+
if tool_params.get("tool_id") != "GTB_ACC":
|
|
219
|
+
sys.exit(f"ERROR: Input Geotiff is labeled as '{tool_params.get('tool_id')}', "
|
|
220
|
+
"acc_stats requires a 'GTB_ACC' result file."
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Define input and output file names
|
|
224
|
+
out_name = Path(acc_tiff).stem
|
|
225
|
+
outdir = Path(outdir) if outdir else acc_tiff.parent
|
|
226
|
+
source_tiff = Path(source_tiff) if source_tiff else None
|
|
227
|
+
|
|
228
|
+
# Accounting pixel and patch counting
|
|
229
|
+
with rasterio.open(acc_tiff) as src:
|
|
230
|
+
acc_data = src.read(1)
|
|
231
|
+
acc_pxl_freq = utils.get_pxl_freq(acc_data)
|
|
232
|
+
|
|
233
|
+
# Get statistics
|
|
234
|
+
stats_dict = _get_acc_stats(acc_freq = acc_pxl_freq,
|
|
235
|
+
lab_freq = None,
|
|
236
|
+
tiff_info = minfo,
|
|
237
|
+
outfile = stat_files,
|
|
238
|
+
out_name = out_name,
|
|
239
|
+
out_dir = outdir,
|
|
240
|
+
source_tiff = source_tiff)
|
|
241
|
+
|
|
242
|
+
# Computational time
|
|
243
|
+
time_str = utils.running_time(start_time_stat, time.time())
|
|
244
|
+
if stat_files:
|
|
245
|
+
txt_file = outdir / f'{out_name}.txt'
|
|
246
|
+
utils.update_time_line(txt_file, time_str)
|
|
247
|
+
|
|
248
|
+
return stats_dict
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
#############
|
|
252
|
+
|
|
253
|
+
def _get_acc_stats(acc_freq,
|
|
254
|
+
tiff_info,
|
|
255
|
+
lab_freq=None,
|
|
256
|
+
outfile=True,
|
|
257
|
+
out_name=None,
|
|
258
|
+
out_dir=None,
|
|
259
|
+
source_tiff=None):
|
|
260
|
+
"""
|
|
261
|
+
Get the Accounting statistics.
|
|
262
|
+
"""
|
|
263
|
+
# Get ACC parameters
|
|
264
|
+
tag = tiff_info["tag"]
|
|
265
|
+
tool_params = utils.get_tool_parameters(tag)
|
|
266
|
+
thre_str = tool_params["thresholds"]
|
|
267
|
+
thresholds = [int(t) for t in thre_str]
|
|
268
|
+
|
|
269
|
+
# Define input and output file names
|
|
270
|
+
source_tiff = Path(source_tiff) if source_tiff else None
|
|
271
|
+
|
|
272
|
+
# Counting pixel per ACC class
|
|
273
|
+
bgrnd = acc_freq[0]
|
|
274
|
+
bgr3 = acc_freq[105]
|
|
275
|
+
bgr4 = acc_freq[176]
|
|
276
|
+
ndata = acc_freq[129]
|
|
277
|
+
fgrnd = (tiff_info["rows"] * tiff_info["cols"]) - bgrnd - bgr3 - bgr4 - ndata
|
|
278
|
+
|
|
279
|
+
# Counting patches and pixels per class
|
|
280
|
+
tot_pch = "-"
|
|
281
|
+
avg_size_txt = "-"
|
|
282
|
+
median_size = "-"
|
|
283
|
+
largest_size = "-"
|
|
284
|
+
class_pch = None
|
|
285
|
+
if lab_freq:
|
|
286
|
+
class_pch = collections.Counter()
|
|
287
|
+
patch_sizes = []
|
|
288
|
+
for patch_id, pixel_count in lab_freq.items():
|
|
289
|
+
if patch_id > 0:
|
|
290
|
+
patch_class = get_class(pixel_count, thresholds)
|
|
291
|
+
class_pch[patch_class] += 1
|
|
292
|
+
patch_sizes.append(pixel_count)
|
|
293
|
+
|
|
294
|
+
tot_pch = len(patch_sizes)
|
|
295
|
+
avg_size = np.mean(patch_sizes)
|
|
296
|
+
avg_size_txt = f"{avg_size:.1f}"
|
|
297
|
+
median_size = np.median(patch_sizes)
|
|
298
|
+
largest_size = np.max(patch_sizes)
|
|
299
|
+
|
|
300
|
+
if outfile:
|
|
301
|
+
### TXT Template Reporting ###
|
|
302
|
+
|
|
303
|
+
color_seq = ["black", "red", "yellow", "orange", "brown", "green"]
|
|
304
|
+
rows_list = []
|
|
305
|
+
num_thr = len(thresholds)
|
|
306
|
+
|
|
307
|
+
class_pxl_out = {}
|
|
308
|
+
class_pth_out = {}
|
|
309
|
+
for i in range(num_thr + 1):
|
|
310
|
+
# Determine Class Value and Color from the sequences
|
|
311
|
+
val = ACC_VALUES[i]
|
|
312
|
+
color = color_seq[i]
|
|
313
|
+
|
|
314
|
+
# Determine the Size String
|
|
315
|
+
if i == 0:
|
|
316
|
+
size_raw = f"[1-{thresholds[i]}]"
|
|
317
|
+
elif i < num_thr:
|
|
318
|
+
size_raw = f"[{thresholds[i-1]+1}-{thresholds[i]}]"
|
|
319
|
+
else:
|
|
320
|
+
size_raw = f"[>{thresholds[-1]}]"
|
|
321
|
+
|
|
322
|
+
size_col = f"{size_raw:<16}"
|
|
323
|
+
|
|
324
|
+
# Pixel class count and freq
|
|
325
|
+
p_count = acc_freq[val]
|
|
326
|
+
p_pct = (p_count / fgrnd * 100) if fgrnd > 0 else 0
|
|
327
|
+
class_pxl_out[f'{i+1} {size_raw}'] = p_count
|
|
328
|
+
|
|
329
|
+
# Patch class count and freq
|
|
330
|
+
o_count_txt = " -"
|
|
331
|
+
o_pct_txt = " -"
|
|
332
|
+
if lab_freq:
|
|
333
|
+
o_count = class_pch.get(val, 0)
|
|
334
|
+
o_pct = (o_count / tot_pch * 100) if tot_pch > 0 else 0
|
|
335
|
+
o_count_txt = f"{o_count:>9}"
|
|
336
|
+
o_pct_txt = f"{o_pct:>8.2f}"
|
|
337
|
+
class_pth_out[f'{i+1} {size_raw}']=o_count
|
|
338
|
+
|
|
339
|
+
# Format the row string
|
|
340
|
+
row = (f"{i+1:<6} {val:<8} {color:<7} {size_col} "
|
|
341
|
+
f"{p_count:>10} {p_pct:>8.2f} {o_count_txt} {o_pct_txt}")
|
|
342
|
+
rows_list.append(row)
|
|
343
|
+
|
|
344
|
+
# Combine rows into a single string
|
|
345
|
+
table_body = "\n".join(rows_list)
|
|
346
|
+
|
|
347
|
+
content = {
|
|
348
|
+
"input_file": source_tiff.name if source_tiff else "n/a",
|
|
349
|
+
"epsg_code": tiff_info["epsg"],
|
|
350
|
+
"unit_type": 'metres' if tiff_info["is_projected"] else 'degrees',
|
|
351
|
+
"resolx": tiff_info["resX"],
|
|
352
|
+
"resoly": tiff_info["resY"],
|
|
353
|
+
"rows_val": tiff_info["rows"],
|
|
354
|
+
"cols_val": tiff_info["cols"],
|
|
355
|
+
"tot_pxl": tiff_info["rows"] * tiff_info["cols"],
|
|
356
|
+
"foreg_pxl": fgrnd,
|
|
357
|
+
"backg_pxl": bgrnd,
|
|
358
|
+
"miss_pxl": ndata,
|
|
359
|
+
"spec3_pxl": bgr3,
|
|
360
|
+
"spec4_pxl": bgr4,
|
|
361
|
+
|
|
362
|
+
"threshold_list": " ".join(thre_str),
|
|
363
|
+
|
|
364
|
+
"output_file": f"{out_name}.tif",
|
|
365
|
+
"table_rows": table_body,
|
|
366
|
+
"tot_pch": tot_pch,
|
|
367
|
+
"avg_pch": avg_size_txt,
|
|
368
|
+
"med_pch": median_size,
|
|
369
|
+
"lar_pch": largest_size,
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
txt_file = out_dir / f'{out_name}.txt'
|
|
373
|
+
utils.generate_text_report(TEMPL_DIR / 'acc_templ.txt', txt_file, content)
|
|
374
|
+
|
|
375
|
+
# Statistic dictionaries
|
|
376
|
+
path_stats_dict = None
|
|
377
|
+
if outfile:
|
|
378
|
+
path_stats_dict = {
|
|
379
|
+
"path tif" : str(out_dir / f"{out_name}.tif"),
|
|
380
|
+
"path txt" : str(txt_file)
|
|
381
|
+
}
|
|
382
|
+
input_stats_dict = {
|
|
383
|
+
"foreground pxl" : fgrnd,
|
|
384
|
+
"background pxl" : bgrnd,
|
|
385
|
+
"missing pxl" : ndata,
|
|
386
|
+
"backgr3 pxl" : bgr3,
|
|
387
|
+
"backgr4 pxl" : bgr4
|
|
388
|
+
}
|
|
389
|
+
output_stats_dict = {
|
|
390
|
+
"class pxl": class_pxl_out,
|
|
391
|
+
"class patch": class_pth_out
|
|
392
|
+
}
|
|
393
|
+
stats_dict = {
|
|
394
|
+
"output paths" : path_stats_dict,
|
|
395
|
+
"input stats" : input_stats_dict,
|
|
396
|
+
"output stats" : output_stats_dict
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return stats_dict
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def get_class(count, thresholds):
|
|
403
|
+
"""
|
|
404
|
+
Checks count against thresholds.
|
|
405
|
+
"""
|
|
406
|
+
for i, t in enumerate(thresholds):
|
|
407
|
+
if count <= t:
|
|
408
|
+
return ACC_VALUES[i]
|
|
409
|
+
# Return the last class
|
|
410
|
+
return ACC_VALUES[len(thresholds)]
|