Class: Pocolog::StreamAligner

Inherits:
Object
  • Object
show all
Defined in:
lib/pocolog/stream_aligner.rb

Defined Under Namespace

Classes: IndexEntry

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(use_rt = false, *streams) ⇒ StreamAligner

Returns a new instance of StreamAligner.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/pocolog/stream_aligner.rb', line 61

def initialize(use_rt = false, *streams)
    @use_sample_time = use_rt == :use_sample_time
    @use_rt  = use_rt
    @global_pos_first_sample = Array.new
    @global_pos_last_sample = Array.new

    @size = 0
    @interval_lg = Array.new
    @base_time = nil
    @stream_state = Array.new
    @streams = Array.new

    @sample_index = -1
    add_streams(*streams)
end

Instance Attribute Details

#base_timeObject (readonly)

Returns the value of attribute base_time.



6
7
8
# File 'lib/pocolog/stream_aligner.rb', line 6

def base_time
  @base_time
end

#full_indexArray<IndexEntry> (readonly)

The full aligned index

This is a mapping from a global position in the aligned stream to information about the sample at that stream

Returns:



49
50
51
# File 'lib/pocolog/stream_aligner.rb', line 49

def full_index
  @full_index
end

#global_pos_first_sampleObject (readonly)

A per-stream mapping from the stream index to the global position of the last of this stream’s samples

See Also:



35
36
37
# File 'lib/pocolog/stream_aligner.rb', line 35

def global_pos_first_sample
  @global_pos_first_sample
end

#global_pos_last_sampleObject (readonly)

A per-stream mapping from the stream index to the global position of the first of this stream’s samples

See Also:



41
42
43
# File 'lib/pocolog/stream_aligner.rb', line 41

def global_pos_last_sample
  @global_pos_last_sample
end

#interval_lg(Time,Time) (readonly)

The time of the first and last samples in the stream, in logical time

Returns:

  • ((Time,Time))


54
55
56
# File 'lib/pocolog/stream_aligner.rb', line 54

def interval_lg
  @interval_lg
end

#sample_indexObject (readonly)

The index of the current sample



29
30
31
# File 'lib/pocolog/stream_aligner.rb', line 29

def sample_index
  @sample_index
end

#sizeInteger (readonly)

Returns the number of samples one can get out of this stream aligner

This is the sum of samples available on each of the underlying streams

Returns:

  • (Integer)


21
22
23
# File 'lib/pocolog/stream_aligner.rb', line 21

def size
  @size
end

#streamsObject (readonly)

Returns the value of attribute streams.



8
9
10
# File 'lib/pocolog/stream_aligner.rb', line 8

def streams
  @streams
end

#use_rtObject (readonly)

Returns the value of attribute use_rt.



3
4
5
# File 'lib/pocolog/stream_aligner.rb', line 3

def use_rt
  @use_rt
end

#use_sample_timeObject (readonly)

Returns the value of attribute use_sample_time.



4
5
6
# File 'lib/pocolog/stream_aligner.rb', line 4

def use_sample_time
  @use_sample_time
end

Instance Method Details

#add_streams(*streams) ⇒ Object

Add new streams to the alignment



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/pocolog/stream_aligner.rb', line 105

