samgis_core 3.0.5__tar.gz → 3.0.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: samgis_core
3
- Version: 3.0.5
3
+ Version: 3.0.8
4
4
  Summary: SamGIS CORE
5
5
  License: MIT
6
6
  Author: alessandro trinca tornidor
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "samgis_core"
3
- version = "3.0.5"
3
+ version = "3.0.8"
4
4
  description = "SamGIS CORE"
5
5
  authors = ["alessandro trinca tornidor <alessandro@trinca.tornidor.com>"]
6
6
  license = "MIT license"
@@ -8,7 +8,7 @@ readme = "README.md"
8
8
 
9
9
  [metadata]
10
10
  name = "samgis_core"
11
- version = "3.0.5"
11
+ version = "3.0.8"
12
12
 
13
13
  [tool.poetry.urls]
14
14
  Source = "https://gitlab.com/aletrn/samgis_core"
@@ -37,6 +37,7 @@ optional = true
37
37
  mpld3 = "^0.5.10"
38
38
  pytest = "^8.2.2"
39
39
  pytest-cov = "^5.0.0"
40
+ rasterio = "^1.3.10"
40
41
 
41
42
  [build-system]
42
43
  requires = ["poetry-core"]
@@ -70,7 +70,8 @@ def get_inference_embedding(
70
70
 
71
71
 
72
72
  def get_raster_inference_using_existing_embedding(
73
- embedding: dict, prompt: ListDict, models_instance: SegmentAnythingONNX2) -> TupleNdarrayInt:
73
+ embedding: dict, prompt: ListDict, models_instance: SegmentAnythingONNX2, folder_write_tmp_on_disk: str = None,
74
+ key: str = None) -> TupleNdarrayInt:
74
75
  """
75
76
  Get inference output for a given image using a SegmentAnythingONNX model, using an existing embedding instead of a
76
77
  new ndarray or PIL image
@@ -79,6 +80,8 @@ def get_raster_inference_using_existing_embedding(
79
80
  embedding: dict
80
81
  prompt: list of prompt dict
81
82
  models_instance: SegmentAnythingONNX instance model
83
+ folder_write_tmp_on_disk: output folder where to write debug images
84
+ key: embedding key
82
85
 
83
86
  Returns:
84
87
  raster prediction mask, prediction number
@@ -90,16 +93,27 @@ def get_raster_inference_using_existing_embedding(
90
93
  app_logger.info(f"Created {len_inference_out} prediction_masks,"
91
94
  f"shape:{inference_out.shape}, dtype:{inference_out.dtype}.")
92
95
  mask = zeros((inference_out.shape[2], inference_out.shape[3]), dtype=uint8)
96
+ write_tmp_img = bool(folder_write_tmp_on_disk)
93
97
  for n, m in enumerate(inference_out[0, :, :, :]):
94
98
  app_logger.debug(f"{n}th of prediction_masks shape {inference_out.shape}"
95
99
  f" => mask shape:{mask.shape}, {mask.dtype}.")
96
100
  mask[m > 0.0] = 255
101
+ if write_tmp_img:
102
+ from pathlib import Path
103
+ from datetime import datetime
104
+ from samgis_core.utilities.utilities import convert_ndarray_to_pil, normalize_array
105
+ m_normalized = normalize_array(m, type_normalization="float")
106
+ m_out = convert_ndarray_to_pil(m_normalized)
107
+ now = datetime.now().isoformat()
108
+ if len(m.shape) == 2:
109
+ m_out = m_out.convert("L")
110
+ m_out.save(Path(folder_write_tmp_on_disk) / f"mask_{key}_{now}_n{n}.png")
97
111
  return mask, len_inference_out
98
112
 
99
113
 
100
114
  def get_raster_inference_with_embedding_from_dict(
101
115
  img: PIL_Image | ndarray, prompt: ListDict, models_instance: SegmentAnythingONNX2, model_name: str,
102
- embedding_key: str, embedding_dict: dict) -> TupleNdarrayInt:
116
+ embedding_key: str, embedding_dict: dict, folder_write_tmp_on_disk: str = None) -> TupleNdarrayInt:
103
117
  """
104
118
  Get inference output using a SegmentAnythingONNX model, but get the image embedding from the given embedding dict
