Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/SpiderImagePlugin.py: 30%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

204 statements  

1# 

2# The Python Imaging Library. 

3# 

4# SPIDER image file handling 

5# 

6# History: 

7# 2004-08-02 Created BB 

8# 2006-03-02 added save method 

9# 2006-03-13 added support for stack images 

10# 

11# Copyright (c) 2004 by Health Research Inc. (HRI) RENSSELAER, NY 12144. 

12# Copyright (c) 2004 by William Baxter. 

13# Copyright (c) 2004 by Secret Labs AB. 

14# Copyright (c) 2004 by Fredrik Lundh. 

15# 

16 

17## 

18# Image plugin for the Spider image format. This format is used 

19# by the SPIDER software, in processing image data from electron 

20# microscopy and tomography. 

21## 

22 

23# 

24# SpiderImagePlugin.py 

25# 

26# The Spider image format is used by SPIDER software, in processing 

27# image data from electron microscopy and tomography. 

28# 

29# Spider home page: 

30# https://spider-em.github.io/SPIDER/ 

31# 

32# Details about the Spider image format: 

33# https://spider-em.github.io/SPIDER/docs/image_doc.html 

34# 

35from __future__ import annotations 

36 

37__lazy_modules__ = {"PIL._util", "struct"} 

38 

39import os 

40import struct 

41import sys 

42 

43from . import Image, ImageFile 

44from ._util import DeferredError 

45 

46TYPE_CHECKING = False 

47if TYPE_CHECKING: 

48 from typing import IO, Any 

49 

50 

51def isInt(f: Any) -> int: 

52 try: 

53 i = int(f) 

54 if f - i == 0: 

55 return 1 

56 else: 

57 return 0 

58 except (ValueError, OverflowError): 

59 return 0 

60 

61 

62iforms = [1, 3, -11, -12, -21, -22] 

63 

64 

65# There is no magic number to identify Spider files, so just check a 

66# series of header locations to see if they have reasonable values. 

67# Returns no. of bytes in the header, if it is a valid Spider header, 

68# otherwise returns 0 

69 

70 

71def isSpiderHeader(t: tuple[float, ...]) -> int: 

72 h = (99,) + t # add 1 value so can use spider header index start=1 

73 # header values 1,2,5,12,13,22,23 should be integers 

74 for i in [1, 2, 5, 12, 13, 22, 23]: 

75 if not isInt(h[i]): 

76 return 0 

77 # check iform 

78 iform = int(h[5]) 

79 if iform not in iforms: 

80 return 0 

81 # check other header values 

82 labrec = int(h[13]) # no. records in file header 

83 labbyt = int(h[22]) # total no. of bytes in header 

84 lenbyt = int(h[23]) # record length in bytes 

85 if labbyt != (labrec * lenbyt): 

86 return 0 

87 # looks like a valid header 

88 return labbyt 

89 

90 

91def isSpiderImage(filename: str) -> int: 

92 with open(filename, "rb") as fp: 

93 f = fp.read(92) # read 23 * 4 bytes 

94 t = struct.unpack(">23f", f) # try big-endian first 

95 hdrlen = isSpiderHeader(t) 

96 if hdrlen == 0: 

97 t = struct.unpack("<23f", f) # little-endian 

98 hdrlen = isSpiderHeader(t) 

99 return hdrlen 

100 

101 

102class SpiderImageFile(ImageFile.ImageFile): 

103 format = "SPIDER" 

104 format_description = "Spider 2D image" 

105 _close_exclusive_fp_after_loading = False 

106 

107 stkoffset = 0 

108 

109 def _open(self) -> None: 

110 # check header 

111 n = 27 * 4 # read 27 float values 

112 assert self.fp is not None 

113 f = self.fp.read(n) 

114 

115 try: 

116 self.bigendian = 1 

117 t = struct.unpack(">27f", f) # try big-endian first 

118 hdrlen = isSpiderHeader(t) 

119 if hdrlen == 0: 

120 self.bigendian = 0 