def add_streams(*streams)
    return if streams.empty?

    if sample_index != -1
        if eof?
            eof = true
            current_entry = full_index[-1]
        else
            current_entry = full_index[sample_index]
        end
    end

    streams_size = streams.inject(0) { |s, stream| s + stream.size }
    size = (@size += streams_size)
    Pocolog.info "adding #{streams.size} streams with #{streams_size} samples"

    tic = Time.now
    unless base_time
        @base_time = streams.map { |s| s.stream_index.base_time }.compact.min
    end

    sort_index = []
    all_streams = (@streams + streams)
    if base_time
        all_streams.each_with_index do |stream, i|
            stream.stream_index.base_time = base_time
            times = stream.stream_index.raw_each_time.map { |t| t * size + i }
            sort_index.concat(times)
        end
    end

    Pocolog.info "concatenated indexes in #{"%.2f" % [Time.now - tic]} seconds"

    tic = Time.now
    sort_index.sort!

    @global_pos_first_sample = Array.new
    @global_pos_last_sample = Array.new
    current_positions = Array.new(all_streams.size, 0)
    @full_index = Array.new(size)
    position_global = 0
    for sort_code in sort_index
        time         = sort_code / size
        stream_index = sort_code % size
        position_in_stream = current_positions[stream_index]
        current_positions[stream_index] = position_in_stream + 1

        entry = IndexEntry.new(time, stream_index, position_in_stream, position_global)
        global_pos_first_sample[entry.stream_number] ||= position_global
        global_pos_last_sample[entry.stream_number] = position_global
        @full_index[position_global] = entry
        position_global += 1
    end

    @streams = all_streams
    update_interval_lg
    Pocolog.info "built full index in #{"%.2f" % [Time.now - tic]} seconds"

    if current_entry
        @sample_index = @full_index.
            index do |e|
                e.stream_number == current_entry.stream_number &&
                    e.position_in_stream == current_entry.position_in_stream
            end

        if eof
            step
        end
    end
end

#advance(Integer,Time)

Advances one step in the joint stream, and returns the index of the update stream as well as the time but does not decode the data sample_index like #step or #next does

The associated data sample can then be retrieved by single_data(stream_idx)

Returns:

  • ((Integer,Time))

See Also:



400
401
402
403
404
405
406
407
# File 'lib/pocolog/stream_aligner.rb', line 400

def advance
    if eof?
        @sample_index = size
        return
    end

    seek_to_pos(@sample_index + 1, false)
end

#count_samplesObject

Provided for backward compatibility only



11
12
13
14
# File 'lib/pocolog/stream_aligner.rb', line 11

def count_samples
    Pocolog.warn "StreamAligner#count_samples is deprecated. Use #size instead"
    size
end

#each(do_rewind = true) {|stream_idx, time, sample| ... } ⇒ Object

Enumerate all samples in this stream

Parameters:

  • do_rewind (Boolean) (defaults to: true)

    whether #rewind should be called first

Yield Parameters:

  • stream_idx (Integer)

    the stream in which the sample is contained

  • time (Time)

    the stream time

  • sample (Object)

    the sample itself



669
670
671
672
673
674
675
# File 'lib/pocolog/stream_aligner.rb', line 669

def each(do_rewind = true)
    return enum_for(__method__, do_rewind) unless block_given?

    raw_each(do_rewind) do |index, time, raw_sample|
        yield(index, time, Typelib.to_ruby(raw_sample))
    end
end

#empty?Boolean

Whether there are no samples in the aligner

Returns:

  • (Boolean)


24
25
26
# File 'lib/pocolog/stream_aligner.rb', line 24

def empty?
    size == 0
end

#eof?Boolean

Tests whether reading the next sample will return something

If #eof? returns true, #advance and #step are guaranteed to return nil if called.

Returns:

  • (Boolean)


90
91
92
# File 'lib/pocolog/stream_aligner.rb', line 90

def eof?
    sample_index >= size - 1
end

#export_to_file(file, start_index = 0, end_index = size, &block) ⇒ Object

exports all streams to a new log file if no start and end index is given all data are exported otherwise the data are truncated according to the given global indexes

the block is called for each sample to update a custom progress bar if the block returns 1 the export is canceled



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/pocolog/stream_aligner.rb', line 465

def export_to_file(file,start_index=0,end_index=size,&block)
    output = Pocolog::Logfiles.create(file)
    streams.each do |s|
        stream_start_index = first_sample_pos(s)
        stream_end_index  = last_sample_pos(s) + 1
        # Ignore the stream if it is empty
        next if !stream_start_index
        # Ignore the stream if there are no samples intersecting with
        # the required interval
        next if start_index >= stream_end_index || end_index <= stream_start_index

        stream_start_index = [start_index, stream_start_index].max
        stream_end_index   = [end_index, stream_end_index].min

        first_stream_pos = find_first_stream_sample_at_or_after(
            stream_start_index, s)
        last_stream_pos  = find_first_stream_sample_at_or_after(
            stream_end_index, s)
        next if first_stream_pos == last_stream_pos

        index = 0
        number_of_samples = stream_end_index-stream_start_index+1
        stream_output = output.create_stream(s.name, s.type)
        result = s.copy_to(first_stream_pos,last_stream_pos,stream_output) do |i|
            if block
                index +=1
                block.call(index,number_of_samples)
            end
        end
        break if !result
    end
    output.close