105
119
  instead of creating a new embedding. This function needs the img argument to update the embedding dict if necessary
@@ -111,6 +125,7 @@ def get_raster_inference_with_embedding_from_dict(
111
125
  model_name: model name string
112
126
  embedding_key: embedding id
113
127
  embedding_dict: embedding images dict
128
+ folder_write_tmp_on_disk: output folder where to write debug images
114
129
 
115
130
  Returns:
116
131
  raster prediction mask, prediction number
@@ -122,4 +137,5 @@ def get_raster_inference_with_embedding_from_dict(
122
137
  embedding = embedding_dict[embedding_key]
123
138
  n_keys = len(embedding_dict)
124
139
  app_logger.info(f"embedding created ({n_keys} keys in embedding dict), running predict_masks with prompt {prompt}.")
125
- return get_raster_inference_using_existing_embedding(embedding, prompt, models_instance)
140
+ return get_raster_inference_using_existing_embedding(
141
+ embedding, prompt, models_instance, folder_write_tmp_on_disk=folder_write_tmp_on_disk, key=embedding_key)
@@ -0,0 +1,150 @@
1
+ import os
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+ from dotenv import load_dotenv
6
+
7
+ from samgis_core import app_logger
8
+ from samgis_core.utilities.type_hints import ListStr
9
+
10
+
11
+ load_dotenv()
12
+ root_folder = Path(globals().get("__file__", "./_")).absolute().parent.parent.parent
13
+ env_project_root_folder = os.getenv("PROJECT_ROOT_FOLDER", str(root_folder))
14
+ env_input_css_path = os.getenv("INPUT_CSS_PATH")
15
+
16
+
17
+ def assert_envs(envs_list: ListStr) -> None:
18
+ """
19
+ Assert env variables are not empty.
20
+
21
+ Args:
22
+ envs_list: list of env variables
23
+
24
+ Returns:
25
+
26
+ """
27
+ for current_env in envs_list:
28
+ try:
29
+ assert current_env is not None and current_env != ""
30
+ except AssertionError as aex:
31
+ app_logger.error(f"error on assertion for current_env: {current_env}.")
32
+ raise aex
33
+
34
+
35
+ def read_std_out_err(std_out_err: str, output_type: str, command: ListStr) -> None:
36
+ """
37
+ Capture the standard output or standard error content for a given system command pipe.
38
+
39
+ Args:
40
+ std_out_err: str
41
+ output_type: output type (stdout or stderr)
42
+ command: command executed
43
+
44
+ Returns:
45
+
46
+ """
47
+ output = std_out_err.split("\n")
48
+ app_logger.info(f"output type:{output_type} for command:{' '.join(command)}.")
49
+ for line in iter(output):
50
+ app_logger.info(f"output_content_home stdout:{line.strip()}.")
51
+ app_logger.info("########")
52
+
53
+
54
+ def run_command(commands_list: ListStr, capture_output: bool = True, text: bool = True, check: bool = True) -> None:
55
+ """
56
+ Run a system command and capture its output.
57
+ See https://docs.python.org/3.11/library/subprocess.html#subprocess.run for more details
58
+
59
+ Args:
60
+ commands_list: list of single string commands
61
+ capture_output: if true, stdout and stderr will be captured
62
+ text: if true, capture stdout and stderr as strings
63
+ check: If check is true, and the process exits with a non-zero exit code, a CalledProcessError exception will
64
+ be raised. Attributes of that exception hold the arguments, the exit code, and stdout and stderr if they
65
+ were captured.
66
+
67
+ Returns:
68
+
69
+ """
70
+ try:
71
+ output_content_home = subprocess.run(
72
+ commands_list,
73
+ capture_output=capture_output,
74
+ text=text,
75
+ check=check
76
+ )
77
+ read_std_out_err(output_content_home.stdout, "stdout", commands_list)
78
+ read_std_out_err(output_content_home.stderr, "stderr", commands_list)
79
+ except Exception as ex:
80
+ app_logger.error(f"ex:{ex}.")
81
+ raise ex
82
+
83
+
84
+ def build_frontend(
85
+ project_root_folder: str | Path,
86
+ input_css_path: str | Path,
87
+ output_dist_folder: Path = root_folder / "static" / "dist",
88
+ index_page_filename: str = "index.html",
89
+ output_css_filename: str = "output.css",
90
+ force_build: bool = False,
91
+ ) -> bool:
92
+ """
93
+ Build a static [Vue js](https://vuejs.org/), [tailwindcss](https://tailwindcss.com/) frontend.
94
+ If force_build is False, the function also check if index_page_filename and output_css_filename already exists:
95
+ in this case skip the build.
96
+
97
+ Args:
98
+ project_root_folder: Project folder that contains the static frontend
99
+ input_css_path: file path pointing to the input css file
100
+ output_dist_folder: dist folder path where to write the frontend bundle
101
+ index_page_filename: index html filename
102
+ output_css_filename: output css filename
103
+ force_build: useful to skip the frontend build
104
+
105
+ Returns:
106
+ state of the build (True in case of build completed, False in case of build skipped)
107
+
108
+ """
109
+ assert_envs([
110
+ str(project_root_folder),
111
+ str(input_css_path)
112
+ ])
113
+ project_root_folder = Path(project_root_folder)
114
+ index_html_pathfile = Path(output_dist_folder) / index_page_filename
115
+ output_css_pathfile = Path(output_dist_folder) / output_css_filename
116
+ if not force_build and output_css_pathfile.is_file() and index_html_pathfile.is_file():
117
+ app_logger.info("frontend ok, build_frontend not necessary...")
118
+ return False
119
+
120
+ # install deps
121
+ os.chdir(project_root_folder / "static")
122
+ current_folder = os.getcwd()
123
+ app_logger.info(f"current_folder:{current_folder}, install pnpm...")
124
+ run_command(["which", "npm"])
125
+ run_command(["npm", "install", "-g", "npm", "pnpm"])
126
+ app_logger.info(f"install pnpm dependencies...")
127
+ run_command(["pnpm", "install"])
128
+
129
+ # build frontend dist and assert for its correct build
130
+ output_css = str(output_dist_folder / output_css_filename)
131
+ output_index_html = str(output_dist_folder / index_page_filename)
132
+ output_dist_folder = str(output_dist_folder)
133
+ app_logger.info(f"pnpm: build '{output_dist_folder}'...")
134
+ run_command(["pnpm", "build"])
135
+ app_logger.info(f"pnpm: ls -l {output_index_html}:")
136
+ run_command(["ls", "-l", output_index_html])
137
+ cmd = ["pnpm", "tailwindcss", "-i", str(input_css_path), "-o", output_css]
138
+ app_logger.info(f"pnpm: {' '.join(cmd)}...")
139
+ run_command(["pnpm", "tailwindcss", "-i", str(input_css_path), "-o", output_css])
140
+ app_logger.info(f"pnpm: ls -l {output_css}:")
141
+ run_command(["ls", "-l", output_css])
142
+ app_logger.info(f"end!")
143
+ return True
144
+
145
+
146
+ if __name__ == '__main__':
147
+ build_frontend(
148
+ project_root_folder=Path(env_project_root_folder),
149
+ input_css_path=Path(env_input_css_path)
150
+ )
@@ -1,12 +1,15 @@
1
+ import structlog.stdlib
1
2
  from numpy import ndarray
2
3
  from matplotlib import pyplot as plt
3
4
 
4
- from samgis_core.utilities.type_hints import ListStr, TupleInt
5
-
5
+ from samgis_core.utilities.type_hints import ListStr, MatplotlibBackend
6
6
 
7
7
  FigAxes = tuple[plt.Figure, plt.Axes]
8
8
 
9
9
 
10
+ logger = structlog.stdlib.get_logger(__file__)
11
+
12
+
10
13
  def helper_imshow_output_expected(
11
14
  img_list: list[ndarray], titles_list: ListStr, cmap: str = "gist_rainbow", plot_size: int = 5,
12
15
  show=False, debug: bool = False, close_after: float = 0.0) -> FigAxes:
@@ -47,10 +50,9 @@ def helper_imshow_output_expected(
47
50
  return fig, ax
48
51
 
49
52
 
50
-
51
53
  def imshow_raster(
52
54
  raster, title, cmap: str = "gist_rainbow", interpolation: str = None, alpha=None, transform=None, plot_size=5,
53
- show=False, debug: bool = False, close_after: float = 0.0) -> FigAxes:
55
+ show=False, debug: bool = False, close_after: float = 0.0, backend: MatplotlibBackend = None) -> FigAxes:
54
56
  """
55
57
  Displays raster images lists/arrays with titles, legend, alpha transparency, figure sizes
56
58
  and geographic transformations, if not none (leveraging rasterio.plot)
@@ -66,6 +68,7 @@ def imshow_raster(
66
68
  show: fire plt.show() action if needed
67
69
  debug: workaround useful in an interactive context, like Pycharm debugger
68
70
  close_after: close after give seconds (useful in tests, contrasted to 'debug' option)
71
+ backend: matplotlib backend string
69
72
 
70
73
  Returns:
71
74
  tuple of matplotlib Figure, Axes
@@ -73,6 +76,11 @@ def imshow_raster(
73
76
  """
74
77
  from rasterio import plot
75
78
 
79
+ if not backend:
80
+ backend = plt.get_backend()
81
+ plt.rcParams["backend"] = backend
82
+ logger.info(f"use {backend} as matplotlib backend...")
83
+
76
84
  fig, ax = plt.subplots(figsize=(plot_size, plot_size))
77
85
  raster_ax = raster[0] if transform is not None else raster
78
86
  image_hidden = ax.imshow(raster_ax, cmap=cmap, interpolation=interpolation, alpha=alpha)
@@ -67,5 +67,32 @@ class EmbeddingPILImage(TypedDict):
67
67
  resized_size: TupleInt2
68
68
 
69
69
 
70
+ class MatplotlibBackend(StrEnum):
71
+ gtk3agg = "gtk3agg"
72
+ gtk3cairo = "gtk3cairo"
73
+ gtk4agg = "gtk4agg"
74
+ gtk4cairo = "gtk4cairo"
75
+ macosx = "macosx"
76
+ nbagg = "nbagg"
77
+ notebook = "notebook"
78
+ qtagg = "qtagg"
79
+ qtcairo = "qtcairo"
80
+ qt5agg = "qt5agg"
81
+ qt5cairo = "qt5cairo"
82
+ tkagg = "tkagg"
83
+ tkcairo = "tkcairo"
84
+ webagg = "webagg"
85
+ wx = "wx"
86
+ wxagg = "wxagg"
87
+ wxcairo = "wxcairo"
88
+ agg = "agg"
89
+ cairo = "cairo"
90
+ pdf = "pdf"
91
+ pgf = "pgf"
92
+ ps = "ps"
93
+ svg = "svg"
94
+ template = "template"
95
+
96
+
70
97
  EmbeddingDict = dict[str, EmbeddingImage]
71
98
  EmbeddingPILDict = dict[str, EmbeddingPILImage]
@@ -1,6 +1,7 @@
1
1
  """Various utilities (logger, time benchmark, args dump, numerical and stats info)"""
2
2
  from copy import deepcopy
3
3
 
4
+ import numpy as np
4
5
  from numpy import ndarray, float32
5
6
 
6
7
  from samgis_core import app_logger
@@ -136,3 +137,24 @@ def apply_coords(coords: ndarray, embedding: EmbeddingPILImage):
136
137
  coords[..., 1] = coords[..., 1] * (resized_height / orig_height)
137
138
 
138
139
  return coords.astype(float32)
140
+
141
+
142
+ def normalize_array(arr: ndarray, new_h: int | float = 255., type_normalization: str = "int") -> ndarray:
143
+ """
144
+ Normalize numpy array between 0 and 'new_h' value. Default dtype of output array is int
145
+
146
+
147
+ Args:
148
+ arr: input numpy array
149
+ new_h: max value of output array
150
+ type_normalization: default dtype of output array
151
+
152
+ Returns:
153
+ numpy array
154
+ """
155
+ arr = arr.astype(float)
156
+ arr_max = np.nanmax(arr)
157
+ arr_min = np.nanmin(arr)
158
+ scaled_arr = (arr - arr_min) / (arr_max - arr_min)
159
+ multiplied_arr = scaled_arr * new_h
160
+ return multiplied_arr.astype(int) if type_normalization == "int" else multiplied_arr
File without changes
File without changes