121 t = struct.unpack("<27f", f) # little-endian 

122 hdrlen = isSpiderHeader(t) 

123 if hdrlen == 0: 

124 msg = "not a valid Spider file" 

125 raise SyntaxError(msg) 

126 except struct.error as e: 

127 msg = "not a valid Spider file" 

128 raise SyntaxError(msg) from e 

129 

130 h = (99,) + t # add 1 value : spider header index starts at 1 

131 iform = int(h[5]) 

132 if iform != 1: 

133 msg = "not a Spider 2D image" 

134 raise SyntaxError(msg) 

135 

136 self._size = int(h[12]), int(h[2]) # size in pixels (width, height) 

137 self.istack = int(h[24]) 

138 self.imgnumber = int(h[27]) 

139 

140 self.n_frames = 1 

141 if self.istack == 0 and self.imgnumber == 0: 

142 # stk=0, img=0: a regular 2D image 

143 offset = hdrlen 

144 elif self.istack > 0 and self.imgnumber == 0: 

145 # stk>0, img=0: Opening the stack for the first time 

146 self.imgbytes = int(h[12]) * int(h[2]) * 4 

147 self.hdrlen = hdrlen 

148 self.n_frames = int(h[26]) 

149 # Point to the first image in the stack 

150 offset = hdrlen * 2 

151 self.imgnumber = 1 

152 elif self.istack == 0 and self.imgnumber > 0 and self.stkoffset != 0: 

153 # stk=0, img>0: an image within the stack, reached through seek() 

154 offset = hdrlen + self.stkoffset 

155 self.istack = 2 # So Image knows it's still a stack 

156 else: 

157 msg = "inconsistent stack header values" 

158 raise SyntaxError(msg) 

159 self.is_animated = self.n_frames > 1 

160 

161 if self.bigendian: 

162 self.rawmode = "F;32BF" 

163 else: 

164 self.rawmode = "F;32F" 

165 self._mode = "F" 

166 

167 self.tile = [ImageFile._Tile("raw", None, offset, self.rawmode)] 

168 self._fp = self.fp # FIXME: hack 

169 

170 # 1st image index is zero (although SPIDER imgnumber starts at 1) 

171 def tell(self) -> int: 

172 if self.imgnumber < 1: 

173 return 0 

174 else: 

175 return self.imgnumber - 1 

176 

177 def seek(self, frame: int) -> None: 

178 if self.istack == 0: 

179 msg = "attempt to seek in a non-stack file" 

180 raise EOFError(msg) 

181 if not self._seek_check(frame): 

182 return 

183 if isinstance(self._fp, DeferredError): 

184 raise self._fp.ex 

185 self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes) 

186 self.fp = self._fp 

187 self.fp.seek(self.stkoffset) 

188 self._open() 

189 Image._decompression_bomb_check(self.size) 

190 

191 # returns a byte image after rescaling to 0..255 

192 def convert2byte(self, depth: int = 255) -> Image.Image: 

193 extrema = self.getextrema() 

194 assert isinstance(extrema[0], float) 

195 minimum, maximum = extrema 

196 m: float = 1 

197 if maximum != minimum: 

198 m = depth / (maximum - minimum) 

199 b = -m * minimum 

200 return self.point(lambda i: i * m + b).convert("L") 

201 

202 if TYPE_CHECKING: 

203 from . import ImageTk 

204 

205 # returns a ImageTk.PhotoImage object, after rescaling to 0..255 

206 def tkPhotoImage(self) -> ImageTk.PhotoImage: 

207 from . import ImageTk 

208 

209 return ImageTk.PhotoImage(self.convert2byte(), palette=256) 

210 

211 

212# -------------------------------------------------------------------- 

213# Image series 

214 

215 

216# given a list of filenames, return a list of images 

217def loadImageSeries(filelist: list[str] | None = None) -> list[Image.Image] | None: 

218 """create a list of :py:class:`~PIL.Image.Image` objects for use in a montage""" 

219 if filelist is None or len(filelist) < 1: 

220 return None 

221 