end

#find_first_stream_sample_after(position_global, stream) ⇒ Object

Returns the stream-local index of the first sample strictly after the sample at the given global position

Parameters:

  • position_global (Integer)

    a global position

  • stream (DataStream)

    the data stream



522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/pocolog/stream_aligner.rb', line 522

def find_first_stream_sample_after(position_global, stream)
    if !(entry = full_index[position_global])
        return stream.size
    end

    stream_number = stream_index_for_stream(stream)

    # First things first, if entry is a sample of stream, we just have
    # to go forward by one
    if entry.stream_number == stream_number
        return entry.position_in_stream + 1
    end

    # Otherwise, we need to search in the stream
    time  = entry.time
    search_pos = stream.stream_index.sample_number_by_internal_time(time)
    if search_pos == stream.size
        return search_pos
    end

    # If the sample we found has the same time than the entry at
    # position_global, We now have to figure out whether it is before or
    # after position global
    #
    # We do a linear search in all samples that have the same time than
    # the reference time. This basically assumes that you don't have a
    # million samples with the same time. I believe it fair.
    search_time = stream.stream_index.internal_time_by_sample_number(search_pos)
    if search_time != time
        return search_pos
    end

    while entry && entry.time == time
        if entry.stream_number == stream_number
            return entry.position_in_stream
        end
        entry = @full_index[position_global += 1]
    end
    return search_pos + 1
end

#find_first_stream_sample_at_or_after(position_global, stream) ⇒ Object

Returns the stream-local index of the first sample that is either at or just after the given global position

Parameters:

  • position_global (Integer)

    a global position

  • stream (DataStream)

    the data stream



504
505
506
507
508
509
510
511
512
513
514
515
# File 'lib/pocolog/stream_aligner.rb', line 504

def find_first_stream_sample_at_or_after(position_global, stream)
    if !(entry = full_index[position_global])
        return stream.size
    end

    stream_number = stream_index_for_stream(stream)
    if entry.stream_number == stream_number
        return entry.position_in_stream
    end

    find_first_stream_sample_after(position_global, stream)
end

#first_sample_pos(stream) ⇒ nil, Integer

Returns the global sample position of the first sample of the given stream

Parameters:

Returns:

  • (nil, Integer)


576
577
578
579
580
581
# File 'lib/pocolog/stream_aligner.rb', line 576

def first_sample_pos(stream)
    if stream.kind_of?(DataStream)
        stream = validate_stream_index_from_stream(stream)
    end
    @global_pos_first_sample[stream]
end

#last_sample_pos(stream) ⇒ nil, Integer

Returns the global sample position of the last sample of the given stream

Parameters:

Returns:

  • (nil, Integer)


588
589
590
591
592
593
# File 'lib/pocolog/stream_aligner.rb', line 588

def last_sample_pos(stream)
    if stream.kind_of?(DataStream)
        stream = validate_stream_index_from_stream(stream)
    end
    @global_pos_last_sample[stream]
end

#next(Time,Time,(Integer,Typelib::Type))?

Defined for compatibility with DataStream#next

Goes one sample further and returns the sample’s logical and real times as well as the stream index and the sample data

Returns:

  • ((Time,Time,(Integer,Typelib::Type)), nil)


428
429
430
431
432
433
# File 'lib/pocolog/stream_aligner.rb', line 428

def next
    stream_index, time, data = step
    if stream_index
        return time, time, [stream_index, data]
    end
end

#pretty_print(pp) ⇒ Object



