Coverage for bioimageio/spec/_package.py: 81%

88 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-27 09:20 +0000

1import collections.abc 

2import shutil 

3from io import BytesIO 

4from pathlib import Path 

5from tempfile import NamedTemporaryFile, mkdtemp 

6from typing import IO, Dict, Literal, Optional, Sequence, Union 

7from zipfile import ZIP_DEFLATED 

8 

9from loguru import logger 

10from pydantic import DirectoryPath, FilePath, NewPath 

11 

12from ._description import InvalidDescr, ResourceDescr, build_description 

13from ._internal.common_nodes import ResourceDescrBase 

14from ._internal.io import ( 

15 BioimageioYamlContent, 

16 BioimageioYamlSource, 

17 FileDescr, 

18 RelativeFilePath, 

19 ensure_is_valid_bioimageio_yaml_name, 

20) 

21from ._internal.io_basics import ( 

22 BIOIMAGEIO_YAML, 

23 AbsoluteFilePath, 

24 BytesReader, 

25 FileName, 

26 ZipPath, 

27) 

28from ._internal.io_utils import open_bioimageio_yaml, write_yaml, write_zip 

29from ._internal.packaging_context import PackagingContext 

30from ._internal.url import HttpUrl 

31from ._internal.utils import get_os_friendly_file_name 

32from ._internal.validation_context import get_validation_context 

33from ._internal.warning_levels import ERROR 

34from ._io import load_description 

35from .model.v0_4 import WeightsFormat 

36 

37 

38# TODO: deprecate in favor of get_package_content 

39def get_resource_package_content( 

40 rd: ResourceDescr, 

41 /, 

42 *, 

43 bioimageio_yaml_file_name: FileName = BIOIMAGEIO_YAML, 

44 weights_priority_order: Optional[Sequence[WeightsFormat]] = None, # model only 

45) -> Dict[FileName, Union[HttpUrl, AbsoluteFilePath, BioimageioYamlContent, ZipPath]]: 

46 ret: Dict[ 

47 FileName, Union[HttpUrl, AbsoluteFilePath, BioimageioYamlContent, ZipPath] 

48 ] = {} 

49 for k, v in get_package_content( 

50 rd, 

51 bioimageio_yaml_file_name=bioimageio_yaml_file_name, 

52 weights_priority_order=weights_priority_order, 

53 ).items(): 

54 if isinstance(v, FileDescr): 

55 if isinstance(v.source, (Path, RelativeFilePath)): 

56 ret[k] = v.source.absolute() 

57 else: 

58 ret[k] = v.source 

59 

60 else: 

61 ret[k] = v 

62 

63 return ret 

64 

65 

66def get_package_content( 

67 rd: ResourceDescr, 

68 /, 

69 *, 

70 bioimageio_yaml_file_name: FileName = BIOIMAGEIO_YAML, 

71 weights_priority_order: Optional[Sequence[WeightsFormat]] = None, # model only 

72) -> Dict[FileName, Union[FileDescr, BioimageioYamlContent]]: 

73 """ 

74 Args: 

75 rd: resource description 

76 bioimageio_yaml_file_name: RDF file name 

77 # for model resources only: 

78 weights_priority_order: If given, only the first weights format present in the model is included. 

79 If none of the prioritized weights formats is found a ValueError is raised. 

80 """ 

81 os_friendly_name = get_os_friendly_file_name(rd.name) 

82 bioimageio_yaml_file_name = bioimageio_yaml_file_name.format( 

83 name=os_friendly_name, type=rd.type 

84 ) 

85 

86 bioimageio_yaml_file_name = ensure_is_valid_bioimageio_yaml_name( 

87 bioimageio_yaml_file_name 

88 ) 

89 content: Dict[FileName, FileDescr] = {} 

90 with PackagingContext( 

91 bioimageio_yaml_file_name=bioimageio_yaml_file_name, 

92 file_sources=content, 

93 weights_priority_order=weights_priority_order, 

94 ): 

95 rdf_content: BioimageioYamlContent = rd.model_dump( 

96 mode="json", exclude_unset=True 

97 ) 

98 

99 _ = rdf_content.pop("rdf_source", None) 

100 

101 return {**content, bioimageio_yaml_file_name: rdf_content} 

102 

103 

104def _prepare_resource_package( 

105 source: Union[BioimageioYamlSource, ResourceDescr], 

106 /, 

107 *, 

108 weights_priority_order: Optional[Sequence[WeightsFormat]] = None, 

109) -> Dict[FileName, Union[BioimageioYamlContent, BytesReader]]: 

110 """Prepare to package a resource description; downloads all required files. 

111 

112 Args: 

113 source: A bioimage.io resource description (as file, raw YAML content or description class) 

114 context: validation context 

115 weights_priority_order: If given only the first weights format present in the model is included. 

116 If none of the prioritized weights formats is found all are included. 

117 """ 

118 context = get_validation_context() 

119 bioimageio_yaml_file_name = context.file_name 

120 if isinstance(source, ResourceDescrBase): 

121 descr = source 

122 elif isinstance(source, collections.abc.Mapping): 

123 descr = build_description(source) 

124 else: 

125 opened = open_bioimageio_yaml(source) 

126 bioimageio_yaml_file_name = opened.original_file_name 

127 context = context.replace( 

128 root=opened.original_root, file_name=opened.original_file_name 

129 ) 

130 with context: 

131 descr = build_description(opened.content) 

132 

133 if isinstance(descr, InvalidDescr): 

134 raise ValueError(f"{source} is invalid: {descr.validation_summary}") 

135 

136 with context: 

