BERATools 0.2.3__py3-none-any.whl → 0.2.4__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 (78) hide show
  1. beratools/__init__.py +8 -3
  2. beratools/core/{algo_footprint_rel.py → algo_canopy_footprint_exp.py} +176 -139
  3. beratools/core/algo_centerline.py +61 -77
  4. beratools/core/algo_common.py +48 -57
  5. beratools/core/algo_cost.py +18 -25
  6. beratools/core/algo_dijkstra.py +37 -45
  7. beratools/core/algo_line_grouping.py +100 -100
  8. beratools/core/algo_merge_lines.py +40 -8
  9. beratools/core/algo_split_with_lines.py +289 -304
  10. beratools/core/algo_vertex_optimization.py +25 -46
  11. beratools/core/canopy_threshold_relative.py +755 -0
  12. beratools/core/constants.py +8 -9
  13. beratools/{tools → core}/line_footprint_functions.py +411 -258
  14. beratools/core/logger.py +18 -2
  15. beratools/core/tool_base.py +17 -75
  16. beratools/gui/assets/BERALogo.ico +0 -0
  17. beratools/gui/assets/BERA_Splash.gif +0 -0
  18. beratools/gui/assets/BERA_WizardImage.png +0 -0
  19. beratools/gui/assets/beratools.json +475 -2171
  20. beratools/gui/bt_data.py +585 -234
  21. beratools/gui/bt_gui_main.py +129 -91
  22. beratools/gui/main.py +4 -7
  23. beratools/gui/tool_widgets.py +530 -354
  24. beratools/tools/__init__.py +0 -7
  25. beratools/tools/{line_footprint_absolute.py → canopy_footprint_absolute.py} +81 -56
  26. beratools/tools/canopy_footprint_exp.py +113 -0
  27. beratools/tools/centerline.py +30 -37
  28. beratools/tools/check_seed_line.py +127 -0
  29. beratools/tools/common.py +65 -586
  30. beratools/tools/{line_footprint_fixed.py → ground_footprint.py} +140 -117
  31. beratools/tools/line_footprint_relative.py +64 -35
  32. beratools/tools/tool_template.py +48 -40
  33. beratools/tools/vertex_optimization.py +20 -34
  34. beratools/utility/env_checks.py +53 -0
  35. beratools/utility/spatial_common.py +210 -0
  36. beratools/utility/tool_args.py +138 -0
  37. beratools-0.2.4.dist-info/METADATA +134 -0
  38. beratools-0.2.4.dist-info/RECORD +50 -0
  39. {beratools-0.2.3.dist-info → beratools-0.2.4.dist-info}/WHEEL +1 -1
  40. beratools-0.2.4.dist-info/entry_points.txt +3 -0
  41. beratools-0.2.4.dist-info/licenses/LICENSE +674 -0
  42. beratools/core/algo_tiler.py +0 -428
  43. beratools/gui/__init__.py +0 -11
  44. beratools/gui/batch_processing_dlg.py +0 -513
  45. beratools/gui/map_window.py +0 -162
  46. beratools/tools/Beratools_r_script.r +0 -1120
  47. beratools/tools/Ht_metrics.py +0 -116
  48. beratools/tools/batch_processing.py +0 -136
  49. beratools/tools/canopy_threshold_relative.py +0 -672
  50. beratools/tools/canopycostraster.py +0 -222
  51. beratools/tools/fl_regen_csf.py +0 -428
  52. beratools/tools/forest_line_attributes.py +0 -408
  53. beratools/tools/line_grouping.py +0 -45
  54. beratools/tools/ln_relative_metrics.py +0 -615
  55. beratools/tools/r_cal_lpi_elai.r +0 -25
  56. beratools/tools/r_generate_pd_focalraster.r +0 -101
  57. beratools/tools/r_interface.py +0 -80
  58. beratools/tools/r_point_density.r +0 -9
  59. beratools/tools/rpy_chm2trees.py +0 -86
  60. beratools/tools/rpy_dsm_chm_by.py +0 -81
  61. beratools/tools/rpy_dtm_by.py +0 -63
  62. beratools/tools/rpy_find_cellsize.py +0 -43
  63. beratools/tools/rpy_gnd_csf.py +0 -74
  64. beratools/tools/rpy_hummock_hollow.py +0 -85
  65. beratools/tools/rpy_hummock_hollow_raster.py +0 -71
  66. beratools/tools/rpy_las_info.py +0 -51
  67. beratools/tools/rpy_laz2las.py +0 -40
  68. beratools/tools/rpy_lpi_elai_lascat.py +0 -466
  69. beratools/tools/rpy_normalized_lidar_by.py +0 -56
  70. beratools/tools/rpy_percent_above_dbh.py +0 -80
  71. beratools/tools/rpy_points2trees.py +0 -88
  72. beratools/tools/rpy_vegcoverage.py +0 -94
  73. beratools/tools/tiler.py +0 -48
  74. beratools/tools/zonal_threshold.py +0 -144
  75. beratools-0.2.3.dist-info/METADATA +0 -108
  76. beratools-0.2.3.dist-info/RECORD +0 -74
  77. beratools-0.2.3.dist-info/entry_points.txt +0 -2
  78. beratools-0.2.3.dist-info/licenses/LICENSE +0 -22
