Coverage for qubalab/qupath/qupath_gateway.py: 23%
90 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-10-07 15:29 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-10-07 15:29 +0000
1import numpy as np
2from py4j.java_gateway import JavaGateway, GatewayParameters, JavaObject
3from py4j.protocol import Py4JNetworkError
4from typing import Union, Optional
5import geojson
6from ..images import utils
7from ..objects.image_feature import ImageFeature
8from ..objects.object_type import ObjectType
9from ..objects.geojson import geojson_features_from_string
12_default_gateway = None
15def create_gateway(
16 auto_convert=True, auth_token=None, port=25333, set_as_default=True, **kwargs
17) -> JavaGateway:
18 """
19 Create a new JavaGateway to communicate with QuPath.
21 This requires launching QuPath and activating Py4J from there first.
23 :param auto_convert: if True, the gateway will try to automatically convert Python objects like sequences and maps to Java Objects
24 :param auth_token: the authentication token to use to connect. Can be None if no authentication is used
25 :param port: the port to use to connect
26 :param set_as_default: whether to set the created gateway as the default gateway
27 :param kwargs: additional arguments to give to the JavaGateway constructor
28 :returns: the created gateway
29 :raises RuntimeError: if the connection to QuPath couldn't be established
30 """
31 if auth_token is None:
32 gateway_parameters = GatewayParameters(port=port)
33 else:
34 gateway_parameters = GatewayParameters(auth_token=auth_token, port=port)
35 gateway = JavaGateway(
36 auto_convert=auto_convert, gateway_parameters=gateway_parameters, **kwargs
37 )
39 try:
40 # This will fail if QuPath is not running with a Py4J gateway
41 qupath = gateway.entry_point.getQuPath()
42 assert qupath is not None
43 except (Py4JNetworkError, AssertionError) as err:
44 raise RuntimeError(
45 "Could not connect to QuPath - is it running, and you have opened a Py4J gateway?"
46 ) from err
48 if set_as_default:
49 set_default_gateway(gateway)
50 return gateway
53def set_default_gateway(gateway: JavaGateway):
54 """
55 Set the default JavaGateway to use if one is not otherwise specified.
57 This default gateway will be used if no gateway is provided in some functions of this file.
59 :param gateway: the gateway that should be the default gateway
60 """
61 global _default_gateway
62 _default_gateway = gateway
65def get_default_gateway() -> JavaGateway:
66 """
67 Return the default gateway. It will be created if it doesn't already exist.
69 :returns: the default gateway
70 """
71 global _default_gateway
72 if _default_gateway is None:
73 _default_gateway = create_gateway()
74 return _default_gateway
77def get_current_image_data(gateway: Optional[JavaGateway] = None) -> JavaObject:
78 """
79 Get the current ImageData opened in QuPath through the provided gateway.
81 :param gateway: the gateway to use. Can be None to use the default gateway
82 :returns: the current ImageData opened in QuPath
83 """
84 gateway = get_default_gateway() if gateway is None else gateway
85 return gateway.entry_point.getQuPath().getImageData()
88def get_project(gateway: Optional[JavaGateway] = None) -> JavaObject:
89 """
90 Return the currently opened QuPath project.
92 :param gateway: the gateway to use. Can be None to use the default gateway
93 :return: a Java Object representing the currently opened QuPath project
94 """
95 gateway = get_default_gateway() if gateway is None else gateway
96 return gateway.entry_point.getProject()
99def create_snapshot(
100 gateway: JavaGateway = None, snapshot_type: str = "qupath"
101) -> np.ndarray:
102 """
103 Create and return a snapshot of QuPath.
105 :param gateway: the gateway to use. Can be None to use the default gateway
106 :param snapshot_type: what to include in the snapshot. 'qupath' for the entire qupath window or
107 'viewer' for only the viewer
108 :returns: a numpy array with dimensions (y, x, c) representing an RGB image
109 :raises ValueError: if the snapshot type was not recognized
110 """
111 gateway = get_default_gateway() if gateway is None else gateway
112 qp = gateway.entry_point
114 if snapshot_type == "qupath":
115 image = qp.snapshotBase64(qp.getQuPath())
116 elif snapshot_type == "viewer":
117 image = qp.snapshotBase64(qp.getCurrentViewer())
118 else:
119 raise ValueError(f"Unknown snapshot_type {snapshot_type}")
121 return utils.base64_to_image(image, True)
124def get_objects(
125 image_data: Optional[JavaObject] = None,
126 gateway: Optional[JavaGateway] = None,
127 object_type: Optional[ObjectType] = None,
128 converter: Optional[str] = None,
129) -> Union[list[geojson.Feature], list[ImageFeature], list[JavaObject]]:
130 """
131 Get the objects (e.g. detections, annotations) of the current or specified image in QuPath.
133 :param image_data: the image_data to retrieve objects from. Can be None to use the current ImageData opened in QuPath
134 :param gateway: the gateway to use. Can be None to use the default gateway
135 :param object_type: the type of object to get. Can be None to get all objects except the root
136 :param converter: can be 'geojson' to get an extended GeoJSON feature represented as a QuBaLab 'ImageObject',
137 or 'simple_feature' to get a regular GeoJSON 'Feature'. By default, the Py4J representation of
138 the QuPath Java object is returned.
139 :return: the list of objects of the specified image. The type of the objects depends on the converted parameter
140 """
141 gateway = get_default_gateway() if gateway is None else gateway
142 image_data = get_current_image_data(gateway) if image_data is None else image_data
143 hierarchy = image_data.getHierarchy()
145 match object_type:
146 case None:
147 path_objects = hierarchy.getAllObjects(False)
148 case ObjectType.ANNOTATION:
149 path_objects = hierarchy.getAnnotationObjects()
150 case ObjectType.DETECTION:
151 path_objects = hierarchy.getDetectionObjects()
152 case ObjectType.TILE:
153 path_objects = hierarchy.getTileObjects()
154 case ObjectType.CELL:
155 path_objects = hierarchy.getCellObjects()
156 case ObjectType.TMA_CORE:
157 tma_grid = hierarchy.getTMAGrid()
158 path_objects = [] if tma_grid is None else tma_grid.getTMACoreList()
159 case _:
160 path_objects = hierarchy.getAllObjects(False)
162 if converter == "geojson" or converter == "simple_feature":
163 # Use toFeatureCollections for performance and to avoid string length troubles
164 features = []
165 for feature_collection in gateway.entry_point.toFeatureCollections(
166 path_objects, 1000
167 ):
168 features.extend(
169 geojson_features_from_string(feature_collection, parse_constant=None)
170 )
172 if converter == "simple_feature":
173 return features
174 else:
175 return [ImageFeature.create_from_feature(f) for f in features]
176 else:
177 return path_objects
180def count_objects(
181 image_data: Optional[JavaObject] = None,
182 gateway: Optional[JavaGateway] = None,
183 object_type: Optional[ObjectType] = None,
184) -> int:
185 """
186 Get a count of all objects in the current or specified image.
188 Since requesting the objects can be slow, this can be used to check if a reasonable
189 number of objects is available.
191 :param image_data: the image_data to retrieve objects from. Can be None to use the current ImageData opened in QuPath
192 :param gateway: the gateway to use. Can be None to use the default gateway
193 :param object_type: the type of object to get. Can be None to get all objects except the root
194 :return: the number of such objects in the current or specified image
195 """
196 return len(get_objects(image_data, gateway, object_type))
199def add_objects(
200 features: Union[list[geojson.Feature], geojson.Feature],
201 image_data: Optional[JavaObject] = None,
202 gateway: Optional[JavaGateway] = None,
203):
204 """
205 Add the provided features to the current or provided ImageData.
207 :param features: the features to add. Can be a list or a single Feature
208 :param image_data: the image_data to add features to. Can be None to use the current ImageData opened in QuPath
209 :param gateway: the gateway to use. Can be None to use the default gateway
210 """
211 gateway = get_default_gateway() if gateway is None else gateway
212 image_data = get_current_image_data(gateway) if image_data is None else image_data
214 if isinstance(features, geojson.Feature):
215 features = list(features)
217 features_json = geojson.dumps(features, allow_nan=True)
218 image_data.getHierarchy().addObjects(
219 gateway.entry_point.toPathObjects(features_json)
220 )
223def delete_objects(
224 image_data: Optional[JavaObject] = None,
225 gateway: Optional[JavaGateway] = None,
226 object_type: Optional[ObjectType] = None,
227):
228 """
229 Delete all specified objects (e.g. annotations, detections) from the current or provided ImageData.
231 :param image_data: the image_data to remove the objects from. Can be None to use the current ImageData opened in QuPath
232 :param gateway: the gateway to use. Can be None to use the default gateway
233 :param object_type: the type of object to remove. Can be None to remove all objects
234 """
235 gateway = get_default_gateway() if gateway is None else gateway
236 image_data = get_current_image_data(gateway) if image_data is None else image_data
238 if image_data is not None:
239 image_data.getHierarchy().removeObjects(
240 get_objects(image_data, gateway, object_type), True
241 )
242 image_data.getHierarchy().getSelectionModel().clearSelection()
245def refresh_qupath(gateway: Optional[JavaGateway] = None) -> None:
246 """
247 Update the current QuPath interface.
249 This is sometimes needed to update the QuPath window when some changes are
250 made to hierarchy.
252 :param gateway: the gateway to use. Can be None to use the default gateway
253 """
254 gateway = get_default_gateway() if gateway is None else gateway
255 gateway.entry_point.fireHierarchyUpdate()