448
449
450
451
452
453
454
455
456
457
# File 'lib/pocolog/stream_aligner.rb', line 448

def pretty_print(pp)
    pp.text "Stream aligner with #{streams.size} streams and #{size} samples"
    pp.nest(2) do
        pp.breakable
        pp.seplist(streams.each_with_index) do |s, i|
            pp.text "[#{i}] "
            s.pretty_print(pp)
        end
    end
end

#previous(Time,Time,(Integer,Typelib::Type))?

Defined for compatibility with DataStream#previous

Goes back one sample and returns the sample’s logical and real times as well as the stream index and the sample data

Returns:

  • ((Time,Time,(Integer,Typelib::Type)), nil)


441
442
443
444
445
446
# File 'lib/pocolog/stream_aligner.rb', line 441

def previous
    stream_index, time, data = step_back
    if stream_index
        return time, time, [stream_index, data]
    end
end

#raw_each(do_rewind = true) {|stream_idx, time, sample| ... } ⇒ Object

Enumerate all samples in this stream

Parameters:

  • do_rewind (Boolean) (defaults to: true)

    whether #rewind should be called first

Yield Parameters:

  • stream_idx (Integer)

    the stream in which the sample is contained

  • time (Time)

    the stream time

  • sample (Object)

    the sample itself



653
654
655
656
657
658
659
660
# File 'lib/pocolog/stream_aligner.rb', line 653

def raw_each(do_rewind = true)
    return enum_for(__method__, do_rewind) unless block_given?

    rewind if do_rewind
    while (stream_idx, time = advance)
        yield(stream_idx, time, single_raw_data(stream_idx))
    end
end

#remove_streams(*streams) ⇒ Object

Remove the given streams in the stream aligner

The index-to-stream mapping changes after this. Refer to #streams to map indexes to streams



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
# File 'lib/pocolog/stream_aligner.rb', line 180

def remove_streams(*streams)
    # First, build a map of the current stream indexes to the new
    # stream indexes
    stream_indexes = Array.new
    for_removal = Array.new
    streams.each do |s|
        s_index = stream_index_for_stream(s)
        stream_indexes << s_index
        for_removal[s_index] = true
    end
    index_map = Array.new
    new_index = 0
    self.streams.size.times do |stream_index|
        if for_removal[stream_index]
            index_map[stream_index] = nil
        else
            index_map[stream_index] = new_index
            new_index += 1
        end
    end

    if eof?
        eof = true
    elsif sample_index != -1
        current_entry = full_index[sample_index]
        changed_sample = for_removal[current_entry.stream_number]
    end

    # Then, transform the index accordingly
    @global_pos_first_sample = Array.new
    @global_pos_last_sample = Array.new
    global_position = 0
    @full_index = full_index.find_all do |entry|
        if current_entry && (current_entry == entry)
            @sample_index = global_position
            current_entry = nil # speedup comparison for the rest of the filtering
        end

        if new_index = index_map[entry.stream_number]
            global_pos_first_sample[new_index] ||= global_position
            global_pos_last_sample[new_index] = global_position
            entry.position_global = global_position
            entry.stream_number = new_index
            global_position += 1
        end
    end

    @stream_state.clear

    if @full_index.empty?
        @sample_index = -1
    elsif eof
        @sample_index = size
    elsif @sample_index != -1
        sample_info = seek_to_pos(@sample_index, false)
    end

    # Remove the streams, and update the global attributes
    stream_indexes.reverse.each do |i|
        s = @streams.delete_at(i)
        @size -= s.size
    end
    update_interval_lg

    if sample_info && changed_sample && (sample_index != full_index.size)
        return *sample_info, single_data(sample_info[0])
    end
end

#rewindObject

Rewinds the stream aligner to the position before the first sample

I.e. calling #next after #rewind would read the first sample



97
98
99
100
# File 'lib/pocolog/stream_aligner.rb', line 97

def rewind
    @sample_index = -1
    nil
end

#sample_info(stream_idx) ⇒ (DataStream,Integer)?

Returns the information necessary to read a stream’s sample later

Examples:

