samgis_core 3.0.4__tar.gz → 3.0.5__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.4
3
+ Version: 3.0.5
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.4"
3
+ version = "3.0.5"
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.4"
11
+ version = "3.0.5"
12
12
 
13
13
  [tool.poetry.urls]
14
14
  Source = "https://gitlab.com/aletrn/samgis_core"
@@ -37,7 +37,7 @@ from samgis_core.utilities.utilities import convert_ndarray_to_pil, apply_coords
37
37
 
38
38
  class SegmentAnythingONNX2:
39
39
  """
40
- Segmentation model using SegmentAnything.
40
+ Segmentation model using Segment Anything.
41
41
  Compatible with onnxruntime 1.17.x and later
42
42
  """
43
43
 
@@ -68,8 +68,17 @@ class SegmentAnythingONNX2:
68
68
  )
69
69
 
70
70
  @staticmethod
71
- def get_input_points(prompt: ListDict):
72
- """Get input points"""
71
+ def get_input_points(prompt: ListDict) -> tuple[ndarray]:
72
+ """
73
+ Get input points from a prompt dict list.
74
+
75
+ Args:
76
+ prompt: dict list
77
+
78
+ Returns:
79
+ tuple of points, labels ndarray ready for Segment Anything inference
80
+
81
+ """
73
82
  points = []
74
83
  labels = []
75
84
  for mark in prompt:
@@ -95,11 +104,12 @@ class SegmentAnythingONNX2:
95
104
 
96
105
  Returns:
97
106
  embedding image dict useful to store and cache image embeddings
107
+
98
108
  """
99
109
  resized_image = self.preprocess_image(img)
100
110
  padded_input_tensor = self.padding_tensor(resized_image)
101
111
 
102
- # 2. GET IMAGE EMBEDDINGS USING IMAGE ENCODER
112
+ # 2. GET IMAGE EMBEDDINGS USING IMAGE ENCODER (`size` argument here is like ndarray `shape`)
103
113
  outputs = self.encoder_session.run(None, {"images": padded_input_tensor})
104
114
  image_embedding = outputs[0]
105
115
  img = convert_ndarray_to_pil(img)
@@ -109,9 +119,17 @@ class SegmentAnythingONNX2:
109
119
  "resized_size": resized_image.size
110
120
  }
111
121
 
112
- def predict_masks(self, embedding: EmbeddingPILImage, prompt: ListDict):
122
+ def predict_masks(self, embedding: EmbeddingPILImage, prompt: ListDict) -> ndarray:
113
123
  """
114
124
  Predict masks for a single image.
125
+
126
+ Args:
127
+ embedding: input image embedding dict
128
+ prompt: Segment Anything input prompt
129
+
130
+ Returns:
131
+ prediction masks ndarray; this should have (1, 1, **image.shape) shape
132
+
115
133
  """
116
134
  input_points, input_labels = self.get_input_points(prompt)
117
135
 
@@ -136,8 +154,17 @@ class SegmentAnythingONNX2:
136
154
  })
137
155
  return output_masks
138
156
 
139
- def preprocess_image(self, img: PIL_Image | ndarray):
140
- """Resize image preserving aspect ratio using 'output_size_target' as a long side"""
157
+ def preprocess_image(self, img: PIL_Image | ndarray) -> ndarray:
158
+ """
159
+ Resize image preserving aspect ratio using `output_size_target` as a long side.
160
+
161
+ Args:
162
+ img: input ndarray/PIL image
163
+
164
+ Returns:
165
+ image ndarray
166
+
167
+ """
141
168
  from PIL import Image
142
169
 
143
170
  app_logger.info(f"image type:{type(img)}, shape/size:{img.size}.")
@@ -157,7 +184,17 @@ class SegmentAnythingONNX2:
157
184
  img = img.resize((resized_width, resized_height), Image.Resampling.BILINEAR)
158
185
  return img
159
186
 
160
- def padding_tensor(self, img: PIL_Image | ndarray):
187
+ def padding_tensor(self, img: PIL_Image | ndarray) -> ndarray:
188
+ """
189
+ Pad an image ndarray/tensor to given instance self.target_size
190
+
191
+ Args:
192
+ img: input ndarray/PIL image
193
+
194
+ Returns:
195
+ image ndarray
196
+
197
+ """
161
198
  # Prepare input tensor from image
162
199
  tensor_input = np_array(img)
163
200
  resized_width, resized_height = img.size
@@ -20,6 +20,7 @@ def get_raster_inference(
20
20
 
21
21
  Returns:
22
22
  raster prediction mask, prediction number
23
+
23
24
  """
