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.
Files changed (40) hide show
  1. pyguidos/__init__.py +167 -0
  2. pyguidos/accounting.py +410 -0
  3. pyguidos/checks.py +315 -0
  4. pyguidos/data/CLC2018_corsica_FNF.tif +0 -0
  5. pyguidos/data/CLC2018_corsica_LandMos.tif +0 -0
  6. pyguidos/data/GISCO_adm_corsica.gpkg +0 -0
  7. pyguidos/engine.py +653 -0
  8. pyguidos/extract_by_polygon.py +242 -0
  9. pyguidos/fragmentation.py +396 -0
  10. pyguidos/fragmentation_change.py +507 -0
  11. pyguidos/land_mosaic.py +690 -0
  12. pyguidos/rss.py +186 -0
  13. pyguidos/spa.py +370 -0
  14. pyguidos/templates/BRG.qml +404 -0
  15. pyguidos/templates/acc_colormap.txt +256 -0
  16. pyguidos/templates/acc_templ.txt +43 -0
  17. pyguidos/templates/frag_change_colormap.txt +256 -0
  18. pyguidos/templates/frag_change_templ.txt +118 -0
  19. pyguidos/templates/frag_colormap.txt +256 -0
  20. pyguidos/templates/frag_templ.txt +53 -0
  21. pyguidos/templates/lm_103to19.txt +104 -0
  22. pyguidos/templates/lm_19c_colormap.txt +256 -0
  23. pyguidos/templates/lm_agr_colormap.txt +256 -0
  24. pyguidos/templates/lm_ant_colormap.txt +256 -0
  25. pyguidos/templates/lm_bgr_colormap.txt +256 -0
  26. pyguidos/templates/lm_dev_colormap.txt +256 -0
  27. pyguidos/templates/lm_div_colormap.txt +256 -0
  28. pyguidos/templates/lm_nat_colormap.txt +256 -0
  29. pyguidos/templates/lm_templ.txt +66 -0
  30. pyguidos/templates/mspa_colormap.txt +256 -0
  31. pyguidos/templates/mspa_templ.txt +84 -0
  32. pyguidos/templates/rss_templ.txt +43 -0
  33. pyguidos/templates/spa_templ.txt +46 -0
  34. pyguidos/utils.py +525 -0
  35. pyguidos-2.3.0.dist-info/METADATA +292 -0
  36. pyguidos-2.3.0.dist-info/RECORD +40 -0
  37. pyguidos-2.3.0.dist-info/WHEEL +5 -0
  38. pyguidos-2.3.0.dist-info/entry_points.txt +2 -0
  39. pyguidos-2.3.0.dist-info/licenses/LICENSE +287 -0
  40. 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)]