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.
- {samgis_core-3.0.5 → samgis_core-3.0.8}/PKG-INFO +1 -1
- {samgis_core-3.0.5 → samgis_core-3.0.8}/pyproject.toml +3 -2
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/prediction_api/sam_onnx_inference.py +19 -3
- samgis_core-3.0.8/samgis_core/utilities/frontend_builder.py +150 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/utilities/plot_images.py +12 -4
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/utilities/type_hints.py +27 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/utilities/utilities.py +22 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/LICENSE +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/README.md +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/__init__.py +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/__version__.py +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/prediction_api/__init__.py +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/prediction_api/sam_onnx2.py +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/utilities/__init__.py +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/utilities/constants.py +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/utilities/create_folders_if_not_exists.py +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/utilities/serialize.py +0 -0
- {samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/utilities/session_logger.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "samgis_core"
|
3
|
-
version = "3.0.
|
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.
|
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
|
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(
|
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,
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{samgis_core-3.0.5 → samgis_core-3.0.8}/samgis_core/utilities/create_folders_if_not_exists.py
RENAMED
File without changes
|
File without changes
|
File without changes
|