222 byte_imgs = [] 

223 for img in filelist: 

224 if not os.path.exists(img): 

225 print(f"unable to find {img}") 

226 continue 

227 try: 

228 with Image.open(img) as im: 

229 assert isinstance(im, SpiderImageFile) 

230 byte_im = im.convert2byte() 

231 except Exception: 

232 if not isSpiderImage(img): 

233 print(f"{img} is not a Spider image file") 

234 continue 

235 byte_im.info["filename"] = img 

236 byte_imgs.append(byte_im) 

237 return byte_imgs 

238 

239 

240# -------------------------------------------------------------------- 

241# For saving images in Spider format 

242 

243 

244def makeSpiderHeader(im: Image.Image) -> list[bytes]: 

245 nsam, nrow = im.size 

246 lenbyt = max(1, nsam) * 4 # There are labrec records in the header 

247 labrec = int(1024 / lenbyt) 

248 if 1024 % lenbyt != 0: 

249 labrec += 1 

250 labbyt = labrec * lenbyt 

251 nvalues = int(labbyt / 4) 

252 if nvalues < 23: 

253 return [] 

254 

255 hdr = [0.0] * nvalues 

256 

257 # NB these are Fortran indices 

258 hdr[1] = 1.0 # nslice (=1 for an image) 

259 hdr[2] = float(nrow) # number of rows per slice 

260 hdr[3] = float(nrow) # number of records in the image 

261 hdr[5] = 1.0 # iform for 2D image 

262 hdr[12] = float(nsam) # number of pixels per line 

263 hdr[13] = float(labrec) # number of records in file header 

264 hdr[22] = float(labbyt) # total number of bytes in header 

265 hdr[23] = float(lenbyt) # record length in bytes 

266 

267 # adjust for Fortran indexing 

268 hdr = hdr[1:] 

269 hdr.append(0.0) 

270 # pack binary data into a string 

271 return [struct.pack("f", v) for v in hdr] 

272 

273 

274def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: 

275 if im.mode != "F": 

276 im = im.convert("F") 

277 

278 hdr = makeSpiderHeader(im) 

279 if len(hdr) < 256: 

280 msg = "Error creating Spider header" 

281 raise OSError(msg) 

282 

283 # write the SPIDER header 

284 fp.writelines(hdr) 

285 

286 rawmode = "F;32NF" # 32-bit native floating point 

287 ImageFile._save(im, fp, [ImageFile._Tile("raw", args=rawmode)]) 

288 

289 

290def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: 

291 # get the filename extension and register it with Image 

292 if filename_ext := os.path.splitext(filename)[1]: 

293 ext = filename_ext.decode() if isinstance(filename_ext, bytes) else filename_ext 

294 Image.register_extension(SpiderImageFile.format, ext) 

295 _save(im, fp, filename) 

296 

297 

298# -------------------------------------------------------------------- 

299 

300 

301Image.register_open(SpiderImageFile.format, SpiderImageFile) 

302Image.register_save(SpiderImageFile.format, _save_spider) 

303 

304if __name__ == "__main__": 

305 if len(sys.argv) < 2: 

306 print("Syntax: python3 SpiderImagePlugin.py [infile] [outfile]") 

307 sys.exit() 

308 

309 filename = sys.argv[1] 

310 if not isSpiderImage(filename): 

311 print("input image must be in Spider format") 

312 sys.exit() 

313 

314 with Image.open(filename) as im: 

315 print(f"image: {im}") 

316 print(f"format: {im.format}") 

317 print(f"size: {im.size}") 

318 print(f"mode: {im.mode}") 

319 print("max, min: ", end=" ") 

320 print(im.getextrema()) 

321 

322 if len(sys.argv) > 2: 

323 outfile = sys.argv[2] 

324 

325 # perform some image operation 

326 transposed_im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) 

327 print( 

328 f"saving a flipped version of {os.path.basename(filename)} " 

329 f"as {outfile} " 

330 ) 

331 transposed_im.save(outfile, SpiderImageFile.format)