io_image.py 8.59 KB
Newer Older
1
2
3
4
5
"""
Created on Nov 8, 2016

@author: Chris Smith -- csmith55@utk.edu
"""
6

7
from __future__ import division, print_function, absolute_import, unicode_literals
8
9

import array
10
import os
11

Chris Smith's avatar
Chris Smith committed
12
import numpy as np
13
from skimage.io import imread
14

15
16
from . import dm4reader
from .dm3_image_utils import parse_dm_header, imagedatadict_to_ndarray
17

18
19
20
21
22
23
24
25
26
27
28
29
30
31

def read_image(image_path, *args, **kwargs):
    """
    Read the image file at `image_path` into a numpy array

    Parameters
    ----------
    image_path : str
        Path to the image file

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
32
33
34
    image_parms : dict
        Dictionary containing image parameters.  If image type does not have
        parameters then an empty dictionary is returned.
Chris Smith's avatar
Chris Smith committed
35

36
    """
Chris Smith's avatar
Chris Smith committed
37
38
    ext = os.path.splitext(image_path)[1]
    if ext == '.dm3':
Unknown's avatar
Unknown committed
39
        kwargs.pop('as_grey', None)
Chris Smith's avatar
Chris Smith committed
40
        return read_dm3(image_path, *args, **kwargs)
Chris Smith's avatar
Chris Smith committed
41
    elif ext == '.dm4':
42
        kwargs.pop('as_grey', None)
Chris Smith's avatar
Chris Smith committed
43
        return read_dm4(image_path, *args, **kwargs)
Chris Smith's avatar
Chris Smith committed
44
45
    elif ext == '.txt':
        return read_txt(image_path, *args, **kwargs), dict()
46
    else:
47
48
        # Set the as_grey argument to True is not already provided.
        kwargs['as_grey'] = (kwargs.pop('as_grey', True))
Chris Smith's avatar
Chris Smith committed
49
        return imread(image_path, *args, **kwargs), dict()
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65


def read_dm3(image_path, get_parms=True):
    """
    Read an image from a dm3 file into a numpy array

    image_path : str
        Path to the image file
    get_parms : Boolean, optional
        Should the parameters from the dm3 file be returned
        Default True

    Returns
    -------
    image : numpy.ndarray
        Array containing the image from the file `image_path`
Chris Smith's avatar
Chris Smith committed
66

67
68
69
70
71
72
73
    """
    image_file = open(image_path, 'rb')
    dmtag = parse_dm_header(image_file)
    img_index = -1
    image = imagedatadict_to_ndarray(dmtag['ImageList'][img_index]['ImageData'])
    image_parms = dmtag['ImageList'][img_index]['ImageTags']

Chris Smith's avatar
Chris Smith committed
74
    if get_parms:
75
        image_parms = unnest_parm_dicts(image_parms)
Chris Smith's avatar
Chris Smith committed
76
77
78
79
80
    else:
        image_parms = dict()

    return image, image_parms

Unknown's avatar
Unknown committed
81

82
def unnest_parm_dicts(image_parms, prefix=''):
Chris Smith's avatar
Chris Smith committed
83
84
85
86
87
88
89
90
91
92
93
    """
    Parses the nested image parameter dictionary and converts it to a single
    level dictionary, prepending the name of inner dictionaries to their
    keys to denote level.

    Parameters
    ----------
    image_parms : dict

    Returns
    -------
94

Chris Smith's avatar
Chris Smith committed
95
96
    """
    new_parms = dict()
Somnath, Suhas's avatar
Somnath, Suhas committed
97
98
    for name in image_parms.keys():
        val = image_parms[name]
Chris Smith's avatar
Chris Smith committed
99
        # print 'name',name,'val',val
Unknown's avatar
Unknown committed
100
        name = '-'.join([prefix] + name.split()).strip('-')
Chris Smith's avatar
Chris Smith committed
101
        if isinstance(val, dict):
102
            new_parms.update(unnest_parm_dicts(val, name))
Chris Smith's avatar
Chris Smith committed
103
104
        elif isinstance(val, list) and isinstance(val[0], dict):
            for thing in val:
105
                new_parms.update(unnest_parm_dicts(thing, name))
