Skip to content

Commit 35da2ea

Browse files
committed
working version
1 parent 4851930 commit 35da2ea

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

ncempy/io/emi.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""
2+
This module provides an interface to the FEI / Thermo Fischer ESVision EMI files.
3+
It is only meant to open files that do not have a corresponding .ser file.
4+
5+
Basic EMI reader which can read the first image in most emi files.
6+
Currently readable image data types are uint16, uint32, float32
7+
8+
Only the first image in the file is read. The EMI file format is not well documented,
9+
and this reader is based on reverse engineering of a few sample files.
10+
11+
"""
12+
13+
from pathlib import Path
14+
import numpy as np
15+
16+
class fileEMI:
17+
"""Class to represent simple EMI files.
18+
19+
Attributes
20+
----------
21+
file_name : str
22+
The name of the file
23+
file_path : pathlib.Path
24+
A pathlib.Path object for the open file
25+
fid : file
26+
The file handle to the opened file.
27+
data_type : list of np.dtype
28+
The numpy dtype of the data.
29+
image_size : list of 2-tuples
30+
The size of the images in pixels.
31+
image_locations : list of int
32+
The file bytes locations of the images in the file.
33+
image_name : list of str
34+
The names of the images in the file.
35+
"""
36+
37+
_text_dtype = np.dtype([('mark','<u2'),('unknown','<u2'),('size','<u4')])
38+
39+
def __init__(self, file_name, verbose=False):
40+
41+
self.data_type = []
42+
self.image_size = []
43+
self.image_locations = []
44+
self.image_name = []
45+
self._verbose = verbose
46+
47+
if hasattr(file_name, 'read'):
48+
self.fid = file_name
49+
try:
50+
self.file_name = self.fid.name
51+
except AttributeError:
52+
self.file_name = None
53+
else:
54+
# check filename type. Prefer pathlib.Path
55+
if isinstance(file_name, str):
56+
filename = Path(file_name)
57+
elif isinstance(file_name, Path):
58+
pass
59+
else:
60+
raise TypeError('Filename is supposed to be a string or pathlib.Path')
61+
self.file_path = filename
62+
self.file_name = self.file_path.name
63+
64+
try:
65+
self.fid = open(self.file_path, 'rb')
66+
except IOError:
67+
print('Error reading file: "{}"'.format(self.file_path))
68+
raise
69+
except:
70+
raise
71+
72+
def _read_text(self, fid):
73+
aa = np.fromfile(fid, dtype=self._text_dtype, count=1)
74+
if aa['size'] > 0:
75+
bin = np.fromfile(fid, dtype='<u1', count=aa['size'][0])
76+
text = ''.join([chr(item) for item in bin])
77+
else:
78+
text = ''
79+
return text
80+
81+
def __del__(self):
82+
"""Destructor which also closes the file
83+
84+
"""
85+
if not self.fid.closed:
86+
if self._verbose:
87+
print('Closing input file: {}'.format(self.file_path))
88+
self.fid.close()
89+
90+
def __enter__(self):
91+
"""Implement python's with statement
92+
93+
"""
94+
return self
95+
96+
def __exit__(self, exception_type, exception_value, traceback):
97+
"""Implement python's with statement
98+
and close the file via __del__()
99+
"""
100+
self.__del__()
101+
return None
102+
103+
def parse_file(self):
104+
# Read in the full file
105+
full_file = np.fromfile(self.file_path, dtype='<u1')
106+
# Find bytes that indicate a certain ASCII character: `
107+
obj_loc = np.where(full_file == 96)[0]
108+
109+
with open(self.file_path, 'rb') as f0:
110+
for loc in obj_loc[0:]:
111+
f0.seek(loc, 0)
112+
cur_text = self._read_text(f0)
113+
self.image_name.append(cur_text)
114+
if self._verbose:
115+
print(cur_text)
116+
second_field = np.fromfile(f0, dtype='<u2', count=1)
117+
if (second_field == 112) or (second_field == 17184):
118+
obj_info = np.fromfile(f0, count=2, dtype='<u2')
119+
if obj_info[0] == 1042:
120+
# this is an image. Read header and continue
121+
image_info = np.fromfile(f0, count=10, dtype='<u2')
122+
f0.seek(-8, 1);
123+
self.image_size.append(np.fromfile(f0,count=2,dtype='<u4'))
124+
if image_info[3] == 8710:
125+
self.data_type.append('<u2')
126+
elif image_info[3] == 8714:
127+
self.data_type.append('<u4')
128+
elif image_info[3] == 514:
129+
self.data_type.append('<u4')
130+
elif image_info[3] == 8716:
131+
self.data_type.append('f32')
132+
else:
133+
print('Unknown data type: {}'.format(image_info[3]))
134+
print('for object named: {}'.format(cur_text))
135+
return
136+
self.image_locations.append(self.fid.tell())
137+
break
138+
f0.seek(-2, 1) # roll back the pointer 2 bytes
139+
140+
141+
def getDataset(self, index=0):
142+
"""Read the data from the file
143+
144+
Paremeters
145+
----------
146+
index : int, optional
147+
The index of the image to load.
148+
149+
Returns
150+
-------
151+
: dict
152+
A dictionary containing the data with the key 'data'
153+
154+
"""
155+
self.fid.seek(self.image_locations[index])
156+
image_size = self.image_size[index]
157+
dtype = self.data_type[index]
158+
image = np.fromfile(self.fid, count=image_size[0]*image_size[1], dtype=dtype)
159+
if self._verbose:
160+
print('Read image named: {}'.format(self.image_name[index]))
161+
162+
return {'data': image.reshape(image_size)}
163+
164+
if __name__ == '__main__':
165+
with fileEMI('../data/with sample CL 170.emi', verbose=False) as f0:
166+
f0.parse_file()
167+
data = f0.getDataset(0)
168+
print(f'data shape: {data["data"].shape}')

0 commit comments

Comments
 (0)