stream_idx, time = aligner.advance
@read_later = aligner.sample_info(stream_idx)
...
if @read_later
   stream, position = *@read_later
   stream.read_one_raw_data_sample(position)
end

Parameters:

  • stream_idx (Integer)

    the index of the stream

Returns:



611
612
613
614
615
# File 'lib/pocolog/stream_aligner.rb', line 611

def sample_info(stream_idx)
    if state = @stream_state[stream_idx]
        return streams[stream_idx], state.position_in_stream
    end
end

#seek(pos, read_data = true) ⇒ Object #seek(time, read_data = true) ⇒ Object

Seek at the given position or time



270
271
272
273
274
275
276
# File 'lib/pocolog/stream_aligner.rb', line 270

def seek(pos, read_data = true)
    if pos.kind_of?(Time)
        seek_to_time(pos, read_data)
    else
        seek_to_pos(pos, read_data)
    end
end

#seek_to_index_entry(entry, read_data = true) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This is a private helper for #seek_to_time and #seek_to_pos. It seeks the stream aligner to the given global sample

Parameters:

  • entry (IndexEntry)

    index entry of the global sample we want to seek to



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/pocolog/stream_aligner.rb', line 319

def seek_to_index_entry(entry, read_data = true)
    if !entry
        @sample_index = size
        return
    end

    @sample_index = entry.position_global
    stream_idx = entry.stream_number
    @stream_state[stream_idx] = entry
    if read_data
        return stream_idx, time, single_data(stream_idx)
    else
        return stream_idx, time
    end
end

#seek_to_pos(pos, read_data = true) ⇒ (Integer,Time[,Typelib::Type])

Seeks to the sample whose global position is pos

Parameters:

  • pos (Integer)

    the targetted global position

  • read_data (Boolean) (defaults to: true)

    whether the sample itself should be read or not

Returns:

  • ((Integer,Time[,Typelib::Type]))

    the stream index, sample time and the sample itself if read_data is true



302
303
304
305
306
307
308
309
310
# File 'lib/pocolog/stream_aligner.rb', line 302

def seek_to_pos(pos, read_data = true)
    if empty?
        raise RangeError, "empty stream"
    elsif pos < 0 || pos > size
        raise RangeError, "#{pos} is out of bounds [0..#{size}]."
    end

    seek_to_index_entry(@full_index[pos], read_data)
end

#seek_to_time(time, read_data = true) ⇒ (Integer,Time[,Typelib::Type])

Seek to the first sample after the given time

Parameters:

  • time (Time)

    the reference time

  • read_data (Boolean) (defaults to: true)

    whether the sample itself should be read or not

Returns:

  • ((Integer,Time[,Typelib::Type]))

    the stream index, sample time and the sample itself if read_data is true



284
285
286
287
288
289
290
291
292
293
294
# File 'lib/pocolog/stream_aligner.rb', line 284

def seek_to_time(time, read_data = true)
    if empty?
        raise RangeError, "#{time} is out of bounds, the stream is empty"
    elsif time < interval_lg[0] || time > interval_lg[1]
        raise RangeError, "#{time} is out of bounds valid interval #{interval_lg[0]} to #{interval_lg[1]}"
    end

    target_time = StreamIndex.time_to_internal(time, base_time)
    entry = @full_index.bsearch { |e| e.time >= target_time }
    seek_to_index_entry(entry, read_data)
end

#single_data(index, sample = nil) ⇒ Object?

Returns the current data sample for the given stream index note stream index is the index of the data stream, not the search index !

Parameters:

  • index (Integer)

    index of the stream

  • sample (Typelib::Type, nil) (defaults to: nil)

    if given, the sample will be decoded in this object instead of creating a new one

Returns:

  • (Object, nil)


625
626
627
628
629
# File 'lib/pocolog/stream_aligner.rb', line 625

def single_data(index, sample = nil)
    if (raw = single_raw_data(index, sample))
        return Typelib.to_ruby(raw)
    end
end

#single_raw_data(index, sample = nil) ⇒ Typelib::Type

