biomedisa 2024.5.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. biomedisa/__init__.py +53 -0
  2. biomedisa/__main__.py +18 -0
  3. biomedisa/biomedisa_features/DataGenerator.py +299 -0
  4. biomedisa/biomedisa_features/DataGeneratorCrop.py +121 -0
  5. biomedisa/biomedisa_features/PredictDataGenerator.py +87 -0
  6. biomedisa/biomedisa_features/PredictDataGeneratorCrop.py +74 -0
  7. biomedisa/biomedisa_features/__init__.py +0 -0
  8. biomedisa/biomedisa_features/active_contour.py +434 -0
  9. biomedisa/biomedisa_features/amira_to_np/__init__.py +0 -0
  10. biomedisa/biomedisa_features/amira_to_np/amira_data_stream.py +980 -0
  11. biomedisa/biomedisa_features/amira_to_np/amira_grammar.py +369 -0
  12. biomedisa/biomedisa_features/amira_to_np/amira_header.py +290 -0
  13. biomedisa/biomedisa_features/amira_to_np/amira_helper.py +72 -0
  14. biomedisa/biomedisa_features/assd.py +167 -0
  15. biomedisa/biomedisa_features/biomedisa_helper.py +801 -0
  16. biomedisa/biomedisa_features/create_slices.py +286 -0
  17. biomedisa/biomedisa_features/crop_helper.py +586 -0
  18. biomedisa/biomedisa_features/curvop_numba.py +149 -0
  19. biomedisa/biomedisa_features/django_env.py +172 -0
  20. biomedisa/biomedisa_features/keras_helper.py +1219 -0
  21. biomedisa/biomedisa_features/nc_reader.py +179 -0
  22. biomedisa/biomedisa_features/pid.py +52 -0
  23. biomedisa/biomedisa_features/process_image.py +253 -0
  24. biomedisa/biomedisa_features/pycuda_test.py +84 -0
  25. biomedisa/biomedisa_features/random_walk/__init__.py +0 -0
  26. biomedisa/biomedisa_features/random_walk/gpu_kernels.py +183 -0
  27. biomedisa/biomedisa_features/random_walk/pycuda_large.py +826 -0
  28. biomedisa/biomedisa_features/random_walk/pycuda_large_allx.py +806 -0
  29. biomedisa/biomedisa_features/random_walk/pycuda_small.py +414 -0
  30. biomedisa/biomedisa_features/random_walk/pycuda_small_allx.py +493 -0
  31. biomedisa/biomedisa_features/random_walk/pyopencl_large.py +760 -0
  32. biomedisa/biomedisa_features/random_walk/pyopencl_small.py +441 -0
  33. biomedisa/biomedisa_features/random_walk/rw_large.py +390 -0
  34. biomedisa/biomedisa_features/random_walk/rw_small.py +310 -0
  35. biomedisa/biomedisa_features/remove_outlier.py +399 -0
  36. biomedisa/biomedisa_features/split_volume.py +274 -0
  37. biomedisa/deeplearning.py +519 -0
  38. biomedisa/interpolation.py +371 -0
  39. biomedisa/mesh.py +406 -0
  40. biomedisa-2024.5.14.dist-info/LICENSE +191 -0
  41. biomedisa-2024.5.14.dist-info/METADATA +306 -0
  42. biomedisa-2024.5.14.dist-info/RECORD +44 -0
  43. biomedisa-2024.5.14.dist-info/WHEEL +5 -0
  44. biomedisa-2024.5.14.dist-info/top_level.txt +1 -0