24
25
  np_img = np_array(img)
25
26
  app_logger.info(f"img type {type(np_img)}, prompt:{prompt}.")
@@ -49,6 +50,7 @@ def get_inference_embedding(
49
50
 
50
51
  Returns:
51
52
  raster dict
53
+
52
54
  """
53
55
  if embedding_key in embedding_dict:
54
56
  app_logger.info("found embedding in dict...")
@@ -80,6 +82,7 @@ def get_raster_inference_using_existing_embedding(
80
82
 
81
83
  Returns:
82
84
  raster prediction mask, prediction number
85
+
83
86
  """
84
87
  app_logger.info(f"using existing embedding of type {type(embedding)}.")
85
88
  inference_out = models_instance.predict_masks(embedding, prompt)
@@ -111,6 +114,7 @@ def get_raster_inference_with_embedding_from_dict(
111
114
 
112
115
  Returns:
113
116
  raster prediction mask, prediction number
117
+
114
118
  """
115
119
  app_logger.info(f"handling embedding using key {embedding_key}.")
116
120
  embedding_dict = get_inference_embedding(img, models_instance, model_name, embedding_key, embedding_dict)
@@ -10,6 +10,14 @@ def stats_pathname(pathname: Path | str):
10
10
 
11
11
 
12
12
  def create_folder_if_not_exists(pathname: Path | str):
13
+ """Create a folder given its path.
14
+
15
+ Args:
16
+ pathname: folder Path or string
17
+
18
+ Returns:
19
+
20
+ """
13
21
  current_pathname = Path(pathname)
14
22
  try:
15
23
  print(f"Pathname exists? {current_pathname.exists()}, That's a folder? {current_pathname.is_dir()}...")
@@ -32,6 +40,18 @@ def create_folder_if_not_exists(pathname: Path | str):
32
40
 
33
41
 
34
42
  def folders_creation(folders_map: dict | str = None, ignore_errors: bool = True):
43
+ """Create all folders listed within the folders_map argument (this argument can be a dict or a json string).
44
+ If folders_map is None the function will try to load the 'FOLDERS_MAP' env variable, then will load that json into
45
+ dict. Once loaded and parsed the folders_map variable, the function will loop over the dict to create the folders
46
+ using the `create_folder_if_not_exists()` function.
47
+
48
+ Args:
49
+ folders_map: dict or string map of folder string
50
+ ignore_errors: bool needed to eventually ignore errors on folder creation
51
+
52
+ Returns:
53
+
54
+ """
35
55
  enforce_validation_with_getenv = folders_map is None
36
56
  if enforce_validation_with_getenv:
37
57
  folders_map = os.getenv("FOLDERS_MAP")
@@ -0,0 +1,91 @@
1
+ from numpy import ndarray
2
+ from matplotlib import pyplot as plt
3
+
4
+ from samgis_core.utilities.type_hints import ListStr, TupleInt
5
+
6
+
7
+ FigAxes = tuple[plt.Figure, plt.Axes]
8
+
9
+
10
+ def helper_imshow_output_expected(
11
+ img_list: list[ndarray], titles_list: ListStr, cmap: str = "gist_rainbow", plot_size: int = 5,
12
+ show=False, debug: bool = False, close_after: float = 0.0) -> FigAxes:
13
+ """
14
+ Simple way to display a list of images with their titles, color map.
15
+ Should work also in an automate environments, like tests (use a `close_after` argument > 0)
16
+
17
+ Args:
18
+ img_list: ndarray images to display
19
+ titles_list: title images
20
+ cmap: color map
21
+ plot_size: figure plot size
22
+ show: fire plt.show() action if needed
23
+ debug: workaround useful in an interactive context, like Pycharm debugger
24
+ close_after: close after give seconds (useful in tests, contrasted to 'debug' option)
25
+
26
+ Returns:
27
+ tuple of matplotlib Figure, Axes
28
+
29
+ """
30
+ n = len(img_list)
31
+ assert len(titles_list) == n
32
+ fig, ax = plt.subplot_mosaic([
33
+ titles_list
34
+ ], figsize=(n * plot_size, plot_size))
35
+
36
+ for title, img in zip(titles_list, img_list):
37
+ ax[title].imshow(img, cmap=cmap)
38
+ ax[title].legend()
39
+ if show:
40
+ if debug:
41
+ plt.pause(0.01)
42
+ plt.show()
43
+ if close_after > 0:
44
+ plt.pause(close_after)
45
+ plt.show(block=False)
46
+ plt.close("all")
47
+ return fig, ax
48
+
49
+
50
+
51
+ def imshow_raster(
52
+ 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:
54
+ """
55
+ Displays raster images lists/arrays with titles, legend, alpha transparency, figure sizes
56
+ and geographic transformations, if not none (leveraging rasterio.plot)
57
+
58
+ Args:
59
+ raster: image to display
60
+ title: title image
61
+ cmap: color map
62
+ interpolation: interpolation type
63
+ alpha: alpha transparency
64
+ transform: geographic transform, eventually used for map representation by rasterio
65
+ plot_size: figure plot size
66
+ show: fire plt.show() action if needed
67
+ debug: workaround useful in an interactive context, like Pycharm debugger
68
+ close_after: close after give seconds (useful in tests, contrasted to 'debug' option)
69
+
70
+ Returns:
71
+ tuple of matplotlib Figure, Axes
72
+
73
+ """
74
+ from rasterio import plot
75
+
76
+ fig, ax = plt.subplots(figsize=(plot_size, plot_size))
77
+ raster_ax = raster[0] if transform is not None else raster
78
+ image_hidden = ax.imshow(raster_ax, cmap=cmap, interpolation=interpolation, alpha=alpha)
79
+ if transform is not None:
80
+ plot.show(raster, transform=transform, ax=ax, cmap=cmap, interpolation=interpolation, alpha=alpha)
81
+ fig.colorbar(image_hidden, ax=ax)
82
+ ax.set_title(title)
83
+ if show:
84
+ if debug:
85
+ plt.pause(0.01)
86
+ plt.show()
87
+ if close_after > 0:
88
+ plt.pause(close_after)
89
+ plt.show(block=False)
90
+ plt.close("all")
91
+ return fig, ax
@@ -27,6 +27,20 @@ def drop_color_message_key(_, __, event_dict: EventDict) -> EventDict:
27
27
 
28
28
 
29
29
  def setup_logging(json_logs: bool = False, log_level: str = "INFO"):
30
+ """Enhance the configuration of structlog.
31
+ Needed for correlation id injection with fastapi middleware in samgis-web.
32
+ After the use of logging_middleware() in samgis_web.web.middlewares, add also the CorrelationIdMiddleware from
33
+ 'asgi_correlation_id' package. (See 'tests/web/test_middlewares.py' in samgis_web).
34
+ To change an input parameter like the log level, re-run the function changing the parameter
35
+ (no need to re-instantiate the logger instance: it's a hot change)
36
+
37
+ Args:
38
+ json_logs: set logs in json format
39
+ log_level: log level string
40
+
41
+ Returns:
42
+
43
+ """
30
44
  timestamper = structlog.processors.TimeStamper(fmt="iso")
31
45
 
32
46
  shared_processors: list[Processor] = [
@@ -98,6 +98,16 @@ def hash_calculate(arr) -> str | bytes:
98
98
 
99
99
 
100
100
  def convert_ndarray_to_pil(pil_image: PIL_Image | ndarray):
101
+ """
102
+ Check if an image is a ndarray and then convert to a PIL Image instance.
103
+
104
+ Args:
105
+ pil_image: PIL image or ndarray
106
+
107
+ Returns:
108
+ PIL Image
109
+
110
+ """
101
111
  from PIL import Image
102
112
 
103
113
  if isinstance(pil_image, ndarray):
@@ -109,6 +119,14 @@ def apply_coords(coords: ndarray, embedding: EmbeddingPILImage):
109
119
  """
110
120
  Expects a numpy np_array of length 2 in the final dimension. Requires the
111
121
  original image size in (H, W) format.
122
+
123
+ Args:
124
+ coords: coordinates ndarray
125
+ embedding: PIL image embedding dict
126
+
127
+ Returns:
128
+ coordinates ndarray
129
+
112
130
  """
113
131
  orig_width, orig_height = embedding["original_size"]
114
132
  resized_width, resized_height = embedding["resized_size"]
@@ -1,11 +0,0 @@
1
- def helper_imshow_output_expected(img1, img2, title1, title2):
2
- from matplotlib import pyplot as plt
3
-
4
- fig, ax = plt.subplot_mosaic([
5
- [title1, title2]
6
- ], figsize=(15, 10))
7
-
8
- ax[title1].imshow(img1)
9
- ax[title2].imshow(img2)
10
-
11
- plt.show()
File without changes
File without changes