Coverage for qubalab/qupath/qupath_gateway.py: 25%

89 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-31 11:24 +0000

1import numpy as np 

2from py4j.java_gateway import JavaGateway, GatewayParameters, JavaObject 

3from py4j.protocol import Py4JNetworkError 

4from enum import Enum 

5from typing import Union 

6import geojson 

7from ..images import utils 

8from ..objects.image_feature import ImageFeature 

9from ..objects.object_type import ObjectType 

10from ..objects.geojson import geojson_features_from_string 

11 

12 

13_default_gateway = None 

14 

15 

16def create_gateway(auto_convert=True, auth_token=None, port=25333, set_as_default=True, **kwargs) -> JavaGateway: 

17 """ 

18 Create a new JavaGateway to communicate with QuPath. 

19 

20 This requires launching QuPath and activating Py4J from there first. 

21 

22 :param auto_convert: if True, the gateway will try to automatically convert Python objects like sequences and maps to Java Objects 

23 :param auth_token: the authentication token to use to connect. Can be None if no authentication is used 

24 :param port: the port to use to connect 

25 :param set_as_default: whether to set the created gateway as the default gateway 

26 :param kwargs: additional arguments to give to the JavaGateway constructor 

27 :returns: the created gateway 

28 :raises RuntimeError: if the connection to QuPath couldn't be established 

29 """ 

30 if auth_token is None: 

31 gateway_parameters = GatewayParameters(port=port) 

32 else: 

33 gateway_parameters = GatewayParameters(auth_token=auth_token, port=port) 

34 gateway = JavaGateway( 

35 auto_convert=auto_convert, 

36 gateway_parameters=gateway_parameters, 

37 **kwargs 

38 ) 

39 

40 try: 

41 # This will fail if QuPath is not running with a Py4J gateway 

42 qupath = gateway.entry_point.getQuPath() 

43 assert qupath is not None 

44 except (Py4JNetworkError, AssertionError) as err: 

45 raise RuntimeError('Could not connect to QuPath - is it running, and you have opened a Py4J gateway?') from err 

46 

47 if set_as_default: 

48 set_default_gateway(gateway) 

49 return gateway 

50 

51 

52def set_default_gateway(gateway: JavaGateway): 

53 """ 

54 Set the default JavaGateway to use if one is not otherwise specified. 

55 

56 This default gateway will be used if no gateway is provided in some functions of this file. 

57 

58 :param gateway: the gateway that should be the default gateway 

59 """ 

60 global _default_gateway 

61 _default_gateway = gateway 

62 

63 

64def get_default_gateway() -> JavaGateway: 

65 """ 

66 Return the default gateway. It will be created if it doesn't already exist. 

67 

68 :returns: the default gateway 

69 """ 

70 global _default_gateway 

71 if _default_gateway is None: 

72 _default_gateway = create_gateway() 

73 return _default_gateway 

74 

75 

76def get_current_image_data(gateway: JavaGateway = None) -> JavaObject: 

77 """ 

78 Get the current ImageData opened in QuPath through the provided gateway. 

79 

80 :param gateway: the gateway to use. Can be None to use the default gateway 

81 :returns: the current ImageData opened in QuPath 

82 """ 

83 gateway = get_default_gateway() if gateway is None else gateway 

84 return gateway.entry_point.getQuPath().getImageData() 

85 

86 

87def get_project(gateway: JavaGateway = None) -> JavaObject: 

88 """ 

89 Return the currently opened QuPath project. 

90 

91 :param gateway: the gateway to use. Can be None to use the default gateway 

92 :return: a Java Object representing the currently opened QuPath project 

93 """ 

94 gateway = get_default_gateway() if gateway is None else gateway 

95 return gateway.entry_point.getProject() 

96 

97 

98def create_snapshot(gateway: JavaGateway = None, snapshot_type: str = 'qupath') -> np.ndarray: 

99 """ 

100 Create and return a snapshot of QuPath.  

101 

102 :param gateway: the gateway to use. Can be None to use the default gateway 

103 :param snapshot_type: what to include in the snapshot. 'qupath' for the entire qupath window or 

104 'viewer' for only the viewer 

105 :returns: a numpy array with dimensions (y, x, c) representing an RGB image 

106 :raises ValueError: if the snapshot type was not recognized 

107 """ 

108 gateway = get_default_gateway() if gateway is None else gateway 

109 qp = gateway.entry_point 

110 

111 if snapshot_type == 'qupath': 

112 image = qp.snapshotBase64(qp.getQuPath()) 

113 elif snapshot_type == 'viewer': 

114 image = qp.snapshotBase64(qp.getCurrentViewer()) 

115 else: 

116 raise ValueError(f'Unknown snapshot_type {snapshot_type}') 

117 

118 return utils.base64_to_image(image, True) 

119 

120 