@@ -0,0 +1,980 @@
1
+ # -*- coding: utf-8 -*-
2
+ # data_stream
3
+
4
+ from collections import UserList
5
+ import numpy
6
+ import re
7
+ import struct
8
+ import time
9
+
10
+ from skimage.measure._find_contours import find_contours
11
+ from numba import jit
12
+
13
+ from .amira_header import AmiraHeader
14
+
15
+ # type of data to find in the stream
16
+ FIND = {
17
+ 'decimal': '\d', # [0-9]
18
+ 'alphanum_': '\w', # [a-aA-Z0-9_]
19
+ }
20
+
21
+ def byterle_decoder(input_data, output_size):
22
+ """Python drop-in replacement for compiled equivalent
23
+
24
+ :param int output_size: the number of items when ``data`` is uncompressed
25
+ :param str data: a raw stream of data to be unpacked
26
+ :return numpy.array output: an array of ``numpy.uint8``
27
+ """
28
+
29
+ # input_data = struct.unpack('<{}B'.format(len(data)), data)
30
+ # input_data = numpy.ndarray((len(data),), '<B', data)
31
+ output = numpy.zeros(output_size, dtype=numpy.uint8)
32
+ i = 0
33
+ count = True
34
+ repeat = False
35
+ no = None
36
+ j = 0
37
+ len_data = len(input_data)
38
+ while i < len_data:
39
+ if count:
40
+ no = input_data[i]
41
+ if no > 127:
42
+ no &= 0x7f # 2's complement
43
+ count = False
44
+ repeat = True
45
+ i += 1
46
+ continue
47
+ else:
48
+ i += 1
49
+ count = False
50
+ repeat = False
51
+ continue
52
+ elif not count:
53
+ if repeat:
54
+ value = input_data[i:i + no]
55
+ repeat = False
56
+ count = True
57
+ output[j:j+no] = numpy.array(value)
58
+ i += no
59
+ j += no
60
+ continue
61
+ elif not repeat:
62
+ value = input_data[i]
63
+ output[j:j+no] = value
64
+ i += 1
65
+ j += no
66
+ count = True
67
+ repeat = False
68
+ continue
69
+
70
+ assert j == output_size
71
+ return output
72
+
73
+ def byterle_encoder(data):
74
+ if len(data) == 0:
75
+ return ""
76
+
77
+ base = 128
78
+ max = 127
79
+
80
+ output = []
81
+ no_rep = []
82
+ i = 0
83
+ prev = None
84
+ cur = None
85
+ cnt = 0
86
+ len_data = len(data)
87
+ while i < len_data:
88
+ cur = data[i]
89
+ if prev == cur: # repeat
90
+ if len(no_rep) > 0:
91
+ output.append(base+len(no_rep))
92
+ output.extend(no_rep)
93
+ no_rep = []
94
+ cnt = 1 # including prev
95
+ while i < len_data:
96
+ cur = data[i]
97
+ if prev == cur:
98
+ cnt += 1
99
+ if cnt == max:
100
+ output.append(cnt)
101
+ output.append(cur)
102
+ prev = None
103
+ cur = None
104
+ cnt = 0
105
+ i += 1
106
+ break
107
+ else: # end of repeat
108
+ output.append(cnt)
109
+ output.append(prev)
110
+ prev = None
111
+ cnt = 0
112
+ break
113
+ prev = cur
114
+ i += 1
115
+ if cnt > 0: # end of file
116
+ output.append(cnt)
117
+ output.append(cur)
118
+ prev = None
119
+ cur = None
120
+ cnt = 0
121
+ else: # no repeat
122
+ if prev is not None:
123
+ no_rep.append(prev)
124
+ if len(no_rep) == max:
125
+ output.append(base+len(no_rep))
126
+ output.extend(no_rep)
127
+ no_rep = []
128
+ prev = cur
129
+ i += 1
130
+
131
+ if cur is not None:
132
+ no_rep.append(cur)
133
+
134
+ if len(no_rep) > 0:
135
+ output.append(base+len(no_rep))
136
+ output.extend(no_rep)
137
+ no_rep = []
138
+
139
+ final_result = bytearray(output)
140
+ return final_result
141
+
142
+ @jit(nopython=True)
143
+ def numba_array_create(dtype, base_size=1073741824):
144
+ assert(base_size > 0)
145
+ new_arr = numpy.zeros(base_size, dtype=dtype)
146
+ return new_arr
147
+
148
+ @jit(nopython=True)
149
+ def numba_array_copy(arr):
150
+ assert(arr.size > 0)
151
+ new_arr = numpy.zeros(arr.size, dtype=arr.dtype)
152
+ for i in range(arr.size):
153
+ new_arr[i] = arr[i]
154
+ return new_arr, new_arr.size
155
+
156
+ @jit(nopython=True)
157
+ def numba_array_resize(arr, base_size=1073741824):
158
+ assert(base_size > 0)
159
+ new_arr = numpy.zeros(arr.size+base_size, dtype=arr.dtype)
160
+ new_arr[:arr.size] = arr
161
+ return new_arr
162
+
163
+ @jit(nopython=True)
164
+ def numba_array_add(arr, idx, elem, base_size=1073741824):
165
+ assert(base_size > 0)
166
+ if idx >= arr.size:
167
+ arr = numba_array_resize(arr, base_size)
168
+ arr[idx] = elem
169
+ idx += 1
170
+ return arr, idx
171
+
172
+ @jit(nopython=True)
173
+ def numba_array_extend(arr, idx, elems, elems_len, base_size=1073741824):
174
+ assert(base_size > 0)
175
+ assert(elems.size <= base_size)
176
+ if idx+elems.size >= arr.size:
177
+ arr = numba_array_resize(arr, base_size)
178
+ for i in range(elems_len):
179
+ arr[idx+i] = elems[i]
180
+ idx += elems_len
181
+ return arr, idx
182
+
183
+ @jit(nopython=True)
184
+ def numba_array_extend_mult(arr, idx, elem, no_elem, base_size=1073741824):
185
+ assert(base_size > 0)
186
+ assert(no_elem <= base_size)
187
+ if idx+no_elem >= arr.size:
188
+ arr = numba_array_resize(arr, base_size)
189
+ arr[idx:idx+no_elem] = elem
190
+ idx += no_elem
191
+ return arr, idx
192
+
193
+ @jit(nopython=True)
194
+ def byterle_decoder_njit(data, len_data, output_size):
195
+ """Python drop-in replacement for compiled equivalent
196
+
197
+ :param int output_size: the number of items when ``data`` is uncompressed
198
+ :param str data: a raw stream of data to be unpacked
199
+ :return numpy.array output: an array of ``numpy.uint8``
200
+ """
201
+
202
+ output = numba_array_create(dtype=numpy.uint8)
203
+ output_idx = 0
204
+
205
+ count = True
206
+ no_repeat = False
207
+ no = None
208
+ i = 0
209
+ while i < len_data:
210
+ if count:
211
+ no = data[i]
212
+ if no > 127:
213
+ no &= 0x7f # 2's complement
214
+ no_repeat = True
215
+ else:
216
+ no_repeat = False
217
+
218
+ i += 1
219
+ count = False
220
+ continue
221
+ elif not count:
222
+ if no_repeat:
223
+ no_rep, no_rep_idx = numba_array_copy(data[i:i + no])
224
+ output, output_idx = numba_array_extend(output, output_idx, no_rep, no_rep_idx)
225
+ i += no
226
+ elif not no_repeat:
227
+ elem = data[i]
228
+ output, output_idx = numba_array_extend_mult(output, output_idx, elem, no)
229
+ i += 1
230
+
231
+ count = True
232
+ no_repeat = False
233
+ continue
234
+
235
+ output = output[:output_idx]
236
+
237
+ assert output_idx == output_size
238
+ return output
239
+
240
+ @jit(nopython=True)
241
+ def byterle_encoder_njit(data, len_data):
242
+
243
+ BASE = 128
244
+ MAX = 127
245
+
246
+ output = numba_array_create(dtype=numpy.uint8)
247
+ no_rep = numba_array_create(dtype=numpy.uint8, base_size=MAX)
248
+ output_idx = 0
249
+ no_rep_idx = 0
250
+
251
+ prev = None
252
+ cur = None
253
+ cnt = 0
254
+ i = 0
255
+ while i < len_data:
256
+ cur = data[i]
257
+ if prev is not None and prev == cur: # repeat
258
+ if no_rep_idx > 0:
259
+ output, output_idx = numba_array_add(output, output_idx, BASE+no_rep_idx)
260
+ output, output_idx = numba_array_extend(output, output_idx, no_rep, no_rep_idx)
261
+ no_rep_idx = 0
262
+
263
+ # find all repeat
264
+ cnt = 1 # prev
265
+ while i < len_data:
266
+ cur = data[i]
267
+ if prev == cur:
268
+ cnt += 1
269
+ if cnt == MAX:
270
+ output, output_idx = numba_array_add(output, output_idx, cnt)
271
+ output, output_idx = numba_array_add(output, output_idx, cur)
272
+ prev = None
273
+ cur = None
274
+ cnt = 0
275
+ i += 1
276
+ break
277
+
278
+ else: # end of repeat
279
+ output, output_idx = numba_array_add(output, output_idx, cnt)
280
+ output, output_idx = numba_array_add(output, output_idx, prev)
281
+ prev = None
282
+ cnt = 0
283
+ break
284
+
285
+ prev = cur
286
+ i += 1
287
+
288
+ if cnt > 0: # end of file
289
+ output, output_idx = numba_array_add(output, output_idx, cnt)
290
+ output, output_idx = numba_array_add(output, output_idx, cur)
291
+ prev = None
292
+ cur = None
293
+ cnt = 0
294
+
295
+ else: # no repeat
296
+ if prev is not None:
297
+ no_rep, no_rep_idx = numba_array_add(no_rep, no_rep_idx, prev)
298
+ if no_rep_idx == MAX:
299
+ output, output_idx = numba_array_add(output, output_idx, BASE+no_rep_idx)
300
+ output, output_idx = numba_array_extend(output, output_idx, no_rep, no_rep_idx)
301
+ no_rep_idx = 0
302
+
303
+ prev = cur
304
+ i += 1
305
+
306
+ if cur is not None:
307
+ no_rep, no_rep_idx = numba_array_add(no_rep, no_rep_idx, cur)
308
+
309
+ if no_rep_idx > 0:
310
+ output, output_idx = numba_array_add(output, output_idx, BASE+no_rep_idx)
311
+ output, output_idx = numba_array_extend(output, output_idx, no_rep, no_rep_idx)
312
+ no_rep_idx = 0
313
+
314
+ output = output[:output_idx]
315
+ return output
316
+
317
+ def hxbyterle_decode(output_size, data):
318
+ """Decode HxRLE data stream
319
+
320
+ If C-extension is not compiled it will use a (slower) Python equivalent
321
+
322
+ :param int output_size: the number of items when ``data`` is uncompressed
323
+ :param str data: a raw stream of data to be unpacked
324
+ :return numpy.array output: an array of ``numpy.uint8``
325
+ """
326
+
327
+ # input_data = struct.unpack('<{}B'.format(len(data)), data)
328
+ input_data = numpy.ndarray((len(data),), '<B', data)
329
+
330
+ output = byterle_decoder_njit(input_data, len(input_data), output_size)
331
+
332
+ assert len(output) == output_size
333
+ return output
334
+
335
+ def hxbyterle_encode(data):
336
+ """Encode HxRLE data
337
+
338
+ :param numpy.array data: an array of ``numpy.uint8``
339
+ :return str output: packed data stream
340
+ """
341
+
342
+ buf = data.tobytes()
343
+ # buf = data.astype('<B').tostring()
344
+
345
+ if len(buf) == 0:
346
+ return ""
347
+
348
+ # output = byterle_encoder(buf)
349
+ output = byterle_encoder_njit(buf, len(buf))
350
+ output = output.tolist()
351
+
352
+ final_output = bytearray(output)
353
+ return final_output
354
+
355
+
356
+ def hxzip_decode(data_size, data):
357
+ """Decode HxZip data stream
358
+
359
+ :param int data_size: the number of items when ``data`` is uncompressed
360
+ :param str data: a raw stream of data to be unpacked
361
+ :return numpy.array output: an array of ``numpy.uint8``
362
+ """
363
+ import zlib
364
+ data_stream = zlib.decompress(data)
365
+ output = numpy.array(struct.unpack('<{}B'.format(len(data_stream)), data_stream), dtype=numpy.uint8)
366
+ # output = numpy.ndarray((data_size,), '<B', data)
367
+
368
+ assert len(output) == data_size
369
+ return output
370
+
371
+ def hxzip_encode(data):
372
+ """Encode HxZip data stream
373
+
374
+ :param numpy.array data: an array of ``numpy.uint8``
375
+ :return str output: packed data stream
376
+ """
377
+ import zlib
378
+
379
+ buf = data.tobytes()
380
+ # buf = data.astype('<B').tostring()
381
+ output = zlib.compress(buf)
382
+ return output
383
+
384
+ def to_numpy_dtype(data_type):
385
+
386
+ # assume little endian
387
+ if data_type == "float":
388
+ dtype = "<f"
389
+ elif data_type == "int":
390
+ dtype = "<i"
391
+ elif data_type == "uint":
392
+ dtype = "<I"
393
+ elif data_type == "short":
394
+ dtype = "<h"
395
+ elif data_type == "ushort":
396
+ dtype = "<H"
397
+ elif data_type == "long":
398
+ dtype = "<l"
399
+ elif data_type == "ulong":
400
+ dtype = "<L"
401
+ elif data_type == "byte":
402
+ # dtype = "<b"
403
+ dtype = "<B" # changed to unsigned char
404
+
405
+ return dtype
406
+
407
+ def unpack_binary(data_pointer, definitions, data):
408
+ """Unpack binary data using ``struct.unpack``
409
+
410
+ :param data_pointer: metadata for the ``data_pointer`` attribute for this data stream
411
+ :type data_pointer: ``ahds.header.Block``
412
+ :param definitions: definitions specified in the header
413
+ :type definitions: ``ahds.header.Block``
414
+ :param bytes data: raw binary data to be unpacked
415
+ :return tuple output: unpacked data
416
+ """
417
+
418
+ if data_pointer.data_dimension:
419
+ data_dimension = data_pointer.data_dimension
420
+ else:
421
+ data_dimension = 1 # if data_dimension is None
422
+
423
+ data_type = to_numpy_dtype(data_pointer.data_type)
424
+
425
+ # get this streams size from the definitions
426
+ x, y, z = definitions.Lattice
427
+ data_length = x * y * z
428
+
429
+ # output = numpy.array(struct.unpack('<' + '{}'.format(data_type) * data_length, data)) # assume little-endian
430
+ output = numpy.ndarray((data_length,), data_type, data)
431
+ output = output.reshape(data_length, data_dimension)
432
+ return output
433
+
434
+ def pack_binary(data_pointer, definitions, data):
435
+
436
+ data_type = to_numpy_dtype(data_pointer.data_type)
437
+
438
+ output = data.astype(data_type).tostring()
439
+ return output
440
+
441
+ def unpack_ascii(data):
442
+ """Unpack ASCII data using string methods``
443
+
444
+ :param data_pointer: metadata for the ``data_pointer`` attribute for this data stream
445
+ :type data_pointer: ``ahds.header.Block``
446
+ :param definitions: definitions specified in the header
447
+ :type definitions: ``ahds.header.Block``
448
+ :param bytes data: raw binary data to be unpacked
449
+ :return list output: unpacked data
450
+ """
451
+ # string: split at newlines -> exclude last list item -> strip space from each
452
+ numstrings = map(lambda s: s.strip(), data.split('\n')[:-1])
453
+
454
+ # check if string is digit (integer); otherwise float
455
+ if len(numstrings) == len(filter(lambda n: n.isdigit(), numstrings)):
456
+ output = map(int, numstrings)
457
+ else:
458
+ output = map(float, numstrings)
459
+ return output
460
+
461
+ def pack_ascii(data):
462
+ return data.tobytes()
463
+
464
+ def encode_data(fmt, dp, defn, data):
465
+ if fmt == "HxByteRLE":
466
+ return hxbyterle_encode(data)
467
+ elif fmt == "HxZip":
468
+ return hxzip_encode(data)
469
+ elif fmt == "ASCII":
470
+ return pack_ascii(data)
471
+ elif fmt is None: # try to pack data
472
+ return pack_binary(dp, defn, data)
473
+ else:
474
+ return None
475
+
476
+ def write_amira(fname, header, data):
477
+ header = header.tobytes() # convert to byte array
478
+ header_obj = AmiraHeader.from_str(header)
479
+
480
+ raw_header = header_obj.raw_header
481
+ file_format = header_obj.designation.format
482
+ data_attr = header_obj.data_pointers
483
+ definitions = header_obj.definitions
484
+
485
+ if file_format.find("BINARY") == -1:
486
+ raise ValueError("write_amira: unsupported file format %r" % file_format)
487
+
488
+ num_stream = len(data)
489
+ raw_data = []
490
+ for i in range(num_stream):
491
+
492
+ dp = getattr(data_attr, "data_pointer_{}".format(i+1))
493
+ encoding = getattr(dp, "data_format")
494
+ size = getattr(dp, "data_length")
495
+ if size is None:
496
+ x, y, z = definitions.Lattice
497
+ size = x * y * z
498
+
499
+ raw_str = encode_data(encoding, dp, definitions, data[i])
500
+ raw_data.append(raw_str)
501
+
502
+ new_size = len(raw_str)
503
+ if new_size != size and encoding is not None: # update header for new size
504
+ enc_size = '@'+str(i+1)+'('+encoding+','+str(size)+')'
505
+ new_enc_size = '@'+str(i+1)+'('+encoding+','+str(new_size)+')'
506
+ raw_header = raw_header.replace(enc_size.encode("utf-8"), new_enc_size.encode("utf-8"))
507
+
508
+ with open(fname, 'wb') as f:
509
+ f.write(raw_header)
510
+
511
+ for i in range(num_stream):
512
+ f.write(b"\n@%d\n" % (i+1))
513
+ f.write(raw_data[i])
514
+ f.write(b'\n')
515
+
516
+ class Image(object):
517
+ """Encapsulates individual images"""
518
+ def __init__(self, z, array):
519
+ self.z = z
520
+ self.__array = array
521
+ self.__byte_values = set(self.__array.flatten().tolist())
522
+ @property
523
+ def byte_values(self):
524
+ return self.__byte_values
525
+ @property
526
+ def array(self):
527
+ """Accessor to underlying array data"""
528
+ return self.__array
529
+ def equalise(self):
530
+ """Increase the dynamic range of the image"""
531
+ multiplier = 255 // len(self.__byte_values)
532
+ return self.__array * multiplier
533
+ @property
534
+ def as_contours(self):
535
+ """A dictionary of lists of contours keyed by byte_value"""
536
+ contours = dict()
537
+ for byte_value in self.__byte_values:
538
+ if byte_value == 0:
539
+ continue
540
+ mask = (self.__array == byte_value) * 255
541
+ found_contours = find_contours(mask, 254, fully_connected='high') # a list of array
542
+ contours[byte_value] = ContourSet(found_contours)
543
+ return contours
544
+ @property
545
+ def as_segments(self):
546
+ return {self.z: self.as_contours}
547
+ def show(self):
548
+ """Display the image"""
549
+ with_matplotlib = True
550
+ try:
551
+ import matplotlib.pyplot as plt
552
+ except RuntimeError:
553
+ import skimage.io as io
554
+ with_matplotlib = False
555
+
556
+ if with_matplotlib:
557
+ equalised_img = self.equalise()
558
+
559
+ _, ax = plt.subplots()
560
+
561
+ ax.imshow(equalised_img, cmap='gray')
562
+
563
+ import random
564
+
565
+ for contour_set in self.as_contours.itervalues():
566
+ r, g, b = random.random(), random.random(), random.random()
567
+ [ax.plot(contour[:,1], contour[:,0], linewidth=2, color=(r,g,b,1)) for contour in contour_set]
568
+
569
+ ax.axis('image')
570
+ ax.set_xticks([])
571
+ ax.set_yticks([])
572
+
573
+ plt.show()
574
+ else:
575
+ io.imshow(self.equalise())
576
+ io.show()
577
+ def __repr__(self):
578
+ return "<Image with dimensions {}>".format(self.array.shape)
579
+ def __str__(self):
580
+ return "<Image with dimensions {}>".format(self.array.shape)
581
+
582
+
583
+ class ImageSet(UserList):
584
+ """Encapsulation for set of ``Image`` objects"""
585
+ def __getitem__(self, index):
586
+ return Image(index, self.data[index])
587
+ @property
588
+ def segments(self):
589
+ """A dictionary of lists of contours keyed by z-index"""
590
+ segments = dict()
591
+ for i in xrange(len(self)):
592
+ image = self[i]
593
+ for z, contour in image.as_segments.iteritems():
594
+ for byte_value, contour_set in contour.iteritems():
595
+ if byte_value not in segments:
596
+ segments[byte_value] = dict()
597
+ if z not in segments[byte_value]:
598
+ segments[byte_value][z] = contour_set
599
+ else:
600
+ segments[byte_value][z] += contour_set
601
+
602
+ return segments
603
+ def __repr__(self):
604
+ return "<ImageSet with {} images>".format(len(self))
605
+
606
+
607
+ class ContourSet(UserList):
608
+ """Encapsulation for a set of ``Contour`` objects"""
609
+ def __getitem__(self, index):
610
+ return Contour(index, self.data[index])
611
+ def __repr__(self):
612
+ string = "{} with {} contours".format(self.__class__, len(self))
613
+ return string
614
+
615
+
616
+ class Contour(object):
617
+ """Encapsulates the array representing a contour"""
618
+ def __init__(self, z, array):
619
+ self.z = z
620
+ self.__array = array
621
+ def __len__(self):
622
+ return len(self.__array)
623
+ def __iter__(self):
624
+ return iter(self.__array)
625
+ @staticmethod
626
+ def string_repr(self):
627
+ string = "<Contour at z={} with {} points>".format(self.z, len(self))
628
+ return string
629
+ def __repr__(self):
630
+ return self.string_repr(self)
631
+ def __str__(self):
632
+ return self.string_repr(self)
633
+
634
+
635
+ class AmiraDataStream(object):
636
+ """Base class for all Amira DataStreams"""
637
+ match = None
638
+ regex = None
639
+ bytes_per_datatype = 4
640
+ dimension = 1
641
+ datatype = None
642
+ find_type = FIND['decimal']
643
+ def __init__(self, amira_header, data_pointer, stream_data):
644
+ self.__amira_header = amira_header
645
+ self.__data_pointer = data_pointer
646
+ self.__stream_data = stream_data
647
+ self.__decoded_length = 0
648
+ @property
649
+ def header(self):
650
+ """An :py:class:``ahds.header.AmiraHeader`` object"""
651
+ return self.__amira_header
652
+ @property
653
+ def data_pointer(self):
654
+ """The data pointer for this data stream"""
655
+ return self.__data_pointer
656
+ @property
657
+ def stream_data(self):
658
+ """All the raw data from the file"""
659
+ return self.__stream_data
660
+ @property
661
+ def encoded_data(self):
662
+ """Encoded raw data in this stream"""
663
+ return None
664
+ @property
665
+ def decoded_data(self):
666
+ """Decoded data for this stream"""
667
+ return None
668
+ @property
669
+ def decoded_length(self):
670
+ """The length of the decoded stream data in relevant units e.g. tuples, integers (not bytes)"""
671
+ return self.__decoded_length
672
+ @decoded_length.setter
673
+ def decoded_length(self, value):
674
+ self.__decoded_length = value
675
+ def __repr__(self):
676
+ return "{} object of {:,} bytes".format(self.__class__, len(self.stream_data))
677
+
678
+
679
+ class AmiraMeshDataStream(AmiraDataStream):
680
+ """Class encapsulating an AmiraMesh/Avizo data stream"""
681
+ last_stream = False
682
+ match = b'stream'
683
+ def __init__(self, *args, **kwargs):
684
+ if self.last_stream:
685
+ self.regex = b'\n@{}\n(?P<%s>.*)' % self.match
686
+ else:
687
+ self.regex = b'\n@{}\n(?P<%s>.*)\n@{}' % self.match
688
+ super(AmiraMeshDataStream, self).__init__(*args, **kwargs)
689
+ if hasattr(self.header.definitions, 'Lattice'):
690
+ X, Y, Z = self.header.definitions.Lattice
691
+ data_size = X * Y * Z
692
+ self.decoded_length = data_size
693
+ elif hasattr(self.header.definitions, 'Vertices'):
694
+ self.decoded_length = None
695
+ elif self.header.parameters.ContentType == "\"HxSpreadSheet\"":
696
+ pass
697
+ elif self.header.parameters.ContentType == "\"SurfaceField\",":
698
+ pass
699
+ else:
700
+ raise ValueError("Unable to determine data size")
701
+ @property
702
+ def encoded_data(self):
703
+ i = self.data_pointer.data_index
704
+ regex = self.regex.replace(b'{}', b'%d')
705
+ cnt = regex.count(b"%d")
706
+ if cnt == 1:
707
+ regex = regex % (i)
708
+ elif cnt == 2:
709
+ regex = regex % (i, i+1)
710
+ else:
711
+ return None
712
+ m = re.search(regex, self.stream_data, flags=re.S)
713
+ return m.group(str(self.match).strip("b'"))
714
+ @property
715
+ def decoded_data(self):
716
+ if self.data_pointer.data_format == "HxByteRLE":
717
+ return hxbyterle_decode(self.decoded_length, self.encoded_data)
718
+ elif self.data_pointer.data_format == "HxZip":
719
+ return hxzip_decode(self.decoded_length, self.encoded_data)
720
+ elif self.header.designation.format == "ASCII":
721
+ return unpack_ascii(self.encoded_data)
722
+ elif self.data_pointer.data_format is None: # try to unpack data
723
+ return unpack_binary(self.data_pointer, self.header.definitions, self.encoded_data)
724
+ else:
725
+ return None
726
+ def get_format(self):
727
+ return self.data_pointer.data_format
728
+ def to_images(self):
729
+ if hasattr(self.header.definitions, 'Lattice'):
730
+ X, Y, Z = self.header.definitions.Lattice
731
+ else:
732
+ raise ValueError("Unable to determine data size")
733
+ image_data = self.decoded_data.reshape(Z, Y, X)
734
+
735
+ imgs = ImageSet(image_data[:])
736
+ return imgs
737
+ def to_volume(self):
738
+ """Return a 3D volume of the data"""
739
+ if hasattr(self.header.definitions, "Lattice"):
740
+ X, Y, Z = self.header.definitions.Lattice
741
+ else:
742
+ raise ValueError("Unable to determine data size")
743
+
744
+ volume = self.decoded_data.reshape(Z, Y, X)
745
+ return volume
746
+
747
+
748
+ class AmiraHxSurfaceDataStream(AmiraDataStream):
749
+ """Base class for all HyperSurface data streams that inherits from ``AmiraDataStream``"""
750
+ def __init__(self, *args, **kwargs):
751
+ self.regex = r"%s (?P<%s>%s+)\n" % (self.match, self.match.lower(), self.find_type)
752
+ super(AmiraHxSurfaceDataStream, self).__init__(*args, **kwargs)
753
+ self.__match = re.search(self.regex, self.stream_data)
754
+ self.__name = None
755
+ self.__count = None
756
+ self.__start_offset = None
757
+ self.__end_offset = None
758
+ @property
759
+ def name(self):
760
+ return self.__name
761
+ @name.setter
762
+ def name(self, value):
763
+ self.__name = value
764
+ @property
765
+ def count(self):
766
+ return self.__count
767
+ @count.setter
768
+ def count(self, value):
769
+ self.__count = value
770
+ @property
771
+ def start_offset(self):
772
+ return self.__start_offset
773
+ @start_offset.setter
774
+ def start_offset(self, value):
775
+ self.__start_offset = value
776
+ @property
777
+ def end_offset(self):
778
+ return self.__end_offset
779
+ @end_offset.setter
780
+ def end_offset(self, value):
781
+ self.__end_offset = value
782
+ @property
783
+ def match_object(self):
784
+ return self.__match
785
+ def __str__(self):
786
+ return """\
787
+ \r{} object
788
+ \r\tname: {}
789
+ \r\tcount: {}
790
+ \r\tstart_offset: {}
791
+ \r\tend_offset: {}
792
+ \r\tmatch_object: {}""".format(
793
+ self.__class__,
794
+ self.name,
795
+ self.count,
796
+ self.start_offset,
797
+ self.end_offset,
798
+ self.match_object,
799
+ )
800
+
801
+
802
+ class VoidDataStream(AmiraHxSurfaceDataStream):
803
+ def __init__(self, *args, **kwargs):
804
+ super(VoidDataStream, self).__init__(*args, **kwargs)
805
+ @property
806
+ def encoded_data(self):
807
+ return []
808
+ @property
809
+ def decoded_data(self):
810
+ return []
811
+
812
+
813
+ class NamedDataStream(VoidDataStream):
814
+ find_type = FIND['alphanum_']
815
+ def __init__(self, *args, **kwargs):
816
+ super(NamedDataStream, self).__init__(*args, **kwargs)
817
+ self.name = self.match_object.group(self.match.lower())
818
+
819
+
820
+ class ValuedDataStream(VoidDataStream):
821
+ def __init__(self, *args, **kwargs):
822
+ super(ValuedDataStream, self).__init__(*args, **kwargs)
823
+ self.count = int(self.match_object.group(self.match.lower()))
824
+
825
+
826
+ class LoadedDataStream(AmiraHxSurfaceDataStream):
827
+ def __init__(self, *args, **kwargs):
828
+ super(LoadedDataStream, self).__init__(*args, **kwargs)
829
+ self.count = int(self.match_object.group(self.match.lower()))
830
+ self.start_offset = self.match_object.end()
831
+ self.end_offset = max([self.start_offset, self.start_offset + self.count * (self.bytes_per_datatype * self.dimension)])
832
+ @property
833
+ def encoded_data(self):
834
+ return self.stream_data[self.start_offset:self.end_offset]
835
+ @property
836
+ def decoded_data(self):
837
+ points = struct.unpack('>' + ((self.datatype * self.dimension) * self.count), self.encoded_data)
838
+ x, y, z = (points[::3], points[1::3], points[2::3])
839
+ return zip(x, y, z)
840
+
841
+
842
+ class VerticesDataStream(LoadedDataStream):
843
+ match = "Vertices"
844
+ datatype = 'f'
845
+ dimension = 3
846
+
847
+
848
+ class NBranchingPointsDataStream(ValuedDataStream):
849
+ match = "NBranchingPoints"
850
+
851
+
852
+ class NVerticesOnCurvesDataStream(ValuedDataStream):
853
+ match = "NVerticesOnCurves"
854
+
855
+
856
+ class BoundaryCurvesDataStream(ValuedDataStream):
857
+ match = "BoundaryCurves"
858
+
859
+
860
+ class PatchesInnerRegionDataStream(NamedDataStream):
861
+ match = "InnerRegion"
862
+
863
+
864
+ class PatchesOuterRegionDataStream(NamedDataStream):
865
+ match = "OuterRegion"
866
+
867
+
868
+ class PatchesBoundaryIDDataStream(ValuedDataStream):
869
+ match = "BoundaryID"
870
+
871
+
872
+ class PatchesBranchingPointsDataStream(ValuedDataStream):
873
+ match = "BranchingPoints"
874
+
875
+
876
+ class PatchesTrianglesDataStream(LoadedDataStream):
877
+ match = "Triangles"
878
+ datatype = 'i'
879
+ dimension = 3
880
+
881
+
882
+ class PatchesDataStream(LoadedDataStream):
883
+ match = "Patches"
884
+ def __init__(self, *args, **kwargs):
885
+ super(PatchesDataStream, self).__init__(*args, **kwargs)
886
+ self.__patches = dict()
887
+ for _ in xrange(self.count):
888
+ # in order of appearance
889
+ inner_region = PatchesInnerRegionDataStream(self.header, None, self.stream_data[self.start_offset:])
890
+ outer_region = PatchesOuterRegionDataStream(self.header, None, self.stream_data[self.start_offset:])
891
+ boundary_id = PatchesBoundaryIDDataStream(self.header, None, self.stream_data[self.start_offset:])
892
+ branching_points = PatchesBranchingPointsDataStream(self.header, None, self.stream_data[self.start_offset:])
893
+ triangles = PatchesTrianglesDataStream(self.header, None, self.stream_data[self.start_offset:])
894
+ patch = {
895
+ 'InnerRegion':inner_region,
896
+ 'OuterRegion':outer_region,
897
+ 'BoundaryID':boundary_id,
898
+ 'BranchingPoints':branching_points,
899
+ 'Triangles':triangles,
900
+ }
901
+ if inner_region.name not in self.__patches:
902
+ self.__patches[inner_region.name] = [patch]
903
+ else:
904
+ self.__patches[inner_region.name] += [patch]
905
+ # start searching from the end of the last search
906
+ self.start_offset = self.__patches[inner_region.name][-1]['Triangles'].end_offset
907
+ self.end_offset = None
908
+ def __iter__(self):
909
+ return iter(self.__patches.keys())
910
+ def __getitem__(self, index):
911
+ return self.__patches[index]
912
+ def __len__(self):
913
+ return len(self.__patches)
914
+ @property
915
+ def encoded_data(self):
916
+ return None
917
+ @property
918
+ def decoded_data(self):
919
+ return None
920
+
921
+
922
+ class DataStreams(object):
923
+ """Class to encapsulate all the above functionality"""
924
+ def __init__(self, fn, header, *args, **kwargs):
925
+ # private attrs
926
+ self.__fn = fn # property
927
+ if header is None:
928
+ self.__amira_header = AmiraHeader.from_file(fn) # property
929
+ else:
930
+ self.__amira_header = header
931
+ self.__data_streams = dict()
932
+ self.__filetype = None
933
+ self.__stream_data = None
934
+ self.__data_streams = self.__configure()
935
+ def __configure(self):
936
+ with open(self.__fn, 'rb') as f:
937
+ self.__stream_data = f.read() #.strip(b'\n')
938
+ if self.__amira_header.designation.filetype == "AmiraMesh" or self.__amira_header.designation.filetype == "Avizo":
939
+ self.__filetype = self.__amira_header.designation.filetype
940
+ i = 0
941
+ while i < len(self.__amira_header.data_pointers.attrs) - 1: # refactor
942
+ data_pointer = getattr(self.__amira_header.data_pointers, 'data_pointer_{}'.format(i + 1))
943
+ self.__data_streams[i + 1] = AmiraMeshDataStream(self.__amira_header, data_pointer, self.__stream_data)
944
+ i += 1
945
+ AmiraMeshDataStream.last_stream = True
946
+ data_pointer = getattr(self.__amira_header.data_pointers, 'data_pointer_{}'. format(i + 1))
947
+ self.__data_streams[i + 1] = AmiraMeshDataStream(self.__amira_header, data_pointer, self.__stream_data)
948
+ # reset AmiraMeshDataStream.last_stream
949
+ AmiraMeshDataStream.last_stream = False
950
+ elif self.__amira_header.designation.filetype == "HyperSurface":
951
+ self.__filetype = "HyperSurface"
952
+ if self.__amira_header.designation.format == "BINARY":
953
+ self.__data_streams['Vertices'] = VerticesDataStream(self.__amira_header, None, self.__stream_data)
954
+ self.__data_streams['NBranchingPoints'] = NBranchingPointsDataStream(self.__amira_header, None, self.__stream_data)
955
+ self.__data_streams['NVerticesOnCurves'] = NVerticesOnCurvesDataStream(self.__amira_header, None, self.__stream_data)
956
+ self.__data_streams['BoundaryCurves'] = BoundaryCurvesDataStream(self.__amira_header, None, self.__stream_data)
957
+ self.__data_streams['Patches'] = PatchesDataStream(self.__amira_header, None, self.__stream_data)
958
+ elif self.__amira_header.designation.format == "ASCII":
959
+ self.__data_streams['Vertices'] = VerticesDataStream(self.__amira_header, None, self.__stream_data)
960
+ return self.__data_streams
961
+ @property
962
+ def file(self): return self.__fn
963
+ @property
964
+ def header(self): return self.__amira_header
965
+ @property
966
+ def stream_data(self): return self.__stream_data
967
+ @property
968
+ def filetype(self): return self.__filetype
969
+ def __iter__(self):
970
+ return iter(self.__data_streams.values())
971
+ def __len__(self):
972
+ return len(self.__data_streams)
973
+ def __getitem__(self, key):
974
+ return self.__data_streams[key]
975
+ def __repr__(self):
976
+ return "{} object with {} stream(s): {}".format(
977
+ self.__class__,
978
+ len(self),
979
+ ", ".join(map(str, self.__data_streams.keys())),
980
+ )