Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pillow-10.4.0-py3.8-linux-x86_64.egg/PIL/SpiderImagePlugin.py: 20%

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

198 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.wadsworth.org/spider_doc/spider/docs/spider.html 

31# 

32# Details about the Spider image format: 

33# https://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html 

34# 

35from __future__ import annotations 

36 

37import os 

38import struct 

39import sys 

40from typing import IO, TYPE_CHECKING, Any, Tuple, cast 

41 

42from . import Image, ImageFile 

43 

44 

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

46 try: 

47 i = int(f) 

48 if f - i == 0: 

49 return 1 

50 else: 

51 return 0 

52 except (ValueError, OverflowError): 

53 return 0 

54 

55 

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

57 

58 

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

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

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

62# otherwise returns 0 

63 

64 

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

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

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

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

69 if not isInt(h[i]): 

70 return 0 

71 # check iform 

72 iform = int(h[5]) 

73 if iform not in iforms: 

74 return 0 

75 # check other header values 

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

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

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

79 if labbyt != (labrec * lenbyt): 

80 return 0 

81 # looks like a valid header 

82 return labbyt 

83 

84 

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

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

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

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

89 hdrlen = isSpiderHeader(t) 

90 if hdrlen == 0: 

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

92 hdrlen = isSpiderHeader(t) 

93 return hdrlen 

94 

95 

96class SpiderImageFile(ImageFile.ImageFile): 

97 format = "SPIDER" 

98 format_description = "Spider 2D image" 

99 _close_exclusive_fp_after_loading = False 

100 

101 def _open(self) -> None: 

102 # check header 

103 n = 27 * 4 # read 27 float values 

104 f = self.fp.read(n) 

105 

106 try: 

107 self.bigendian = 1 

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

109 hdrlen = isSpiderHeader(t) 

110 if hdrlen == 0: 

111 self.bigendian = 0 

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

113 hdrlen = isSpiderHeader(t) 

114 if hdrlen == 0: 

115 msg = "not a valid Spider file" 

116 raise SyntaxError(msg) 

117 except struct.error as e: 

118 msg = "not a valid Spider file" 

119 raise SyntaxError(msg) from e 

120 

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

122 iform = int(h[5]) 

123 if iform != 1: 

124 msg = "not a Spider 2D image" 

125 raise SyntaxError(msg) 

126 

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

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

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

130 

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

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

133 offset = hdrlen 

134 self._nimages = 1 

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

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

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

138 self.hdrlen = hdrlen 

139 self._nimages = int(h[26]) 

140 # Point to the first image in the stack 

141 offset = hdrlen * 2 

142 self.imgnumber = 1 

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

144 # stk=0, img>0: an image within the stack 

145 offset = hdrlen + self.stkoffset 

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

147 else: 

148 msg = "inconsistent stack header values" 

149 raise SyntaxError(msg) 

150 

151 if self.bigendian: 

152 self.rawmode = "F;32BF" 

153 else: 

154 self.rawmode = "F;32F" 

155 self._mode = "F" 

156 

157 self.tile = [("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))] 

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

159 

160 @property 

161 def n_frames(self) -> int: 

162 return self._nimages 

163 

164 @property 

165 def is_animated(self) -> bool: 

166 return self._nimages > 1 

167 

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

169 def tell(self) -> int: 

170 if self.imgnumber < 1: 

171 return 0 

172 else: 

173 return self.imgnumber - 1 

174 

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

176 if self.istack == 0: 

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

178 raise EOFError(msg) 

179 if not self._seek_check(frame): 

180 return 

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

182 self.fp = self._fp 

183 self.fp.seek(self.stkoffset) 

184 self._open() 

185 

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

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

188 extrema = self.getextrema() 

189 assert isinstance(extrema[0], float) 

190 minimum, maximum = cast(Tuple[float, float], extrema) 

191 m: float = 1 

192 if maximum != minimum: 

193 m = depth / (maximum - minimum) 

194 b = -m * minimum 

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

196 

197 if TYPE_CHECKING: 

198 from . import ImageTk 

199 

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

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

202 from . import ImageTk 

203 

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

205 

206 

207# -------------------------------------------------------------------- 

208# Image series 

209 

210 

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

212def loadImageSeries(filelist: list[str] | None = None) -> list[SpiderImageFile] | None: 

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

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

215 return None 

216 

217 imglist = [] 

218 for img in filelist: 

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

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

221 continue 

222 try: 

223 with Image.open(img) as im: 

224 im = im.convert2byte() 

225 except Exception: 

226 if not isSpiderImage(img): 

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

228 continue 

229 im.info["filename"] = img 

230 imglist.append(im) 

231 return imglist 

232 

233 

234# -------------------------------------------------------------------- 

235# For saving images in Spider format 

236 

237 

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

239 nsam, nrow = im.size 

240 lenbyt = nsam * 4 # There are labrec records in the header 

241 labrec = int(1024 / lenbyt) 

242 if 1024 % lenbyt != 0: 

243 labrec += 1 

244 labbyt = labrec * lenbyt 

245 nvalues = int(labbyt / 4) 

246 if nvalues < 23: 

247 return [] 

248 

249 hdr = [0.0] * nvalues 

250 

251 # NB these are Fortran indices 

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

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

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

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

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

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

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

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

260 

261 # adjust for Fortran indexing 

262 hdr = hdr[1:] 

263 hdr.append(0.0) 

264 # pack binary data into a string 

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

266 

267 

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

269 if im.mode[0] != "F": 

270 im = im.convert("F") 

271 

272 hdr = makeSpiderHeader(im) 

273 if len(hdr) < 256: 

274 msg = "Error creating Spider header" 

275 raise OSError(msg) 

276 

277 # write the SPIDER header 

278 fp.writelines(hdr) 

279 

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

281 ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) 

282 

283 

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

285 # get the filename extension and register it with Image 

286 filename_ext = os.path.splitext(filename)[1] 

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

288 Image.register_extension(SpiderImageFile.format, ext) 

289 _save(im, fp, filename) 

290 

291 

292# -------------------------------------------------------------------- 

293 

294 

295Image.register_open(SpiderImageFile.format, SpiderImageFile) 

296Image.register_save(SpiderImageFile.format, _save_spider) 

297 

298if __name__ == "__main__": 

299 if len(sys.argv) < 2: 

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

301 sys.exit() 

302 

303 filename = sys.argv[1] 

304 if not isSpiderImage(filename): 

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

306 sys.exit() 

307 

308 with Image.open(filename) as im: 

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

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

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

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

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

314 print(im.getextrema()) 

315 

316 if len(sys.argv) > 2: 

317 outfile = sys.argv[2] 

318 

319 # perform some image operation 

320 im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) 

321 print( 

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

323 f"as {outfile} " 

324 ) 

325 im.save(outfile, SpiderImageFile.format)