@@ -13,49 +13,35 @@ Description:
13
13
  The purpose of this script is the public interface for vertex optimization.
14
14
  """
15
15
 
16
- import time
16
+ import logging
17
17
 
18
18
  import beratools.core.algo_vertex_optimization as bt_vo
19
- import beratools.tools.common as bt_common
20
-
21
-
22
- def vertex_optimization(
23
- in_line,
24
- in_raster,
25
- search_distance,
26
- line_radius,
27
- out_line,
28
- processes,
29
- verbose,
30
- in_layer=None,
31
- out_layer=None,
32
- ):
33
- if not bt_common.compare_crs(
34
- bt_common.vector_crs(in_line), bt_common.raster_crs(in_raster)
35
- ):
19
+ import beratools.utility.spatial_common as sp_common
20
+ from beratools.core.logger import Logger
21
+ from beratools.utility.tool_args import CallMode
22
+
23
+ log = Logger("vertex_optimization", file_level=logging.INFO)
24
+ logger = log.get_logger()
25
+ print = log.print
26
+
27
+
28
+ def vertex_optimization(in_line, in_raster, search_distance, line_radius, out_line,
29
+ processes=0, call_mode=CallMode.CLI, log_level="INFO"):
30
+ in_file, in_layer = sp_common.decode_file_layer(in_line)
31
+ if not sp_common.compare_crs(sp_common.vector_crs(in_file), sp_common.raster_crs(in_raster)):
36
32
  return
37
33
 
38
- vg = bt_vo.VertexGrouping(
39
- in_line,
40
- in_raster,
41
- search_distance,
42
- line_radius,
43
- out_line,
44
- processes,
45
- verbose,
46
- in_layer,
47
- out_layer,
48
- )
34
+ vg = bt_vo.VertexGrouping(in_file, in_raster, search_distance, line_radius, processes, call_mode, layer=in_layer)
49
35
  vg.create_all_vertex_groups()
50
36
  vg.compute()
51
37
  vg.update_all_lines()
52
38
  vg.save_all_layers(out_line)
53
39
 
54
-
55
40
  if __name__ == "__main__":
56
- in_args, in_verbose = bt_common.check_arguments()
41
+ import time
42
+
43
+ from beratools.utility.tool_args import compose_tool_kwargs
57
44
  start_time = time.time()
58
- vertex_optimization(
59
- **in_args.input, processes=int(in_args.processes), verbose=in_verbose
60
- )
45
+ kwargs = compose_tool_kwargs("vertex_optimization")
46
+ vertex_optimization(**kwargs)
61
47
  print("Elapsed time: {}".format(time.time() - start_time))
@@ -0,0 +1,53 @@
1
+ """Environment validation helpers for BERA Tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ import warnings
8
+ from pathlib import Path
9
+
10
+
11
+ def get_gdal_proj_env_warning() -> str | None:
12
+ """Return a Windows-only GDAL/PROJ environment warning message, or None."""
13
+ if not sys.platform.startswith("win"):
14
+ return None
15
+
16
+ gdal_data_env = os.environ.get("GDAL_DATA")
17
+ proj_lib_env = os.environ.get("PROJ_LIB")
18
+ if not (gdal_data_env or proj_lib_env):
19
+ return None
20
+
21
+ prefix_path = Path(sys.prefix).resolve()
22
+ mismatches: list[tuple[str, str]] = []
23
+
24
+ if gdal_data_env:
25
+ gdal_path = Path(gdal_data_env).resolve()
26
+ if not gdal_path.is_relative_to(prefix_path):
27
+ mismatches.append(("GDAL_DATA", gdal_data_env))
28
+
29
+ if proj_lib_env:
30
+ proj_path = Path(proj_lib_env).resolve()
31
+ if not proj_path.is_relative_to(prefix_path):
32
+ mismatches.append(("PROJ_LIB", proj_lib_env))
33
+
34
+ if not mismatches:
35
+ return None
36
+
37
+ mismatch_details = "\n".join(
38
+ f"{env_name:<9}: {env_value}" for env_name, env_value in mismatches
39
+ )
40
+
41
+ return (
42
+ "[GDAL/PROJ Warning] Potential env mismatch\n"
43
+ f"Python env: {sys.prefix}\n"
44
+ f"{mismatch_details}\n"
45
+ "If you encounter GDAL/PROJ errors, ensure these env paths belong to this Python environment."
46
+ )
47
+
48
+
49
+ def warn_gdal_proj_env() -> None:
50
+ """Emit a warning for CLI/headless usage if GDAL/PROJ env vars look mismatched."""
51
+ warning_message = get_gdal_proj_env_warning()
52
+ if warning_message:
53
+ warnings.warn(warning_message)
@@ -0,0 +1,210 @@
1
+ """
2
+ Copyright (C) 2025 Applied Geospatial Research Group.
3
+
4
+ This script is licensed under the GNU General Public License v3.0.
5
+ See <https://gnu.org/licenses/gpl-3.0> for full license details.
6
+
7
+ Author: Richard Zeng, Maverick Fong
8
+
9
+ Description:
10
+ This script is part of the BERA Tools.
11
+ Webpage: https://github.com/appliedgrg/beratools
12
+
13
+ This file is intended to be hosting common spatial classes/functions for BERA Tools
14
+ """
15
+
16
+ import warnings
17
+
18
+ import geopandas as gpd
19
+ import numpy as np
20
+ import pyproj
21
+ import rasterio
22
+ from osgeo import gdal, osr, version_info
23
+ from pyogrio import set_gdal_config_options
24
+ from rasterio import mask
25
+
26
+ import beratools.core.constants as bt_const
27
+
28
+ # suppress pandas UserWarning: Geometry column contains no geometry when splitting lines
29
+ warnings.simplefilter(action="ignore", category=UserWarning)
30
+
31
+ # restore .shx for shapefile for using GDAL or pyogrio
32
+ gdal.SetConfigOption("SHAPE_RESTORE_SHX", "YES")
33
+ set_gdal_config_options({"SHAPE_RESTORE_SHX": "YES"}) # for pyogrio
34
+
35
+
36
+ # suppress all kinds of warnings
37
+ if not bt_const.BT_DEBUGGING:
38
+ gdal.SetConfigOption("CPL_LOG", "NUL") # GDAL warning
39
+ warnings.filterwarnings("ignore") # suppress warnings
40
+ warnings.simplefilter(action="ignore", category=UserWarning) # suppress Pandas UserWarning
41
+
42
+
43
+ def clip_raster(
44
+ in_raster_file,
45
+ clip_geom,
46
+ buffer=0.0,
47
+ out_raster_file=None,
48
+ default_nodata=bt_const.BT_NODATA,
49
+ ):
50
+ out_meta = None
51
+ with rasterio.open(in_raster_file) as raster_file:
52
+ out_meta = raster_file.meta
53
+ ras_nodata = out_meta["nodata"]
54
+ if ras_nodata is None:
55
+ ras_nodata = default_nodata
56
+
57
+ clip_geo_buffer = [clip_geom.buffer(buffer)]
58
+ out_image = None
59
+ out_image, out_transform = mask.mask(
60
+ raster_file, clip_geo_buffer, crop=True, nodata=ras_nodata, filled=True
61
+ )
62
+ if np.isnan(ras_nodata):
63
+ out_image[np.isnan(out_image)] = default_nodata
64
+
65
+ elif np.isinf(ras_nodata):
66
+ out_image[np.isinf(out_image)] = default_nodata
67
+ else:
68
+ out_image[out_image == ras_nodata] = default_nodata
69
+
70
+ out_image = np.ma.masked_where(out_image == default_nodata, out_image)
71
+ out_image.fill_value = default_nodata
72
+ ras_nodata = default_nodata
73
+
74
+ height, width = out_image.shape[1:]
75
+
76
+ out_meta.update(
77
+ {
78
+ "driver": "GTiff",
79
+ "height": height,
80
+ "width": width,
81
+ "transform": out_transform,
82
+ "nodata": ras_nodata,
83
+ }
84
+ )
85
+
86
+ if out_raster_file:
87
+ with rasterio.open(out_raster_file, "w", **out_meta) as dest:
88
+ dest.write(out_image)
89
+ print("[Clip raster]: data saved to {}.".format(out_raster_file))
90
+
91
+ return out_image, out_meta
92
+
93
+
94
+ def decode_file_layer(encoded):
95
+ """
96
+ Decode encoded file|layer string into file path and layer name.
97
+
98
+ Args:
99
+ encoded (str): Encoded string like "file.shp|" or "C:/path/file.gpkg|layer"
100
+
101
+ Returns:
102
+ tuple: (file_path, layer_name) where layer_name is None if empty
103
+ """
104
+ if "|" in encoded:
105
+ file_path, layer = encoded.rsplit("|", 1)
106
+ layer_name = layer if layer else None
107
+ elif "::" in encoded:
108
+ file_path, layer = encoded.rsplit("::", 1)
109
+ layer_name = layer if layer else None
110
+ else:
111
+ file_path = encoded
112
+ layer_name = None
113
+ return file_path, layer_name
114
+
115
+
116
+ def vector_crs(in_vector):
117
+ osr_crs = osr.SpatialReference()
118
+ from pyproj.enums import WktVersion
119
+
120
+ vec_crs = None
121
+ # open input vector data as GeoDataFrame
122
+ gpd_vector = gpd.GeoDataFrame.from_file(in_vector)
123
+ try:
124
+ if gpd_vector.crs is not None:
125
+ vec_crs = gpd_vector.crs
126
+ if version_info.major < 3:
127
+ osr_crs.ImportFromWkt(vec_crs.to_wkt(WktVersion.WKT1_GDAL))
128
+ else:
129
+ osr_crs.ImportFromEPSG(vec_crs.to_epsg())
130
+ return osr_crs
131
+ else:
132
+ print("No CRS found in the input feature, please check!")
133
+ exit()
134
+ except Exception as e:
135
+ print(e)
136
+ exit()
137
+
138
+
139
+ def raster_crs(in_raster):
140
+ osr_crs = osr.SpatialReference()
141
+ with rasterio.open(in_raster) as raster_file:
142
+ from pyproj.enums import WktVersion
143
+
144
+ try:
145
+ if raster_file.crs is not None:
146
+ vec_crs = raster_file.crs
147
+ if version_info.major < 3:
148
+ osr_crs.ImportFromWkt(vec_crs.to_wkt(WktVersion.WKT1_GDAL))
149
+ else:
150
+ osr_crs.ImportFromEPSG(vec_crs.to_epsg())
151
+ return osr_crs
152
+ else:
153
+ print("No Coordinate Reference System (CRS) find in the input feature, please check!")
154
+ exit()
155
+ except Exception as e:
156
+ print(e)
157
+ exit()
158
+
159
+
160
+ def get_crs_proj_name(crs_norm, label="crs"):
161
+ import warnings
162
+
163
+ if crs_norm.is_compound:
164
+ op = crs_norm.sub_crs_list[0].coordinate_operation
165
+ if op is None:
166
+ warnings.warn(f"{label}.sub_crs_list[0].coordinate_operation is None; using 'unknown'")
167
+ return "unknown"
168
+ return op.name
169
+ elif crs_norm.name == "unnamed":
170
+ return None
171
+ else:
172
+ op = crs_norm.coordinate_operation
173
+ if op is None:
174
+ warnings.warn(f"{label}.coordinate_operation is None; using 'unknown'")
175
+ return "unknown"
176
+ return op.name
177
+
178
+
179
+ def compare_crs(crs_org, crs_dst):
180
+ if crs_org and crs_dst:
181
+ if crs_org.IsSameGeogCS(crs_dst):
182
+ print("Check: Input file Spatial Reference are the same, continue.")
183
+ return True
184
+ else:
185
+ crs_org_norm = pyproj.CRS(crs_org.ExportToWkt())
186
+ crs_dst_norm = pyproj.CRS(crs_dst.ExportToWkt())
187
+
188
+ crs_org_proj = get_crs_proj_name(crs_org_norm, "crs_org_norm")
189
+ if crs_org_proj is None:
190
+ return False
191
+
192
+ crs_dst_proj = get_crs_proj_name(crs_dst_norm, "crs_dst_norm")
193
+ if crs_dst_proj is None:
194
+ return False
195
+
196
+ if crs_org_proj == crs_dst_proj:
197
+ if crs_org_norm.name == crs_dst_norm.name:
198
+ print("Input files Spatial Reference are the same, continue.")
199
+ return True
200
+ else:
201
+ print(
202
+ """Checked: Data are on the same projected Zone but using
203
+ different Spatial Reference. \n Consider to re-project
204
+ all data onto same spatial reference system.\n Process Stop."""
205
+ )
206
+ exit()
207
+ else:
208
+ return False
209
+
210
+ return False
@@ -0,0 +1,138 @@
1
+ """Utility functions for tool argument parsing."""
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import platform
7
+ import sys
8
+ from enum import Enum
9
+
10
+
11
+ # TODO: Consider using StrEnum from Python 3.11+
12
+ class StrEnum(str, Enum):
13
+ pass
14
+
15
+ class CallMode(StrEnum):
16
+ CLI = "CLI"
17
+ GUI = "GUI"
18
+
19
+ def determine_cpu_core_limit():
20
+ running_windows = platform.system() == "Windows"
21
+ core_count = os.cpu_count() or 1
22
+ max_cores = max(core_count - 1, 1)
23
+ if running_windows:
24
+ max_cores = min(max_cores, 60)
25
+ return max_cores
26
+
27
+ def compose_tool_kwargs(tool_api):
28
+ """
29
+ Auto-detecting argument composer for both CLI and GUI modes.
30
+
31
+ - Detects mode from --input presence
32
+ - GUI: unpacks --input JSON, adds common args from CLI flags
33
+ - CLI: parses all args from beratools.json definitions with validation
34
+ - Returns: complete kwargs dict with all parameters
35
+
36
+ Note: In CLI mode, argparse automatically prints help and exits
37
+ if required arguments are missing.
38
+ """
39
+ from beratools.gui.bt_data import BTData
40
+
41
+ bt_data = BTData()
42
+ tool_name = bt_data.get_bera_tool_name(tool_api)
43
+ tool_api_name = tool_api
44
+
45
+ mode_parser = argparse.ArgumentParser(add_help=False)
46
+ mode_parser.add_argument("-i", "--input", type=json.loads, default=None)
47
+ mode_parser.add_argument("-c", "--call_mode", default=CallMode.CLI.value)
48
+ mode_parser.add_argument("-p", "--processes", type=int, default=0)
49
+ mode_parser.add_argument("-l", "--log_level", default="INFO")
50
+ mode_args, remaining = mode_parser.parse_known_args()
51
+
52
+ if mode_args.input is None:
53
+ from beratools.utility import env_checks
54
+
55
+ env_checks.warn_gdal_proj_env()
56
+
57
+ if mode_args.input is not None:
58
+ # GUI MODE: unpack tool params from JSON with TYPE VALIDATION
59
+ try:
60
+ kwargs = bt_data.validate_tool_params(tool_name, mode_args.input)
61
+ except ValueError as e:
62
+ print(f"ERROR: {str(e)}", file=sys.stderr)
63
+ sys.exit(1)
64
+
65
+ else:
66
+ # CLI MODE: parse tool params from CLI args with validation
67
+ # Load parameter definitions from beratools.json
68
+ tool_params = bt_data.get_bera_tool_params(tool_name)
69
+
70
+ # Helper: Separate parameters by type
71
+ required_params = [
72
+ p for p in tool_params.get("parameters", []) if p.get("variable") and not p.get("optional", False)
73
+ ]
74
+ optional_params = [
75
+ p for p in tool_params.get("parameters", []) if p.get("variable") and p.get("optional", False)
76
+ ]
77
+
78
+ # Build usage string with only required parameters
79
+ required_flags = " ".join([f"--{p.get('variable')}" for p in required_params])
80
+ custom_usage = f"python {tool_api_name}.py {required_flags}" if required_flags else f"python {tool_api_name}.py"
81
+
82
+ full_parser = argparse.ArgumentParser(
83
+ prog=tool_name,
84
+ usage=custom_usage,
85
+ description=f"Tool: {tool_name}",
86
+ formatter_class=argparse.RawDescriptionHelpFormatter,
87
+ )
88
+
89
+ # Create argument groups
90
+ required_group = full_parser.add_argument_group("Required Parameters")
91
+ optional_group = full_parser.add_argument_group("Optional Parameters")
92
+ framework_group = full_parser.add_argument_group("Framework Options")
93
+
94
+ # Helper function to add parameters to argument group
95
+ def add_param_args(group, params, required=False):
96
+ """Add parameter arguments to argument group."""
97
+ for param in params:
98
+ arg_config = {"required": required}
99
+ if param.get("default_value") is not None and param.get("default_value") != "":
100
+ arg_config["default"] = param.get("default_value")
101
+ if param.get("description"):
102
+ # Escape % in help text to prevent argparse formatting errors
103
+ arg_config["help"] = param.get("description").replace("%", "%%")
104
+ group.add_argument(f"--{param.get('variable')}", **arg_config)
105
+
106
+ # Add required and optional parameters using helper
107
+ add_param_args(required_group, required_params, required=True)
108
+ add_param_args(optional_group, optional_params, required=False)
109
+
110
+ # Add framework parameters to their group (with descriptions)
111
+ framework_group.add_argument(
112
+ "-p",
113
+ "--processes",
114
+ type=int,
115
+ default=mode_args.processes,
116
+ help="Number of CPU processes (default: 1)",
117
+ )
118
+ framework_group.add_argument(
119
+ "-c", "--call_mode", default=mode_args.call_mode, help="'CLI' or 'GUI' mode (default: CLI)"
120
+ )
121
+ framework_group.add_argument(
122
+ "-l",
123
+ "--log_level",
124
+ default=mode_args.log_level,
125
+ help="DEBUG, INFO, WARNING, ERROR (default: INFO)",
126
+ )
127
+
128
+ # Parse all arguments
129
+ # This automatically prints help and exits if required args are missing
130
+ args = full_parser.parse_args()
131
+ kwargs = vars(args)
132
+
133
+ # Always ensure framework params are set from first pass
134
+ kwargs.update(
135
+ {"processes": mode_args.processes, "call_mode": mode_args.call_mode, "log_level": mode_args.log_level}
136
+ )
137
+
138
+ return kwargs
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: BERATools
3
+ Version: 0.2.4
4
+ Summary: An advanced forest line feature analysis platform
5
+ Project-URL: Homepage, https://github.com/appliedgrg/beratools
6
+ Author-email: AppliedGRG <appliedgrg@gmail.com>, Richard Zeng <richardqzeng@gmail.com>
7
+ License: GPL-3.0-or-later
8
+ License-File: LICENSE
9
+ Keywords: BERA,Line
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: bera-centerlines
21
+ Requires-Dist: gdal; platform_system != 'Windows'
22
+ Requires-Dist: geopandas
23
+ Requires-Dist: networkit
24
+ Requires-Dist: pyogrio>=0.9.0
25
+ Requires-Dist: pyqt5
26
+ Requires-Dist: rasterio
27
+ Requires-Dist: scikit-image>=0.24.0
28
+ Requires-Dist: tabulate
29
+ Requires-Dist: tqdm
30
+ Requires-Dist: xarray-spatial
31
+ Provides-Extra: dev
32
+ Requires-Dist: build; extra == 'dev'
33
+ Requires-Dist: isort; extra == 'dev'
34
+ Requires-Dist: mypy; extra == 'dev'
35
+ Requires-Dist: pre-commit; extra == 'dev'
36
+ Requires-Dist: pytest; extra == 'dev'
37
+ Requires-Dist: pytest-cov; extra == 'dev'
38
+ Requires-Dist: ruff; extra == 'dev'
39
+ Requires-Dist: tox; extra == 'dev'
40
+ Requires-Dist: twine; extra == 'dev'
41
+ Provides-Extra: doc
42
+ Requires-Dist: click==8.2.1; extra == 'doc'
43
+ Requires-Dist: mkdocs; extra == 'doc'
44
+ Requires-Dist: mkdocs-git-revision-date-localized-plugin; extra == 'doc'
45
+ Requires-Dist: mkdocs-material; extra == 'doc'
46
+ Requires-Dist: mkdocstrings; extra == 'doc'
47
+ Requires-Dist: mkdocstrings-python; extra == 'doc'
48
+ Requires-Dist: pymdown-extensions; extra == 'doc'
49
+ Description-Content-Type: text/markdown
50
+
51
+ # BERA Tools
52
+
53
+ BERA Tools is successor of [Forest Line Mapper](https://github.com/appliedgrg/flm). It is a toolset for enhanced delineation and attribution of linear disturbances in forests.
54
+
55
+ <div align="center">
56
+
57
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/appliedgrg/beratools/python-tests.yml?branch=main)](https://github.com/appliedgrg/beratools/actions/workflows/python-tests.yml)
58
+ [![Codecov](https://img.shields.io/codecov/c/github/appliedgrg/beratools/main)](https://codecov.io/gh/appliedgrg/beratools)
59
+ [![GitHub Pages](https://img.shields.io/github/deployments/appliedgrg/beratools/github-pages?label=docs)](https://appliedgrg.github.io/beratools/)
60
+ [![Conda Version](https://img.shields.io/conda/v/AppliedGRG/beratools)](https://anaconda.org/AppliedGRG/beratools)
61
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/release/python-3100/)
62
+ [![License: GPL-3.0](https://img.shields.io/badge/License-GPL%203.0-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
63
+
64
+ </div>
65
+
66
+ ## [Quick Start](https://appliedgrg.github.io/beratools)
67
+
68
+ BERA Tools is built upon open-source Python libraries. Anaconda is used to manage runtime environments.
69
+
70
+ Ways to install BERA Tools:
71
+
72
+ - Windows installer
73
+ - Install with Anaconda.
74
+
75
+ ### Windows Installer
76
+
77
+ Windows installer is provided with releases. Check the [latest release](https://github.com/appliedgrg/beratools/releases/latest) for the up-to-date installer.
78
+
79
+ ### Install with Anaconda
80
+
81
+ Install with Anaconda works on Windows, macOS, and Linux.
82
+
83
+ - Install Miniconda. Download Miniconda from [Miniconda](https://docs.anaconda.com/miniconda/) and install on your machine.
84
+ - Download the file [environment.yml](https://raw.githubusercontent.com/appliedgrg/beratools/main/environment.yml
85
+ ) and save to local storage. Launch **Anaconda Prompt** or **Miniconda Prompt**.
86
+ - **Change directory** to where environment.yml is saved in the command prompt.
87
+ - Run the following command to create a new environment named **bera**. **BERA Tools** will be installed in the new environment at the same time.
88
+
89
+ ```bash
90
+ $ conda env create -n bera -f environment.yml
91
+ ```
92
+
93
+ Wait until the installation is done.
94
+ - Activate the **bera** environment and launch BERA Tools:
95
+
96
+ ```bash
97
+ $ conda activate bera
98
+ $ beratools
99
+ ```
100
+
101
+ - [Download latest example data](https://github.com/appliedgrg/beratools/releases/latest/download/test_data.zip) to try with BERA Tools.
102
+ - To update BERA Tools when new release is issued, run the following commands:
103
+
104
+ ```bash
105
+ $ conda activate bera
106
+ $ conda update beratools
107
+ ```
108
+
109
+ - To completely remove BERA Tools and its environment, run the following command:
110
+
111
+ ```bash
112
+ $ conda remove -n bera
113
+ ```
114
+
115
+ ## BERA Tools Guide
116
+
117
+ Check the online [BERA Tools Guide](https://appliedgrg.github.io/beratools/) for user, developer guides.
118
+
119
+ ## Credits
120
+
121
+ <table>
122
+ <tr>
123
+ <td><img src="docs/files/icons/bera_logo.png" alt="Logos" width="80"></td>
124
+ <td>
125
+ <p>
126
+ This tool is part of the <strong><a href="http://www.beraproject.org/">Boreal Ecosystem Recovery & Assessment (BERA)</a></strong>.
127
+ It is actively developed by the <a href="https://www.appliedgrg.ca/"><strong>Applied Geospatial Research Group</strong></a>.
128
+ </p>
129
+ <p>
130
+ © 2026 Applied Geospatial Research Group. All rights reserved.
131
+ </p>
132
+ </td>
133
+ </tr>
134
+ </table>
@@ -0,0 +1,50 @@
1
+ beratools/__init__.py,sha256=PW8O7ptzcibf-RBahr8etZ5bSyEmBNsj5HhR6zIQ64A,266
2
+ beratools/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ beratools/core/algo_canopy_footprint_exp.py,sha256=IlfwyMo7ySwx_UG9H91aEnHaLYkBoyeHg_4bj8d_IHw,22603
4
+ beratools/core/algo_centerline.py,sha256=HvXViuMwoOt9Xt3i8KX4bvpsZNz4zKv7LvdGIVvbIdA,15627
5
+ beratools/core/algo_common.py,sha256=vMGcjyF_pP79bqjNvjuBN_i9HoebUq2JWyHNyyKOObE,14714
6
+ beratools/core/algo_cost.py,sha256=JFNdy9U4B2-AgUR_o-zKABEINqU8GJLFJIAxFOm05Zw,5934
7
+ beratools/core/algo_dijkstra.py,sha256=_pABOkwTm3Tc9J33G_1or_iW96D4UPuL93iPlg_vK6k,15167
8
+ beratools/core/algo_line_grouping.py,sha256=_VuxMntZE3rAwJfZZwOkvFSUwyhxpi1nFapJ4ZOtT8Q,32998
9
+ beratools/core/algo_merge_lines.py,sha256=EKgGRyuomc7JpliK1nwCrOA8uwepGatmPaicxl0TTH0,7950
10
+ beratools/core/algo_split_with_lines.py,sha256=-k8oMBQ-XM3Xzdxdd9Vhi-YgMaRFHixdW8Ts9lL6dYU,10747
11
+ beratools/core/algo_vertex_optimization.py,sha256=OvvDOt7pi8X0xpaR45JH9e6WQVL-2ycWfBG_R0jMnHc,15380
12
+ beratools/core/canopy_threshold_relative.py,sha256=N5HIJiA6SgLA8qrML6qAbkJlxs3sEEKGnlvkfZ3mdbg,30015
13
+ beratools/core/constants.py,sha256=h4rnXNbKd7HCkyJNIrh01OKBegQb-tQrwavzrbCEUTM,1115
14
+ beratools/core/line_footprint_functions.py,sha256=B5PExYq4xZMMGvvnIwi3vDxhoG25NEwerthI3uOdCYU,31864
15
+ beratools/core/logger.py,sha256=55nhQV3Jjjf3GD-v6hfd-LKc8YyH4Wpe53ImQhLGwfM,2803
16
+ beratools/core/tool_base.py,sha256=dDJPUPf2qmjFRrbkqFR49r0osEEKmTLNrPQ3PbBtj5E,4343
17
+ beratools/gui/bt_data.py,sha256=VebUxYqRVJf68sMcRJLRWovj71ZwE8608RG0gVMYCbA,31024
18
+ beratools/gui/bt_gui_main.py,sha256=X-UGSl90a21OCINzqDysxVm5gWdlgkXejPal8ocoGIE,26877
19
+ beratools/gui/main.py,sha256=6rireFhO1NNuuKlyU89HT7ESq3_zrex5xlloGYQO6WM,482
20
+ beratools/gui/tool_widgets.py,sha256=mCZrudV33uamPxb2JMp1ZU2e8gNBXh5dq85ur8MBm4s,35876
21
+ beratools/gui/assets/BERALogo.ico,sha256=5kNFkKe0FdL-oBXiO2ExVTMHhelO6sB69I3VHPCRQno,107799
22
+ beratools/gui/assets/BERALogo.png,sha256=hnGLSL7Jv2ljDn6Z8ad5ekzH9k1PEuq7xe7Hh8bXZkE,6660
23
+ beratools/gui/assets/BERA_Splash.gif,sha256=4QB5Xua2F59mBeOoeQ1Eoz0t-fZOQbYMTAl62RE2yEU,4377179
24
+ beratools/gui/assets/BERA_WizardImage.png,sha256=IKgbY0h_uZ3Sk2B7-X5oMIs5GkNwLXgbf87JjuealvY,11370
25
+ beratools/gui/assets/beratools.json,sha256=KJzkd5DyXaou_MfOg_glEc7xOfDB6cGItRzOzK9V9VY,18964
26
+ beratools/gui/assets/closed.gif,sha256=T9BBGl286-FdSRYbWVt68HwPRF0geWoowtBzeYsk_bI,592
27
+ beratools/gui/assets/closed.png,sha256=FRSAh3wzASowFUEt88zHhk1BQmwRbjoCs50Ff8KpJNQ,311
28
+ beratools/gui/assets/gui.json,sha256=kRD3Ozim9IotcY8P3fapgs4qqj5vebQHLxmss_SOJdY,329
29
+ beratools/gui/assets/open.gif,sha256=o8LL1OaDNkQ0Gcc9dO-1f1UZFAQ3ALc4svtqiUuWJPY,1062
30
+ beratools/gui/assets/open.png,sha256=v0OOwfTNGMmtdFbffCbxdYLaNZeRC9Cy5whAwi71oVw,524
31
+ beratools/gui/assets/tool.gif,sha256=2KGcUI_b-qFdrJJhgbx9pMZ8pExX7zH8nYvweRiQs6U,555
32
+ beratools/gui/assets/tool.png,sha256=35Txwum2l_aKeazF_I97ZaZAKHsrKQCGG-LZxEIFyZw,714
33
+ beratools/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ beratools/tools/canopy_footprint_absolute.py,sha256=gcQjCDc21vcatcF5IQVUmwVQSUn9drHn09yvnnfYsqI,7865
35
+ beratools/tools/canopy_footprint_exp.py,sha256=91Nh0MCS6J0sTx65tHeR122uXzwEvT1rtaisq8yB1qQ,4185
36
+ beratools/tools/centerline.py,sha256=Zn0iPEL0fki6VMENCDqaPIrM433a-b_9PGjyxEfx3S4,3956
37
+ beratools/tools/check_seed_line.py,sha256=653X2R75zP667VWPylwu5pLjLfiSCI-z6va_NCXa55E,4389
38
+ beratools/tools/common.py,sha256=b_4b3RuBSiQVmtwWSDpXEZvROnBY0vIQzpDT5TW_nfw,11183
39
+ beratools/tools/ground_footprint.py,sha256=VhVdB6ZPBG4zxZCHUWSZnpIn1aq_uB5RebvKVi-ZaRA,16177
40
+ beratools/tools/line_footprint_relative.py,sha256=DJQiS4aQRN-KjOENl04qPDxBQfza4iicS39Xjd7-Zdw,3716
41
+ beratools/tools/tool_template.py,sha256=U4YYGWCXLGQImE96tjR6tx3eUSTTAs2TuGmMbF6zlME,2661
42
+ beratools/tools/vertex_optimization.py,sha256=b8PU5VHFx8Z42lgTl4P7AB5DaoJ8USztHGoU9s-iBW0,1591
43
+ beratools/utility/env_checks.py,sha256=-NBUJc1oh4XzcjNvY-IgvTLs7_zcHS3_KdRjXdYz790,1606
44
+ beratools/utility/spatial_common.py,sha256=GA2FwbxKeqJPfX7jtrnE6LuH9PgBlMujgG-ejoXlOrQ,6732
45
+ beratools/utility/tool_args.py,sha256=If8qGoK-snnTZuWhtPA_xSOVY5DMZPlZ-CGoSuy2-tk,5251
46
+ beratools-0.2.4.dist-info/METADATA,sha256=KrA5HJxOy6z4lfmQermjXWhyoXaeZQyiMR1CckQNtfE,5317
47
+ beratools-0.2.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
48
+ beratools-0.2.4.dist-info/entry_points.txt,sha256=wEzv7VVMxnmCmzV3D10B-9DXdqnZgPsBgVWtiIhcdaU,98
49
+ beratools-0.2.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
50
+ beratools-0.2.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ BERATools = beratools.gui.main:gui_main
3
+ beratools = beratools.gui.main:gui_main