Chris Smith's avatar
Chris Smith committed
106
107
108
109
110
        else:
            new_parms[name] = try_tag_to_string(val)

    return new_parms

Unknown's avatar
Unknown committed
111

Chris Smith's avatar
Chris Smith committed
112
def read_dm4(file_path, *args, **kwargs):
Chris Smith's avatar
Chris Smith committed
113
114
    """
    Read dm4 file
115

Chris Smith's avatar
Chris Smith committed
116
117
118
119
120
121
122
123
124
125
126
127
    Parameters
    ----------
    file_path : str
        Path to the file to be read

    Returns
    -------
    image_array : numpy.ndarray
        Image data from the file located at `file_path`
    file_parms : dict
        Dictionary of parameters read from the dm4 file

Chris Smith's avatar
Chris Smith committed
128
    """
Chris Smith's avatar
Chris Smith committed
129
    get_parms = kwargs.pop('get_parms', True)
Chris Smith's avatar
Chris Smith committed
130
    header = kwargs.pop('header', None)
Chris Smith's avatar
Chris Smith committed
131
132

    file_parms = dict()
Chris Smith's avatar
Chris Smith committed
133
    dm4_file = dm4reader.DM4File.open(file_path)
Chris Smith's avatar
Chris Smith committed
134
135
136
137
138
139
140
141
    if header is None:
        tags = dm4_file.read_directory()
        header = tags.named_subdirs['ImageList'].dm4_tag
        image_list = tags.named_subdirs['ImageList'].unnamed_subdirs
    else:
        dm4_file.hfile.seek(header.offset)
        image_list = dm4_file.read_directory(header)

Chris Smith's avatar
Chris Smith committed
142
143
144
    for image_dir in image_list:
        image_data_tag = image_dir.named_subdirs['ImageData']
        image_tag = image_data_tag.named_tags['Data']
145

Chris Smith's avatar
Chris Smith committed
146
147
        x_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[0])
        y_dim = dm4_file.read_tag_data(image_data_tag.named_subdirs['Dimensions'].unnamed_tags[1])
148

Chris Smith's avatar
Chris Smith committed
149
        image_array = np.array(dm4_file.read_tag_data(image_tag), dtype=np.float32)
Chris Smith's avatar
Chris Smith committed
150
        image_array = np.reshape(image_array, (y_dim, x_dim))
151
152

    if get_parms:
Chris Smith's avatar
Chris Smith committed
153
        file_parms = parse_dm4_parms(dm4_file, tags, '')
Chris Smith's avatar
Chris Smith committed
154
        file_parms['Image_Tag'] = header
155

Chris Smith's avatar
Chris Smith committed
156
157
    return image_array, file_parms

Unknown's avatar
Unknown committed
158

Chris Smith's avatar
Chris Smith committed
159
160
161
162
163
164
165
166
167
def parse_dm4_parms(dm4_file, tag_dir, base_name=''):
    """
    Recursive function to trace the dictionary tree of the Image Data
    and build a single dictionary of all parameters

    Parameters
    ----------
    dm4_file : DM4File
        File object of the dm4 file to be parsed.
Chris Smith's avatar
Chris Smith committed
168

Chris Smith's avatar
Chris Smith committed
169
170
171
172
173
    tag_dir : dict
        Dictionary to be traced.  Has the following attributes:
            tag_dir.name : str
                Name of the directory
            tag_dir.dm4_tag : str
Chris Smith's avatar
Chris Smith committed
174
                Contents of the directory
Chris Smith's avatar
Chris Smith committed
175

Chris Smith's avatar
Chris Smith committed
176
177
178
179
180
181
182
183
184
    base_name : str
        Base name of parameters.  Tag and subdirectory names will be appended
        for named tags and subdirectories.  Unnamed ones will recieve a number.
        Default ''.  'Root' is automatically prepended to the name.

    Returns
    -------
    parm_dict : dict()
        Dictionary containing the name:value pairs of all parameters `dm4_file`
Chris Smith's avatar
Chris Smith committed
185

Chris Smith's avatar
Chris Smith committed
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    """
    parm_dict = dict()

    '''
    Loop over named tags
    '''
    for name in tag_dir.named_tags.keys():
        '''
        Skip Data tags.  These will be handled elseware.
        '''
        if name == 'Data':
            continue
        tag_name = '_'.join([base_name, name.replace(' ', '_')])
        if base_name == '':
