11from typing import List , Dict , Any , Optional , Tuple , TypedDict , Union
22import os
3+ from urllib .parse import urljoin
34import requests
45from qgis .core import (
56 QgsVectorLayer ,
1920from qgis .utils import iface
2021from PyQt5 .QtCore import QMetaType
2122from helper_func import (
22- read_displayed_raster_data , encode_image ,
23+ read_displayed_raster_data ,
24+ encode_image ,
2325)
2426from collections import deque
2527
2628
2729class ModelInfo (TypedDict ):
2830 """Type definition for model info returned from API"""
31+
2932 id : str
3033 name : str
3134 description : Optional [str ]
3235
3336
3437class FeatureState (TypedDict ):
3538 """Type definition for layer state in undo/redo stacks"""
39+
3640 click_layer : List [QgsFeature ]
3741 segm_layer : List [QgsFeature ]
3842
@@ -98,8 +102,7 @@ def __init__(
98102 "click_type" ,
99103 [
100104 QgsRendererCategory (
101- 0 , QgsMarkerSymbol .createSimple ({"color" : "red" }),
102- "Negative Click"
105+ 0 , QgsMarkerSymbol .createSimple ({"color" : "red" }), "Negative Click"
103106 ),
104107 QgsRendererCategory (
105108 1 ,
@@ -145,7 +148,9 @@ def _headers(self) -> Dict[str, str]:
145148
146149 def get_models (self ) -> List [ModelInfo ]:
147150 """Retrieve a list of available models for segmentation."""
148- response = requests .get (os .path .join (self .api_url , "v1/models" ), headers = self ._headers ())
151+ response = requests .get (
152+ urljoin (self .api_url , "v1/models" ), headers = self ._headers ()
153+ )
149154 response .raise_for_status ()
150155 return response .json ()
151156
@@ -155,7 +160,12 @@ def add_click(self, feature: QgsFeature) -> None:
155160 self .click_layer .updateExtents ()
156161 self .click_layer .triggerRepaint ()
157162
158- def segment (self , model_id : str , raster_layer : QgsRasterLayer , new_click : Optional [QgsFeature ] = None ) -> Dict [str , Any ]:
163+ def segment (
164+ self ,
165+ model_id : str ,
166+ raster_layer : QgsRasterLayer ,
167+ new_click : Optional [QgsFeature ] = None ,
168+ ) -> Dict [str , Any ]:
159169 """Perform image segmentation using the current model and manage clicks/mask."""
160170 self ._save_state_for_undo () # Save current state before modification
161171 self .redo_stack .clear () # Clear redo stack on new action
@@ -183,20 +193,20 @@ def segment(self, model_id: str, raster_layer: QgsRasterLayer, new_click: Option
183193
184194 # if self.segm_layer has feature, convert it to previous_mask in the payload
185195 if False :
186- # if self.segm_layer.featureCount() > 0:
196+ # if self.segm_layer.featureCount() > 0:
187197 payload ["previous_mask" ] = self ._segm_layer_to_geojson ()
188198 else :
189199 payload ["previous_mask" ] = []
190200
191201 response = requests .post (
192- os . path . join (self .api_url , "v1/segment" ), json = payload , headers = self ._headers ()
202+ urljoin (self .api_url , "v1/segment" ), json = payload , headers = self ._headers ()
193203 )
194204 response .raise_for_status ()
195205 result = response .json ()
196206
197207 # Save segmentation result to the segmentation layer
198208 self ._geojson_to_segm_layer (result ["segmentation" ])
199-
209+
200210 return result
201211
202212 def _geojson_to_segm_layer (self , segm : List [Dict [str , Any ]]) -> None :
@@ -241,14 +251,13 @@ def _segm_layer_to_geojson(self) -> List[Dict[str, Any]]:
241251 ring_points = []
242252 for point in ring :
243253 # Convert geo coordinates to pixel coordinates
244- pixel_x , pixel_y = self ._geo2pixel_coords (point , self .segm_layer .crs ())
254+ pixel_x , pixel_y = self ._geo2pixel_coords (
255+ point , self .segm_layer .crs ()
256+ )
245257 ring_points .append ([pixel_x , pixel_y ])
246258 polygon .append (ring_points )
247259
248- geojson_features .append ({
249- "type" : "Polygon" ,
250- "coordinates" : polygon
251- })
260+ geojson_features .append ({"type" : "Polygon" , "coordinates" : polygon })
252261
253262 return geojson_features
254263
@@ -270,9 +279,8 @@ def _get_click_list(self) -> List[List[Union[float, int]]]:
270279 return clicks
271280
272281 def _geo2pixel_coords (
273- self , point : QgsPointXY ,
274- point_crs : QgsCoordinateReferenceSystem
275- ) -> Tuple [float , float ]:
282+ self , point : QgsPointXY , point_crs : QgsCoordinateReferenceSystem
283+ ) -> Tuple [float , float ]:
276284 """Convert geo coordinates to pixel coordinates."""
277285 # if point crs is not the same as the canvas crs, transform it to canvas crs
278286 canvas_crs = self .canvas .mapSettings ().destinationCrs ()
@@ -286,13 +294,14 @@ def _geo2pixel_coords(
286294
287295 # Convert to pixel coordinates
288296 pixel_x = (point .x () - extent .xMinimum ()) / extent .width () * self .canvas .width ()
289- pixel_y = (extent .yMaximum () - point .y ()) / extent .height () * self .canvas .height ()
297+ pixel_y = (
298+ (extent .yMaximum () - point .y ()) / extent .height () * self .canvas .height ()
299+ )
290300
291301 return pixel_x , pixel_y
292302
293303 def _pixel2geo_coords (
294- self , pixel_x : float , pixel_y : float ,
295- target_crs : QgsCoordinateReferenceSystem
304+ self , pixel_x : float , pixel_y : float , target_crs : QgsCoordinateReferenceSystem
296305 ) -> Tuple [float , float ]:
297306 """Convert pixel coordinates to geo coordinates."""
298307 extent = self .canvas .extent ()
0 commit comments