121def get_objects( 

122 image_data: JavaObject = None, 

123 gateway: JavaGateway = None, 

124 object_type: ObjectType = None, 

125 converter: str = None 

126) -> Union[list[geojson.Feature], list[ImageFeature], list[JavaObject]]: 

127 """ 

128 Get the objects (e.g. detections, annotations) of the current or specified image in QuPath. 

129 

130 :param image_data: the image_data to retrieve objects from. Can be None to use the current ImageData opened in QuPath 

131 :param gateway: the gateway to use. Can be None to use the default gateway 

132 :param object_type: the type of object to get. Can be None to get all objects except the root 

133 :param converter: can be 'geojson' to get an extended GeoJSON feature represented as a QuBaLab 'ImageObject', 

134 or 'simple_feature' to get a regular GeoJSON 'Feature'. By default, the Py4J representation of 

135 the QuPath Java object is returned. 

136 :return: the list of objects of the specified image. The type of the objects depends on the converted parameter 

137 """ 

138 gateway = get_default_gateway() if gateway is None else gateway 

139 image_data = get_current_image_data(gateway) if image_data is None else image_data 

140 hierarchy = image_data.getHierarchy() 

141 

142 match object_type: 

143 case None: 

144 path_objects = hierarchy.getAllObjects(False) 

145 case ObjectType.ANNOTATION: 

146 path_objects = hierarchy.getAnnotationObjects() 

147 case ObjectType.DETECTION: 

148 path_objects = hierarchy.getDetectionObjects() 

149 case ObjectType.TILE: 

150 path_objects = hierarchy.getTileObjects() 

151 case ObjectType.CELL: 

152 path_objects = hierarchy.getCellObjects() 

153 case ObjectType.TMA_CORE: 

154 tma_grid = hierarchy.getTMAGrid() 

155 path_objects = [] if tma_grid is None else tma_grid.getTMACoreList() 

156 

157 if converter == 'geojson' or converter == 'simple_feature': 

158 # Use toFeatureCollections for performance and to avoid string length troubles 

159 features = [] 

160 for feature_collection in gateway.entry_point.toFeatureCollections(path_objects, 1000): 

161 features.extend(geojson_features_from_string(feature_collection, parse_constant=None)) 

162 

163 if converter == 'simple_feature': 

164 return features 

165 else: 

166 return [ImageFeature.create_from_feature(f) for f in features] 

167 else: 

168 return path_objects 

169 

170 

171def count_objects(image_data: JavaObject = None, gateway: JavaGateway = None, object_type: ObjectType = None,) -> int: 

172 """ 

173 Get a count of all objects in the current or specified image. 

174 

175 Since requesting the objects can be slow, this can be used to check if a reasonable 

176 number of objects is available. 

177 

178 :param image_data: the image_data to retrieve objects from. Can be None to use the current ImageData opened in QuPath 

179 :param gateway: the gateway to use. Can be None to use the default gateway 

180 :param object_type: the type of object to get. Can be None to get all objects except the root 

181 :return: the number of such objects in the current or specified image 

182 """ 

183 return len(get_objects(image_data, gateway, object_type)) 

184 

185 

186def add_objects(features: Union[list[geojson.Feature], geojson.Feature], image_data: JavaObject = None, gateway: JavaGateway = None): 

187 """ 

188 Add the provided features to the current or provided ImageData. 

189 

190 :param features: the features to add. Can be a list or a single Feature 

191 :param image_data: the image_data to add features to. 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 """ 

194 gateway = get_default_gateway() if gateway is None else gateway 

195 image_data = get_current_image_data(gateway) if image_data is None else image_data 

196 

197 if isinstance(features, geojson.Feature): 

198 features = list(features) 

199 

200 features_json = geojson.dumps(features, allow_nan=True) 

201 image_data.getHierarchy().addObjects(gateway.entry_point.toPathObjects(features_json)) 

202 

203 

204def delete_objects(image_data: JavaObject = None, gateway: JavaGateway = None, object_type: ObjectType = None): 

205 """ 

206 Delete all specified objects (e.g. annotations, detections) from the current or provided ImageData. 

207 

208 :param image_data: the image_data to remove the objects from. 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 :param object_type: the type of object to remove. Can be None to remove all objects 

211 """ 

212 gateway = get_default_gateway() if gateway is None else gateway 

213 image_data = get_current_image_data(gateway) if image_data is None else image_data 

214 

215 if image_data is not None: 

216 image_data.getHierarchy().removeObjects(get_objects(image_data, gateway, object_type), True) 

217 image_data.getHierarchy().getSelectionModel().clearSelection() 

218 

219 

220def refresh_qupath(gateway: JavaGateway = None): 

221 """ 

222 Update the current QuPath interface. 

223  

224 This is sometimes needed to update the QuPath window when some changes are 

225 made to hierarchy. 

226 

227 :param gateway: the gateway to use. Can be None to use the default gateway 

228 """ 

229 gateway = get_default_gateway() if gateway is None else gateway 

230 gateway.entry_point.fireHierarchyUpdate()