Unknown's avatar
Unknown committed
200
            tag_name = 'Root' + tag_name
Chris Smith's avatar
Chris Smith committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
        tag_data = dm4_file.read_tag_data(tag_dir.named_tags[name])

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over unnamed tags
    '''
    for itag, tag in enumerate(tag_dir.unnamed_tags):
        tag_name = '_'.join([base_name, 'Tag_{:03d}'.format(itag)])
        if base_name == '':
Unknown's avatar
Unknown committed
215
            tag_name = 'Root' + tag_name
Chris Smith's avatar
Chris Smith committed
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231

        tag_data = dm4_file.read_tag_data(tag)

        '''
        See if we can convert the array into a string
        '''
        tag_data = try_tag_to_string(tag_data)
        parm_dict[tag_name] = tag_data

    '''
    Loop over named subdirectories
    '''
    for name in tag_dir.named_subdirs.keys():
        dir_name = '_'.join([base_name, name.replace(' ', '_')])
        sub_dir = tag_dir.named_subdirs[name]
        if base_name == '':
Unknown's avatar
Unknown committed
232
            dir_name = 'Root' + dir_name
Chris Smith's avatar
Chris Smith committed
233
234
235
236
237
238
239
240
241
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    '''
    Loop over unnamed subdirectories
    '''
    for idir, sub_dir in enumerate(tag_dir.unnamed_subdirs):
        dir_name = '_'.join([base_name, 'SubDir_{:03d}'.format(idir)])
        if base_name == '':
Unknown's avatar
Unknown committed
242
            dir_name = 'Root' + dir_name
Chris Smith's avatar
Chris Smith committed
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
        sub_parms = parse_dm4_parms(dm4_file, sub_dir, dir_name)
        parm_dict.update(sub_parms)

    return parm_dict


def try_tag_to_string(tag_data):
    """
    Attempt to convert array of integers into a string

    Parameters
    ----------
    tag_data : array.array
        Array of 16-bit integers

Chris Smith's avatar
Chris Smith committed
258
259
260
261
262
    Returns
    -------
    tag_data : str
        Decoded string from the integer tag

Chris Smith's avatar
Chris Smith committed
263
264
265
266
267
268
269
270
271
272
273
274
275
276
    """
    if not isinstance(tag_data, array.array):
        return tag_data

    if tag_data.typecode == 'H':
        try:
            tag_data = str(tag_data.tostring().decode('utf-16'))
        except UnicodeDecodeError:
            pass
        except UnicodeEncodeError:
            pass
        except:
            raise

Chris Smith's avatar
Chris Smith committed
277
278
279
    return tag_data


Chris Smith's avatar
Chris Smith committed
280
def read_txt(image_path, header_lines=0, delimiter=None, *args, **kwargs):
Chris Smith's avatar
Chris Smith committed
281
282
    """

Chris Smith's avatar
Chris Smith committed
283
284
285
286
287
288
289
290
291
292
    Parameters
    ----------
    image_path : str
        Path to the image file
    header_lines : int
        Number of lines to skip as the header
    delimiter : str
        Separator between the columns of data
    args
    kwargs
Chris Smith's avatar
Chris Smith committed
293

Chris Smith's avatar
Chris Smith committed
294
295
296
297
    Returns
    -------
    image : numpy.ndarray
        Image array read from the plaintext file
Chris Smith's avatar
Chris Smith committed
298

Chris Smith's avatar
Chris Smith committed
299
300
    """
    image = np.loadtxt(image_path, *args,
Chris Smith's avatar
Chris Smith committed
301
                       skiprows=header_lines,
Chris Smith's avatar
Chris Smith committed
302
                       delimiter=delimiter, **kwargs)
Chris Smith's avatar
Chris Smith committed
303

304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
    return image


def no_bin(image, *args, **kwargs):
    """
    Does absolutely nothing to the image.  Exists so that we can have
    a bin function to call whether we actually rebin the image or not.

    Parameters
    ----------
    image : ndarray
        Image
    args:
        Argument list
    kwargs:
        Keyword argument list

    Returns
    -------
    image : ndarray
        The input image
    """
    return image