@@ -45,6 +45,9 @@ class TdmsSegment(object):
45
45
'final_chunk_lengths_override' ,
46
46
'object_index' ,
47
47
'segment_incomplete' ,
48
+ 'has_daqmx_objects_cached' ,
49
+ 'chunk_size_cached' ,
50
+ 'data_objects_cached' ,
48
51
]
49
52
50
53
def __init__ (self , position , toc_mask , next_segment_pos , data_position , segment_incomplete ):
@@ -57,6 +60,9 @@ def __init__(self, position, toc_mask, next_segment_pos, data_position, segment_
57
60
self .ordered_objects = None
58
61
self .object_index = None
59
62
self .segment_incomplete = segment_incomplete
63
+ self .has_daqmx_objects_cached = None
64
+ self .chunk_size_cached = None
65
+ self .data_objects_cached = None
60
66
61
67
def __repr__ (self ):
62
68
return "<TdmsSegment at position %d>" % self .position
@@ -135,6 +141,7 @@ def read_segment_objects(self, file, previous_segment_objects, index_cache, prev
135
141
136
142
if index_cache is not None :
137
143
self .object_index = index_cache .get_index (self .ordered_objects )
144
+
138
145
self ._calculate_chunks ()
139
146
return properties
140
147
@@ -261,18 +268,18 @@ def read_raw_data_for_channel(self, f, channel_path, chunk_offset=0, num_chunks=
261
268
262
269
f .seek (self .data_position )
263
270
264
- data_objects = [o for o in self .ordered_objects if o .has_data ]
265
271
chunk_size = self ._get_chunk_size ()
266
272
267
273
# Ensure we're working with Python ints as np.int32 values could overflow
268
274
# (https://github.com/adamreeve/npTDMS/issues/338)
269
- chunk_size = int (chunk_size )
270
275
chunk_offset = int (chunk_offset )
271
276
272
277
if chunk_offset > 0 :
273
278
f .seek (chunk_size * chunk_offset , os .SEEK_CUR )
274
279
stop_chunk = self .num_chunks if num_chunks is None else num_chunks + chunk_offset
275
- for chunk in self ._read_channel_data_chunks (f , data_objects , channel_path , chunk_offset , stop_chunk ):
280
+ for chunk in self ._read_channel_data_chunks (
281
+ f , self ._get_data_objects (), channel_path , chunk_offset , stop_chunk , chunk_size
282
+ ):
276
283
yield chunk
277
284
278
285
def _calculate_chunks (self ):
@@ -351,11 +358,15 @@ def _new_segment_object(self, object_path, raw_data_index_header):
351
358
return TdmsSegmentObject (object_path )
352
359
353
360
def _get_chunk_size (self ):
361
+ if self .chunk_size_cached is not None :
362
+ return self .chunk_size_cached
363
+
354
364
if self ._have_daqmx_objects ():
355
- return get_daqmx_chunk_size (self .ordered_objects )
356
- return sum (
357
- o .data_size
358
- for o in self .ordered_objects if o .has_data )
365
+ self .chunk_size_cached = int (get_daqmx_chunk_size (self .ordered_objects ))
366
+ return self .chunk_size_cached
367
+
368
+ self .chunk_size_cached = int (sum (o .data_size for o in self .ordered_objects if o .has_data ))
369
+ return self .chunk_size_cached
359
370
360
371
def _read_data_chunks (self , file , data_objects , num_chunks ):
361
372
""" Read multiple data chunks at once
@@ -365,13 +376,17 @@ def _read_data_chunks(self, file, data_objects, num_chunks):
365
376
for chunk in reader .read_data_chunks (file , data_objects , num_chunks ):
366
377
yield chunk
367
378
368
- def _read_channel_data_chunks (self , file , data_objects , channel_path , chunk_offset , stop_chunk ):
379
+ def _read_channel_data_chunks (self , file , data_objects , channel_path , chunk_offset , stop_chunk , chunk_size ):
369
380
""" Read multiple data chunks for a single channel at once
370
381
In the base case we read each chunk individually but subclasses can override this
371
382
"""
372
383
reader = self ._get_data_reader ()
373
- for chunk in reader .read_channel_data_chunks (file , data_objects , channel_path , chunk_offset , stop_chunk ):
384
+ initial_position = file .tell ()
385
+ for i , chunk in enumerate (reader .read_channel_data_chunks (
386
+ file , data_objects , channel_path , chunk_offset , stop_chunk
387
+ )):
374
388
yield chunk
389
+ file .seek (initial_position + (i + 1 ) * chunk_size )
375
390
376
391
def _get_data_reader (self ):
377
392
endianness = '>' if (self .toc_mask & toc_properties ['kTocBigEndian' ]) else '<'
@@ -383,6 +398,9 @@ def _get_data_reader(self):
383
398
return ContiguousDataReader (self .num_chunks , self .final_chunk_lengths_override , endianness )
384
399
385
400
def _have_daqmx_objects (self ):
401
+ if self .has_daqmx_objects_cached is not None :
402
+ return self .has_daqmx_objects_cached
403
+
386
404
data_obj_count = 0
387
405
daqmx_count = 0
388
406
for o in self .ordered_objects :
@@ -391,12 +409,12 @@ def _have_daqmx_objects(self):
391
409
if isinstance (o , DaqmxSegmentObject ):
392
410
daqmx_count += 1
393
411
if daqmx_count == 0 :
394
- return False
395
- if daqmx_count == data_obj_count :
396
- return True
397
- if daqmx_count > 0 :
412
+ self . has_daqmx_objects_cached = False
413
+ elif daqmx_count == data_obj_count :
414
+ self . has_daqmx_objects_cached = True
415
+ elif daqmx_count > 0 :
398
416
raise Exception ("Cannot read mixed DAQmx and non-DAQmx data" )
399
- return False
417
+ return self . has_daqmx_objects_cached
400
418
401
419
def _have_interleaved_data (self ):
402
420
""" Whether data in this segment is interleaved. Assumes data is not DAQmx.
@@ -420,6 +438,13 @@ def _have_interleaved_data(self):
420
438
else :
421
439
raise ValueError ("Cannot read interleaved segment containing channels with unsized types" )
422
440
441
+ def _get_data_objects (self ):
442
+ if self .data_objects_cached is not None :
443
+ return self .data_objects_cached
444
+
445
+ self .data_objects_cached = [o for o in self .ordered_objects if o .has_data ]
446
+ return self .data_objects_cached
447
+
423
448
424
449
class InterleavedDataReader (BaseDataReader ):
425
450
""" Reads data in a TDMS segment with interleaved data
@@ -492,24 +517,27 @@ def _read_channel_data_chunk(self, file, data_objects, chunk_index, channel_path
492
517
""" Read data from a chunk for a single channel
493
518
"""
494
519
channel_data = RawChannelDataChunk .empty ()
520
+ current_position = file .tell ()
495
521
for obj in data_objects :
496
522
number_values = self ._get_channel_number_values (obj , chunk_index )
497
523
if obj .path == channel_path :
524
+ file .seek (current_position )
498
525
channel_data = RawChannelDataChunk .channel_data (obj .read_values (file , number_values , self .endianness ))
526
+ current_position = file .tell ()
527
+ break
499
528
elif number_values == obj .number_values :
500
529
# Seek over data for other channel data
501
- file . seek ( obj .data_size , os . SEEK_CUR )
502
- else :
530
+ current_position += obj .data_size
531
+ elif obj . data_type . size is not None :
503
532
# In last chunk with reduced chunk size
504
- if obj .data_type .size is None :
505
- # Type is unsized (eg. string), try reading number of values
506
- obj .read_values (file , number_values , self .endianness )
507
- else :
508
- file .seek (obj .data_type .size * number_values , os .SEEK_CUR )
533
+ current_position += obj .data_type .size * number_values
534
+ else :
535
+ raise Exception ("Cannot skip over channel with unsized type in a truncated segment" )
536
+
509
537
return channel_data
510
538
511
539
def _get_channel_number_values (self , obj , chunk_index ):
512
- if chunk_index == (self .num_chunks - 1 ) and self . final_chunk_lengths_override is not None :
540
+ if self . final_chunk_lengths_override is not None and chunk_index == (self .num_chunks - 1 ):
513
541
return self .final_chunk_lengths_override .get (obj .path , 0 )
514
542
else :
515
543
return obj .number_values
0 commit comments