137 package_content = get_package_content( 

138 descr, 

139 bioimageio_yaml_file_name=bioimageio_yaml_file_name or BIOIMAGEIO_YAML, 

140 weights_priority_order=weights_priority_order, 

141 ) 

142 

143 return { 

144 k: v if isinstance(v, collections.abc.Mapping) else v.get_reader() 

145 for k, v in package_content.items() 

146 } 

147 

148 

149def save_bioimageio_package_as_folder( 

150 source: Union[BioimageioYamlSource, ResourceDescr], 

151 /, 

152 *, 

153 output_path: Union[NewPath, DirectoryPath, None] = None, 

154 weights_priority_order: Optional[ # model only 

155 Sequence[ 

156 Literal[ 

157 "keras_hdf5", 

158 "onnx", 

159 "pytorch_state_dict", 

160 "tensorflow_js", 

161 "tensorflow_saved_model_bundle", 

162 "torchscript", 

163 ] 

164 ] 

165 ] = None, 

166) -> DirectoryPath: 

167 """Write the content of a bioimage.io resource package to a folder. 

168 

169 Args: 

170 source: bioimageio resource description 

171 output_path: file path to write package to 

172 weights_priority_order: If given only the first weights format present in the model is included. 

173 If none of the prioritized weights formats is found all are included. 

174 

175 Returns: 

176 directory path to bioimageio package folder 

177 """ 

178 package_content = _prepare_resource_package( 

179 source, 

180 weights_priority_order=weights_priority_order, 

181 ) 

182 if output_path is None: 

183 output_path = Path(mkdtemp()) 

184 else: 

185 output_path = Path(output_path) 

186 

187 output_path.mkdir(exist_ok=True, parents=True) 

188 for name, src in package_content.items(): 

189 if isinstance(src, collections.abc.Mapping): 

190 write_yaml(src, output_path / name) 

191 else: 

192 with (output_path / name).open("wb") as dest: 

193 _ = shutil.copyfileobj(src, dest) 

194 

195 return output_path 

196 

197 

198def save_bioimageio_package( 

199 source: Union[BioimageioYamlSource, ResourceDescr], 

200 /, 

201 *, 

202 compression: int = ZIP_DEFLATED, 

203 compression_level: int = 1, 

204 output_path: Union[NewPath, FilePath, None] = None, 

205 weights_priority_order: Optional[ # model only 

206 Sequence[ 

207 Literal[ 

208 "keras_hdf5", 

209 "onnx", 

210 "pytorch_state_dict", 

211 "tensorflow_js", 

212 "tensorflow_saved_model_bundle", 

213 "torchscript", 

214 ] 

215 ] 

216 ] = None, 

217 allow_invalid: bool = False, 

218) -> FilePath: 

219 """Package a bioimageio resource as a zip file. 

220 

221 Args: 

222 rd: bioimageio resource description 

223 compression: The numeric constant of compression method. 

224 compression_level: Compression level to use when writing files to the archive. 

225 See https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile 

226 output_path: file path to write package to 

227 weights_priority_order: If given only the first weights format present in the model is included. 

228 If none of the prioritized weights formats is found all are included. 

229 

230 Returns: 

231 path to zipped bioimageio package 

232 """ 

233 package_content = _prepare_resource_package( 

234 source, 

235 weights_priority_order=weights_priority_order, 

236 ) 

237 if output_path is None: 

238 output_path = Path( 

239 NamedTemporaryFile(suffix=".bioimageio.zip", delete=False).name 

240 ) 

241 else: 

242 output_path = Path(output_path) 

243 

244 write_zip( 

245 output_path, 

246 package_content, 

247 compression=compression, 

248 compression_level=compression_level, 

249 ) 

250 with get_validation_context().replace(warning_level=ERROR): 

251 if isinstance((exported := load_description(output_path)), InvalidDescr): 

252 exported.validation_summary.display() 

253 msg = f"Exported package at '{output_path}' is invalid." 

254 if allow_invalid: 

255 logger.error(msg) 

256 else: 

257 raise ValueError(msg) 

258 

259 return output_path 

260 

261 

262def save_bioimageio_package_to_stream( 

263 source: Union[BioimageioYamlSource, ResourceDescr], 

264 /, 

265 *, 

266 compression: int = ZIP_DEFLATED, 

267 compression_level: int = 1, 

268 output_stream: Union[IO[bytes], None] = None, 

269 weights_priority_order: Optional[ # model only 

270 Sequence[ 

271 Literal[ 

272 "keras_hdf5", 

273 "onnx", 

274 "pytorch_state_dict", 

275 "tensorflow_js", 

276 "tensorflow_saved_model_bundle", 

277 "torchscript", 

278 ] 

279 ] 

280 ] = None, 

281) -> IO[bytes]: 

282 """Package a bioimageio resource into a stream. 

283 

284 Args: 

285 rd: bioimageio resource description 

286 compression: The numeric constant of compression method. 

287 compression_level: Compression level to use when writing files to the archive. 

288 See https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile 

289 output_stream: stream to write package to 

290 weights_priority_order: If given only the first weights format present in the model is included. 

291 If none of the prioritized weights formats is found all are included. 

292 

293 Note: this function bypasses safety checks and does not load/validate the model after writing. 

294 

295 Returns: 

296 stream of zipped bioimageio package 

297 """ 

298 if output_stream is None: 

299 output_stream = BytesIO() 

300 

301 package_content = _prepare_resource_package( 

302 source, 

303 weights_priority_order=weights_priority_order, 

304 ) 

305 

306 write_zip( 

307 output_stream, 

308 package_content, 

309 compression=compression, 

310 compression_level=compression_level, 

311 ) 

312 

313 return output_stream