io_image.py 8.6 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
    """
    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
Unknown's avatar
Unknown committed
91
    prefix : str
Chris Smith's avatar
Chris Smith committed
92
93
94

    Returns
    -------
95

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

    return new_parms

Unknown's avatar
Unknown committed
112

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

Chris Smith's avatar
Chris Smith committed
117
118
119
120
121
122
123
124
125
126
127
128
    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
129
    """
Chris Smith's avatar
Chris Smith committed
130
    get_parms = kwargs.pop('get_parms', True)
Chris Smith's avatar
Chris Smith committed
131
    header = kwargs.pop('header', None)
Chris Smith's avatar
Chris Smith committed
132
133

    file_parms = dict()
Chris Smith's avatar
Chris Smith committed
134
    dm4_file = dm4reader.DM4File.open(file_path)
Chris Smith's avatar
Chris Smith committed
135
136
137
138
139
140
141
142
    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
143
144
145
    for image_dir in image_list:
        image_data_tag = image_dir.named_subdirs['ImageData']
        image_tag = image_data_tag.named_tags['Data']
146

Chris Smith's avatar
Chris Smith committed
147
148
        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])
149

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

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

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

Unknown's avatar
Unknown committed
159

Chris Smith's avatar
Chris Smith committed
160
161
162
163
164
165
166
167
168
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
169

Chris Smith's avatar
Chris Smith committed
170
171
172
173
174
    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
175
                Contents of the directory
Chris Smith's avatar
Chris Smith committed
176

Chris Smith's avatar
Chris Smith committed
177
178
179
180
181
182
183
184
185
    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
186

Chris Smith's avatar
Chris Smith committed
187
188
189
190
191
192
193
194
195
196
197
198
199
200
    """
    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
201
            tag_name = 'Root' + tag_name
Chris Smith's avatar
Chris Smith committed
202
203
204
205
206
207
208
209
210
211
212
213
214
215
        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
216
            tag_name = 'Root' + tag_name
Chris Smith's avatar
Chris Smith committed
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232

        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
233
            dir_name = 'Root' + dir_name
Chris Smith's avatar
Chris Smith committed
234
235
236
237
238
239
240
241
242
        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
243
            dir_name = 'Root' + dir_name
Chris Smith's avatar
Chris Smith committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
        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
259
260
261
262
263
    Returns
    -------
    tag_data : str
        Decoded string from the integer tag

Chris Smith's avatar
Chris Smith committed
264
265
266
267
268
269
270
271
272
273
274
275
276
277
    """
    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
278
279
280
    return tag_data


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

Chris Smith's avatar
Chris Smith committed
284
285
286
287
288
289
290
291
292
293
    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
294

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

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

305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
    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