You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

313 lines
9.0 KiB

4 years ago
  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. # Image plugin for the Spider image format. This format is is used
  18. # by the SPIDER software, in processing image data from electron
  19. # microscopy and tomography.
  20. ##
  21. #
  22. # SpiderImagePlugin.py
  23. #
  24. # The Spider image format is used by SPIDER software, in processing
  25. # image data from electron microscopy and tomography.
  26. #
  27. # Spider home page:
  28. # https://spider.wadsworth.org/spider_doc/spider/docs/spider.html
  29. #
  30. # Details about the Spider image format:
  31. # https://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html
  32. #
  33. from __future__ import print_function
  34. from PIL import Image, ImageFile
  35. import os
  36. import struct
  37. import sys
  38. def isInt(f):
  39. try:
  40. i = int(f)
  41. if f-i == 0:
  42. return 1
  43. else:
  44. return 0
  45. except (ValueError, OverflowError):
  46. return 0
  47. iforms = [1, 3, -11, -12, -21, -22]
  48. # There is no magic number to identify Spider files, so just check a
  49. # series of header locations to see if they have reasonable values.
  50. # Returns no. of bytes in the header, if it is a valid Spider header,
  51. # otherwise returns 0
  52. def isSpiderHeader(t):
  53. h = (99,) + t # add 1 value so can use spider header index start=1
  54. # header values 1,2,5,12,13,22,23 should be integers
  55. for i in [1, 2, 5, 12, 13, 22, 23]:
  56. if not isInt(h[i]):
  57. return 0
  58. # check iform
  59. iform = int(h[5])
  60. if iform not in iforms:
  61. return 0
  62. # check other header values
  63. labrec = int(h[13]) # no. records in file header
  64. labbyt = int(h[22]) # total no. of bytes in header
  65. lenbyt = int(h[23]) # record length in bytes
  66. if labbyt != (labrec * lenbyt):
  67. return 0
  68. # looks like a valid header
  69. return labbyt
  70. def isSpiderImage(filename):
  71. with open(filename, 'rb') as fp:
  72. f = fp.read(92) # read 23 * 4 bytes
  73. t = struct.unpack('>23f', f) # try big-endian first
  74. hdrlen = isSpiderHeader(t)
  75. if hdrlen == 0:
  76. t = struct.unpack('<23f', f) # little-endian
  77. hdrlen = isSpiderHeader(t)
  78. return hdrlen
  79. class SpiderImageFile(ImageFile.ImageFile):
  80. format = "SPIDER"
  81. format_description = "Spider 2D image"
  82. _close_exclusive_fp_after_loading = False
  83. def _open(self):
  84. # check header
  85. n = 27 * 4 # read 27 float values
  86. f = self.fp.read(n)
  87. try:
  88. self.bigendian = 1
  89. t = struct.unpack('>27f', f) # try big-endian first
  90. hdrlen = isSpiderHeader(t)
  91. if hdrlen == 0:
  92. self.bigendian = 0
  93. t = struct.unpack('<27f', f) # little-endian
  94. hdrlen = isSpiderHeader(t)
  95. if hdrlen == 0:
  96. raise SyntaxError("not a valid Spider file")
  97. except struct.error:
  98. raise SyntaxError("not a valid Spider file")
  99. h = (99,) + t # add 1 value : spider header index starts at 1
  100. iform = int(h[5])
  101. if iform != 1:
  102. raise SyntaxError("not a Spider 2D image")
  103. self._size = int(h[12]), int(h[2]) # size in pixels (width, height)
  104. self.istack = int(h[24])
  105. self.imgnumber = int(h[27])
  106. if self.istack == 0 and self.imgnumber == 0:
  107. # stk=0, img=0: a regular 2D image
  108. offset = hdrlen
  109. self._nimages = 1
  110. elif self.istack > 0 and self.imgnumber == 0:
  111. # stk>0, img=0: Opening the stack for the first time
  112. self.imgbytes = int(h[12]) * int(h[2]) * 4
  113. self.hdrlen = hdrlen
  114. self._nimages = int(h[26])
  115. # Point to the first image in the stack
  116. offset = hdrlen * 2
  117. self.imgnumber = 1
  118. elif self.istack == 0 and self.imgnumber > 0:
  119. # stk=0, img>0: an image within the stack
  120. offset = hdrlen + self.stkoffset
  121. self.istack = 2 # So Image knows it's still a stack
  122. else:
  123. raise SyntaxError("inconsistent stack header values")
  124. if self.bigendian:
  125. self.rawmode = "F;32BF"
  126. else:
  127. self.rawmode = "F;32F"
  128. self.mode = "F"
  129. self.tile = [
  130. ("raw", (0, 0) + self.size, offset,
  131. (self.rawmode, 0, 1))]
  132. self.__fp = self.fp # FIXME: hack
  133. @property
  134. def n_frames(self):
  135. return self._nimages
  136. @property
  137. def is_animated(self):
  138. return self._nimages > 1
  139. # 1st image index is zero (although SPIDER imgnumber starts at 1)
  140. def tell(self):
  141. if self.imgnumber < 1:
  142. return 0
  143. else:
  144. return self.imgnumber - 1
  145. def seek(self, frame):
  146. if self.istack == 0:
  147. raise EOFError("attempt to seek in a non-stack file")
  148. if not self._seek_check(frame):
  149. return
  150. self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
  151. self.fp = self.__fp
  152. self.fp.seek(self.stkoffset)
  153. self._open()
  154. # returns a byte image after rescaling to 0..255
  155. def convert2byte(self, depth=255):
  156. (minimum, maximum) = self.getextrema()
  157. m = 1
  158. if maximum != minimum:
  159. m = depth / (maximum-minimum)
  160. b = -m * minimum
  161. return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
  162. # returns a ImageTk.PhotoImage object, after rescaling to 0..255
  163. def tkPhotoImage(self):
  164. from PIL import ImageTk
  165. return ImageTk.PhotoImage(self.convert2byte(), palette=256)
  166. # --------------------------------------------------------------------
  167. # Image series
  168. # given a list of filenames, return a list of images
  169. def loadImageSeries(filelist=None):
  170. """create a list of Image.images for use in montage"""
  171. if filelist is None or len(filelist) < 1:
  172. return
  173. imglist = []
  174. for img in filelist:
  175. if not os.path.exists(img):
  176. print("unable to find %s" % img)
  177. continue
  178. try:
  179. im = Image.open(img).convert2byte()
  180. except:
  181. if not isSpiderImage(img):
  182. print(img + " is not a Spider image file")
  183. continue
  184. im.info['filename'] = img
  185. imglist.append(im)
  186. return imglist
  187. # --------------------------------------------------------------------
  188. # For saving images in Spider format
  189. def makeSpiderHeader(im):
  190. nsam, nrow = im.size
  191. lenbyt = nsam * 4 # There are labrec records in the header
  192. labrec = 1024 / lenbyt
  193. if 1024 % lenbyt != 0:
  194. labrec += 1
  195. labbyt = labrec * lenbyt
  196. hdr = []
  197. nvalues = int(labbyt / 4)
  198. for i in range(nvalues):
  199. hdr.append(0.0)
  200. if len(hdr) < 23:
  201. return []
  202. # NB these are Fortran indices
  203. hdr[1] = 1.0 # nslice (=1 for an image)
  204. hdr[2] = float(nrow) # number of rows per slice
  205. hdr[5] = 1.0 # iform for 2D image
  206. hdr[12] = float(nsam) # number of pixels per line
  207. hdr[13] = float(labrec) # number of records in file header
  208. hdr[22] = float(labbyt) # total number of bytes in header
  209. hdr[23] = float(lenbyt) # record length in bytes
  210. # adjust for Fortran indexing
  211. hdr = hdr[1:]
  212. hdr.append(0.0)
  213. # pack binary data into a string
  214. hdrstr = []
  215. for v in hdr:
  216. hdrstr.append(struct.pack('f', v))
  217. return hdrstr
  218. def _save(im, fp, filename):
  219. if im.mode[0] != "F":
  220. im = im.convert('F')
  221. hdr = makeSpiderHeader(im)
  222. if len(hdr) < 256:
  223. raise IOError("Error creating Spider header")
  224. # write the SPIDER header
  225. fp.writelines(hdr)
  226. rawmode = "F;32NF" # 32-bit native floating point
  227. ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
  228. def _save_spider(im, fp, filename):
  229. # get the filename extension and register it with Image
  230. ext = os.path.splitext(filename)[1]
  231. Image.register_extension(SpiderImageFile.format, ext)
  232. _save(im, fp, filename)
  233. # --------------------------------------------------------------------
  234. Image.register_open(SpiderImageFile.format, SpiderImageFile)
  235. Image.register_save(SpiderImageFile.format, _save_spider)
  236. if __name__ == "__main__":
  237. if len(sys.argv) < 2:
  238. print("Syntax: python SpiderImagePlugin.py [infile] [outfile]")
  239. sys.exit()
  240. filename = sys.argv[1]
  241. if not isSpiderImage(filename):
  242. print("input image must be in Spider format")
  243. sys.exit()
  244. im = Image.open(filename)
  245. print("image: " + str(im))
  246. print("format: " + str(im.format))
  247. print("size: " + str(im.size))
  248. print("mode: " + str(im.mode))
  249. print("max, min: ", end=' ')
  250. print(im.getextrema())
  251. if len(sys.argv) > 2:
  252. outfile = sys.argv[2]
  253. # perform some image operation
  254. im = im.transpose(Image.FLIP_LEFT_RIGHT)
  255. print(
  256. "saving a flipped version of %s as %s " %
  257. (os.path.basename(filename), outfile))
  258. im.save(outfile, SpiderImageFile.format)