io_image.py 8.1 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':
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

81
def unnest_parm_dicts(image_parms, prefix=''):
Chris Smith's avatar
Chris Smith committed
82
83
84
85
86
87
88
89
90
91
92
    """
    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
    -------
93

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

    return new_parms

Chris Smith's avatar
Chris Smith committed
109
def read_dm4(file_path, *args, **kwargs):
Chris Smith's avatar
Chris Smith committed
110
111
    """
    Read dm4 file
112

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

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

Chris Smith's avatar
Chris Smith committed
143
144
        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])
145

Chris Smith's avatar
Chris Smith committed
146
        image_array = np.array(dm4_file.read_tag_data(image_tag), dtype=np.float32)
Chris Smith's avatar
Chris Smith committed
147
        image_array = np.reshape(image_array, (y_dim, x_dim))
148
149

    if get_parms:
Chris Smith's avatar
Chris Smith committed
150
        file_parms = parse_dm4_parms(dm4_file, tags, '')
Chris Smith's avatar
Chris Smith committed
151
        file_parms['Image_Tag'] = header
152

Chris Smith's avatar
Chris Smith committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    return image_array, file_parms

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.
    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
169

Chris Smith's avatar
Chris Smith committed
170
171
172
173
174
175
176
177
178
    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
179

Chris Smith's avatar
Chris Smith committed
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
    """
    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 == '':
            tag_name = 'Root'+tag_name
        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 == '':
            tag_name = 'Root'+tag_name

        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 == '':
            dir_name = 'Root'+dir_name
        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 == '':
            dir_name = 'Root'+dir_name
        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
252
253
254
255
256
    Returns
    -------
    tag_data : str
        Decoded string from the integer tag

Chris Smith's avatar
Chris Smith committed
257
258
259
260
261
262
263
264
265
266
267
268
269
270
    """
    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
271
272
273
    return tag_data


Chris Smith's avatar
Chris Smith committed
274
def read_txt(image_path, header_lines=0, delimiter=None, *args, **kwargs):
Chris Smith's avatar
Chris Smith committed
275
276
    """

Chris Smith's avatar
Chris Smith committed
277
278
279
280
281
282
283
284
285
286
    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
287

Chris Smith's avatar
Chris Smith committed
288
289
290
291
    Returns
    -------
    image : numpy.ndarray
        Image array read from the plaintext file
Chris Smith's avatar
Chris Smith committed
292

Chris Smith's avatar
Chris Smith committed
293
294
    """
    image = np.loadtxt(image_path, *args,
Chris Smith's avatar
Chris Smith committed
295
                       skiprows=header_lines,
Chris Smith's avatar
Chris Smith committed
296
                       delimiter=delimiter, **kwargs)
Chris Smith's avatar
Chris Smith committed
297
298

    return image