Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/SpiderImagePlugin.py: 12%

187 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

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# 

35import os 

36import struct 

37import sys 

38 

39from PIL import Image, ImageFile 

40 

41 

42def isInt(f): 

43 try: 

44 i = int(f) 

45 if f - i == 0: 

46 return 1 

47 else: 

48 return 0 

49 except (ValueError, OverflowError): 

50 return 0 

51 

52 

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

54 

55 

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

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

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

59# otherwise returns 0 

60 

61 

62def isSpiderHeader(t): 

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

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

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

66 if not isInt(h[i]): 

67 return 0 

68 # check iform 

69 iform = int(h[5]) 

70 if iform not in iforms: 

71 return 0 

72 # check other header values 

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

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

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

76 if labbyt != (labrec * lenbyt): 

77 return 0 

78 # looks like a valid header 

79 return labbyt 

80 

81 

82def isSpiderImage(filename): 

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

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

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

86 hdrlen = isSpiderHeader(t) 

87 if hdrlen == 0: 

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

89 hdrlen = isSpiderHeader(t) 

90 return hdrlen 

91 

92 

93class SpiderImageFile(ImageFile.ImageFile): 

94 

95 format = "SPIDER" 

96 format_description = "Spider 2D image" 

97 _close_exclusive_fp_after_loading = False 

98 

99 def _open(self): 

100 # check header 

101 n = 27 * 4 # read 27 float values 

102 f = self.fp.read(n) 

103 

104 try: 

105 self.bigendian = 1 

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

107 hdrlen = isSpiderHeader(t) 

108 if hdrlen == 0: 

109 self.bigendian = 0 

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

111 hdrlen = isSpiderHeader(t) 

112 if hdrlen == 0: 

113 raise SyntaxError("not a valid Spider file") 

114 except struct.error as e: 

115 raise SyntaxError("not a valid Spider file") from e 

116 

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

118 iform = int(h[5]) 

119 if iform != 1: 

120 raise SyntaxError("not a Spider 2D image") 

121 

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

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

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

125 

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

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

128 offset = hdrlen 

129 self._nimages = 1 

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

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

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

133 self.hdrlen = hdrlen 

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

135 # Point to the first image in the stack 

136 offset = hdrlen * 2 

137 self.imgnumber = 1 

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

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

140 offset = hdrlen + self.stkoffset 

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

142 else: 

143 raise SyntaxError("inconsistent stack header values") 

144 

145 if self.bigendian: 

146 self.rawmode = "F;32BF" 

147 else: 

148 self.rawmode = "F;32F" 

149 self.mode = "F" 

150 

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

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

153 

154 @property 

155 def n_frames(self): 

156 return self._nimages 

157 

158 @property 

159 def is_animated(self): 

160 return self._nimages > 1 

161 

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

163 def tell(self): 

164 if self.imgnumber < 1: 

165 return 0 

166 else: 

167 return self.imgnumber - 1 

168 

169 def seek(self, frame): 

170 if self.istack == 0: 

171 raise EOFError("attempt to seek in a non-stack file") 

172 if not self._seek_check(frame): 

173 return 

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

175 self.fp = self._fp 

176 self.fp.seek(self.stkoffset) 

177 self._open() 

178 

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

180 def convert2byte(self, depth=255): 

181 (minimum, maximum) = self.getextrema() 

182 m = 1 

183 if maximum != minimum: 

184 m = depth / (maximum - minimum) 

185 b = -m * minimum 

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

187 

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

189 def tkPhotoImage(self): 

190 from PIL import ImageTk 

191 

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

193 

194 

195# -------------------------------------------------------------------- 

196# Image series 

197 

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

199def loadImageSeries(filelist=None): 

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

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

202 return 

203 

204 imglist = [] 

205 for img in filelist: 

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

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

208 continue 

209 try: 

210 with Image.open(img) as im: 

211 im = im.convert2byte() 

212 except Exception: 

213 if not isSpiderImage(img): 

214 print(img + " is not a Spider image file") 

215 continue 

216 im.info["filename"] = img 

217 imglist.append(im) 

218 return imglist 

219 

220 

221# -------------------------------------------------------------------- 

222# For saving images in Spider format 

223 

224 

225def makeSpiderHeader(im): 

226 nsam, nrow = im.size 

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

228 labrec = int(1024 / lenbyt) 

229 if 1024 % lenbyt != 0: 

230 labrec += 1 

231 labbyt = labrec * lenbyt 

232 nvalues = int(labbyt / 4) 

233 if nvalues < 23: 

234 return [] 

235 

236 hdr = [] 

237 for i in range(nvalues): 

238 hdr.append(0.0) 

239 

240 # NB these are Fortran indices 

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

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

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

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

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

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

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

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

249 

250 # adjust for Fortran indexing 

251 hdr = hdr[1:] 

252 hdr.append(0.0) 

253 # pack binary data into a string 

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

255 

256 

257def _save(im, fp, filename): 

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

259 im = im.convert("F") 

260 

261 hdr = makeSpiderHeader(im) 

262 if len(hdr) < 256: 

263 raise OSError("Error creating Spider header") 

264 

265 # write the SPIDER header 

266 fp.writelines(hdr) 

267 

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

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

270 

271 

272def _save_spider(im, fp, filename): 

273 # get the filename extension and register it with Image 

274 ext = os.path.splitext(filename)[1] 

275 Image.register_extension(SpiderImageFile.format, ext) 

276 _save(im, fp, filename) 

277 

278 

279# -------------------------------------------------------------------- 

280 

281 

282Image.register_open(SpiderImageFile.format, SpiderImageFile) 

283Image.register_save(SpiderImageFile.format, _save_spider) 

284 

285if __name__ == "__main__": 285 ↛ 287line 285 didn't jump to line 287, because the condition on line 285 was never true

286 

287 if len(sys.argv) < 2: 

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

289 sys.exit() 

290 

291 filename = sys.argv[1] 

292 if not isSpiderImage(filename): 

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

294 sys.exit() 

295 

296 with Image.open(filename) as im: 

297 print("image: " + str(im)) 

298 print("format: " + str(im.format)) 

299 print("size: " + str(im.size)) 

300 print("mode: " + str(im.mode)) 

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

302 print(im.getextrema()) 

303 

304 if len(sys.argv) > 2: 

305 outfile = sys.argv[2] 

306 

307 # perform some image operation 

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

309 print( 

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

311 f"as {outfile} " 

312 ) 

313 im.save(outfile, SpiderImageFile.format)