Returns the current raw data sample for the given stream index note stream index is the index of the data stream, not the search index !

Parameters:

  • index (Integer)

    index of the stream

  • sample (Typelib::Type, nil) (defaults to: nil)

    if given, the sample will be decoded in this object instead of creating a new one

Returns:

  • (Typelib::Type)


639
640
641
642
643
644
# File 'lib/pocolog/stream_aligner.rb', line 639

def single_raw_data(index, sample = nil)
    stream, position = sample_info(index)
    if stream
        stream.read_one_raw_data_sample(position)
    end
end

#step(Integer,Time,Typelib::Type)

Advances one step in the joint stream, and returns the index of the updated stream as well as the time and the data sample

The associated data sample can also be retrieved by single_data(stream_idx)

Returns:

  • ((Integer,Time,Typelib::Type))

See Also:



384
385
386
387
388
389
# File 'lib/pocolog/stream_aligner.rb', line 384

def step
    stream_idx, time = advance
    if stream_idx
        return stream_idx, time, single_data(stream_idx)
    end
end

#step_back(Integer,Time,Typelib::Type)

Decrements one step in the joint stream, an returns the index of the updated stream, its time as well as the sample data

Returns:

  • ((Integer,Time,Typelib::Type))


413
414
415
416
417
418
419
420
# File 'lib/pocolog/stream_aligner.rb', line 413

def step_back
    if @sample_index == 0
        @sample_index = -1
        return nil
    end

    seek_to_pos(sample_index - 1)
end

#stream_by_index(stream_idx) ⇒ DataStream

Return the stream object at the given stream index

Parameters:

  • stream_idx (Integer)

Returns:



339
340
341
# File 'lib/pocolog/stream_aligner.rb', line 339

def stream_by_index(stream_idx)
    @streams[stream_idx]
end

#stream_index_for_name(name) ⇒ Integer?

Returns the stream index of the stream with this name

Parameters:

  • name (String)

Returns:

  • (Integer, nil)


352
353
354
# File 'lib/pocolog/stream_aligner.rb', line 352

def stream_index_for_name(name)
    streams.index { |s| s.name == name }
end

#stream_index_for_stream(stream) ⇒ Object

Returns the stream index for the given stream



344
345
346
# File 'lib/pocolog/stream_aligner.rb', line 344

def stream_index_for_stream(stream)
    streams.index(stream)
end

#stream_index_for_type(type) ⇒ Integer?

Returns the stream index of the stream whose type has this name

Parameters:

  • name (String)

Returns:

  • (Integer, nil)

Raises:

  • (ArgumentError)

    if more than one stream has this type



361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/pocolog/stream_aligner.rb', line 361

def stream_index_for_type(type)
    if type.respond_to?(:name)
        type = type.name
    end

    match_i = streams.index { |s| s.type.name == type }
    if match_i
        rmatch_i = streams.rindex { |s| s.type.name == type }
        if match_i != rmatch_i
            raise ArgumentError, "There exists more than one stream with type #{type}"
        end
        match_i
    end
end

#timeTime

Returns the time of the last played back sample

Returns:

  • (Time)


80
81
82
83
84
# File 'lib/pocolog/stream_aligner.rb', line 80

def time
    if (sample_index != -1) && (entry = full_index[sample_index])
        StreamIndex.time_from_internal(entry.time, base_time)
    end
end

#time_intervalObject



56
57
58
59
# File 'lib/pocolog/stream_aligner.rb', line 56

def time_interval
    Pocolog.warn_deprecated "StreamAligner#time_interval is deprecated in favor of #interval_lg"
    interval_lg
end

#update_interval_lgObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Update #interval_lg based on the information currently in #full_index



253
254
255
256
257
258
259
260
261
262
# File 'lib/pocolog/stream_aligner.rb', line 253

def update_interval_lg
    if full_index.empty?
        @interval_lg = []
    else
        @interval_lg = [
            StreamIndex.time_from_internal(full_index.first.time, base_time),
            StreamIndex.time_from_internal(full_index.last.time, base_time)
        ]
    end
end