-
Notifications
You must be signed in to change notification settings - Fork 8
/
raw_data_manipulation.nim
1433 lines (1305 loc) · 59.6 KB
/
raw_data_manipulation.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
175
176
177
178
179
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
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
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
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
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
## InGrid raw data manipulation.
## this script performs the raw data manipulation of a given run or list of runs
## and outputs the resulting data to a HDF5 file
## steps which are performed
## - reading all data*.txt and data*.txt-fadc files and writing them to a
## HDF5 file, one group for each run
## - calculating the occupancy of each run
## - calculating the num_pix / event histogram
## - caluclating the FADC signal depth / event histogram
## - calculate the ToT per pixel histogram
# standard lib
import std / [os, osproc, logging, sequtils, sugar, algorithm, tables, times,
strutils, strformat, rdstdin, tempfiles]
# InGrid-module
import fadc_helpers
import helpers/utils
import tos_helpers
import ingrid_types
# other modules
import seqmath
import nimhdf5
import arraymancer
import parsetoml
import cligen / macUt
# We don't use `zippy` for now due to it failing for archives e.g. for run 186 (> int32 in bytes)
# from zippy/tarballs import extractAll # to handle `.tar.gz` archives
type
RawFlagKind = enum
rfIgnoreRunList, rfOverwrite, rfNoFadc, rfTpx3
## XXX: make runtime changeable using config.toml!
const FILE_BUFSIZE {.intdefine.} = 5_000
##############################
# create globals for 2014/15 run list
##############################
# projectDefs contains `OldTosRunListPath` among others
import projectDefs
## TODO: this check is broken! Oh no, it's not broken, but if the binary is moved after compilation
## the CT check is invalid!
var
oldTosRunListFound = false
oldTosCalibRuns: set[uint16] = {}
oldTosBackRuns: set[uint16] = {}
oldTosXrayRuns: set[uint16] = {}
if fileExists(OldTosRunListPath):
oldTosCalibRuns = parseOldTosRunlist(OldTosRunListPath, rtCalibration)
oldTosBackRuns = parseOldTosRunlist(OldTosRunListPath, rtBackground)
oldTosXrayRuns = parseOldTosRunlist(OldTosRunListPath, rtXrayFinger)
oldTosRunListFound = true
when defined(linux):
const commitHash = staticExec("git rev-parse --short HEAD")
else:
const commitHash = ""
# get date using `CompileDate` magic
const compileDate = CompileDate & " at " & CompileTime
const versionStr = "Version: $# built on: $#" % [commitHash, compileDate]
const docStr = """
Usage:
raw_data_manipulation <folder> [options]
raw_data_manipulation <folder> --runType <type> [options]
raw_data_manipulation <folder> --out=<name> [--nofadc] [--runType=<type>] [--ignoreRunList] [options]
raw_data_manipulation <folder> --nofadc [options]
raw_data_manipulation --tpx3 <H5File> [options]
raw_data_manipulation --tpx3 <H5File> --runType <type> [options]
raw_data_manipulation --tpx3 <H5File> --runType <type> --out=<name> [options]
raw_data_manipulation -h | --help
raw_data_manipulation --version
Options:
--tpx3 <H5File> Convert data from a Timepix3 H5 file to TPA format
--runType=<type> Select run type (Calib | Back | Xray)
The following are parsed case insensetive:
Calib = {"calib", "calibration", "c"}
Back = {"back", "background", "b"}
Xray = {"xray", "xrayfinger", "x"}
--out=<name> Filename of output file
--nofadc Do not read FADC files
--ignoreRunList If set ignores the run list 2014/15 to indicate
using any rfOldTos run
--overwrite If set will overwrite runs already existing in the
file. By default runs found in the file will be skipped.
HOWEVER: overwriting is assumed, if you only hand a
run folder!
--config <path> Path to the configuration file to use.
-h --help Show this help
--version Show version.
"""
# define the compression filter we use
#let filter = H5Filter(kind: fkZlib, zlibLevel: 6) # on run 146 took: 0.889728331565857 min
let filter = H5Filter(kind: fkZlib, zlibLevel: 4) # on run 146 took: 0.5987015843391419 min
#let filter = H5Filter(kind: fkZlib, zlibLevel: 9) # on run 146 took: 2.170824865500132 min
# let filter = H5Filter(kind: fkBlosc, bloscLevel: 4,
# doShuffle: false,
# compressor: BloscCompressor.LZ4) # on run 146 took: 0.7583944002787272 min
# let filter = H5Filter(kind: fkBlosc, bloscLevel: 9,
# doShuffle: false,
# compressor: BloscCompressor.LZ4) # on run 146 took: 0.6799025972684224 min
# let filter = H5Filter(kind: fkBlosc, bloscLevel: 4,
# doShuffle: true,
# compressor: BloscCompressor.LZ4) # on run 146 took: 0.6656568646430969 min
# let filter = H5Filter(kind: fkBlosc, bloscLevel: 9,
# doShuffle: true,
# compressor: BloscCompressor.LZ4) # on run 146 took: 0.4899360696474711 min
# let filter = H5Filter(kind: fkNone)
################################################################################
# set up the logger
var L = newConsoleLogger()
if not dirExists("logs"):
createDir("logs")
var fL = newFileLogger("logs/raw_data_manipulation.log", fmtStr = verboseFmtStr)
when isMainModule:
addHandler(L)
addHandler(fL)
################################################################################
template ch_len(): int = 2560
template all_ch_len(): int = ch_len() * 4
proc parseTomlConfig(configFile: string): TomlValueRef =
let configPath = if configFile.len == 0:
const sourceDir = currentSourcePath().parentDir
sourceDir / "config.toml"
else:
configFile
info "Reading config file: ", configPath
result = parseToml.parseFile(configPath)
proc initTotCut(cfg: TomlValueRef): TotCut =
## Initializes a `ToTCut` object from the given config file
let low = cfg["RawData"]["rmTotLow"].getInt
let high = cfg["RawData"]["rmTotHigh"].getInt
template check(val, name: untyped): untyped =
if val < 0:
raise newException(ValueError, "Invalid configuration field `" & $name & "`. Cannot " &
"be a negative value. Given value is: " & $val)
if val > uint16.high.int:
raise newException(ValueError, "Invalid configuration field `" & $name & "`. Cannot " &
"be a larger than `uint16.high = " & $uint16.high & "`. Given value is: " & $val)
check(low, "rmTotLow")
check(high, "rmTotHigh")
result = TotCut(low: low, high: high)
proc specialTypesAndEvKeys(): (DatatypeID, DatatypeID, array[7, string]) =
let
# create datatypes for variable length data
ev_type_xy = special_type(uint8)
ev_type_ch = special_type(uint16)
# dataset names corresponding to event header keys
eventHeaderKeys = ["eventNumber", "useHvFadc", "fadcReadout", "timestamp",
"szint1ClockInt", "szint2ClockInt", "fadcTriggerClock"]
result[0] = ev_type_xy
result[1] = ev_type_ch
result[2] = eventHeaderKeys
proc getTotHitOccDsets(h5f: var H5File, chipGroups: seq[H5Group]):
(seq[H5DataSet], seq[H5DataSet], seq[H5DataSet]) =
proc fromChipGroups(h5f: var H5File, chipGroups: seq[H5Group], name: string): seq[H5DataSet] =
chipGroups.mapIt(h5f[(it.name & name).dset_str])
var
totDset = h5f.fromChipGroups(chipGroups, "/ToT")
hitDset = h5f.fromChipGroups(chipGroups, "/Hits")
occDset = h5f.fromChipGroups(chipGroups, "/Occupancy")
result = (totDset, hitDset, occDset)
template batchFiles(files: var seq[string], bufsize, actions: untyped): untyped =
## this is a template, which can be used to batch a set of files into chunks
## of size `bufsize`. This is done by deleting elements from `files` until it
## is empty. A block of code `actions` is given to the template, which will be
## performed.
## The variable ind_high is injected into the calling space, to allow the
## `actions` block to slice the current files
##
## Example:
##
## .. code-block:
## let
## fname_base = "data$#.txt"
## files = mapIt(toSeq(0..<1000), fname_base & $it)
## batch_files(files, 100):
## # make use of batching for calc with high memory consumption, making use of
## # injected `ind_high` variable
## echo memoryConsumptuousCalc(files[0..ind_high])
while len(files) > 0:
# variable to set last index to read to
var ind_high {.inject.} = min(files.high, bufsize - 1)
# perform actions as desired
actions
info "... removing read elements from list"
# sequtils.delete removes the element with ind_high as well!
files.delete(0, ind_high)
proc readFileBatch[T](files: seq[string],
rfKind: RunFolderKind = rfNewTos): seq[T] {.inline.} =
## Reads the entire given batch of files
let t0 = epochTime()
when T is Event:
case rfKind
of rfOldTos, rfNewTos, rfSrsTos:
result = readListOfInGridFiles(files, rfKind)
else:
raise newException(IOError, "Unknown run folder kind. Cannot read " &
"event files!")
elif T is FadcFile:
result = readListOfFadcFiles(files)
info "All files read. Number = " & $len(result)
info "Reading took $# seconds" % $(epochTime() - t0)
proc readRawInGridData*(listOfFiles: seq[string],
rfKind: RunFolderKind):
seq[Event] =
## given a run_folder it reads all event files (data<number>.txt) and returns
## a sequence of Events, which store the raw event data
## Intermediately we receive FlowVars to ref Events after reading. We read via
## inodes, which may be scramled, so we sort the data and get the FlowVar values.
## NOTE: this procedure does the reading of the data in parallel, thanks to
## using spawn
# Get data files sorted by inode for better performance (esp on HDDs)
let files = sortByInode(listOfFiles)
# read them
result = readFileBatch[Event](files, rfKind)
proc sortReadInGridData(rawInGrid: seq[Event],
rfKind: RunFolderKind): seq[Event] =
## sorts the seq of FlowVars according to event numbers again, otherwise
## h5 file is all mangled
info "Sorting data (", rawInGrid.len, " files)..."
# case on the old TOS' data storage and new TOS' version
let t0 = epochTime()
case rfKind
of rfNewTos, rfOldTos, rfSrsTos:
# in this case there may be missing events, so we simply sort by the indices themselves
# sorting is done the following way:
# - extract list of eventNumbers from `Events`
# - create list of tuples of (`EventNumber`, `AtIndex`)
# - sort tuples by `EventNumber`
# - add elements to result taken from `AtIndex` in `Events`
let
numEvents = rawInGrid.len
# get event numbers
numList = mapIt(rawInGrid, it.evHeader["eventNumber"].parseInt)
# zip event numbers with indices in rawInGrid (unsorted!)
zipped = zip(numList, toSeq(0 ..< numEvents))
# sort tuples by event numbers (indices thus mangled, but in "correct" order
# for insertion)
sortedNums = zipped.sortedByIt(it[0])
info &"Min event number {min(numList)} and max number {max(numList)}"
# insert elements into result
result = newSeqOfCap[Event](rawInGrid.len)
for i in sortedNums:
result.add rawInGrid[i[1]]
else:
# we'll never end up here with rfUnknown, unless something bad happens
logging.fatal("Unkown error. Ended up with unknown run folder kind " &
"in `sortReadInGridData`. Stopping program")
quit(1)
let t1 = epochTime()
info &"...Sorting done, took {$(t1 - t0)} seconds"
proc processRawInGridData(run: Run, totCut: ToTCut): ProcessedRun =
## procedure to process the raw data read from the event files by readRawInGridData
## inputs:
## ch: seq[Event]] = seq of Event objects, which each store raw data of a single event.
## We read normal events, perform calculations
## to obtain ToT per pixel, number of hits and occupancies of that data
## runNumber: int = the run number of the current run
## runHeader = The header valid for the whole run.
## outputs:
## ProcessedRun containing:
## events: seq[Event] = the raw data from the seq of FlowVars saved in seq of Events
## tuple of:
## tot: seq[seq[int]] = ToT values for each chip of Septemboard for run
## hits: seq[seq[int]] = number of hits for each chip of Septemboard fo run
## occ: Tensor[int] = (nChips, 256, 256) tensor containing occupancies of all chips for
## this data.
let
ch = run.events
runHeader = run.runHeader
# get number of chips from header
let nChips = parseInt(runHeader["numChips"])
# variable to count number of processed files
var
count = 0
# store ToT data of all events for each chip
# Note: still contains a single seq for each event! Need to concat
# these seqs at the end
tot_run: seq[seq[seq[uint16]]] = newSeq[seq[seq[uint16]]](nChips)
# store occupancy frames for each chip
# TODO: allow for other values than 7 chips!
occ = zeros[int64](nChips, 256, 256)
# store number of hits for each chip
hits = newSeq[seq[uint16]](nChips)
# initialize the events sequence of result, since we add to this sequence
# instead of copying ch to it!
events = newSeq[Event](len(ch))
# mutable local copy of `TotCut` to count removed pixels
totCut = totCut
let
# get the run specific time and shutter mode
time = parseFloat(runHeader["shutterTime"])
mode = float(parseShutterMode(runHeader["shutterMode"]))
# set the run number
result.runNumber = run.runNumber
# initialize empty sequences. Input to anonymous function is var
# as we change each inner sequence in place with newSeq
apply(tot_run, (x: var seq[seq[uint16]]) => newSeq[seq[uint16]](x, len(ch)))
apply(hits, (x: var seq[uint16]) => newSeq[uint16](x, len(ch)))
info "starting to process events..."
count = 0
for i in 0 .. ch.high:
# assign the length field of the ref object
# for the rest, get a copy of the event
var a: Event = ch[i]
a.length = calcLength(a, time, mode)
for c in mitems(a.chips):
let num = c.chip.number
# filter out unwanted pixels
c.pixels.applyTotCut(totCut)
addPixelsToOccupancySeptem(occ, c.pixels, num)
# store remaining ToT values & # hits
tot_run[num][i] = c.pixels.mapIt(it.ch)
hits[num][i] = c.pixels.len.uint16
## reassing possibly modified event
events[i] = a
echoCount(count, msg = " files processed.")
# use first event of run to fill event header. Fine, because event
# header is contained in every file
result.runHeader = runHeader
result.chips = run.chips
result.nChips = nChips
result.events = events
result.tots = newSeq[seq[uint16]](nChips)
for i, tot in tot_run:
result.tots[i] = concat tot
result.hits = hits
result.occupancies = occ
result.totCut = totCut
proc processFadcData(fadcFiles: seq[FadcFile]): ProcessedFadcRun {.inline.} =
## proc which performs all processing needed to be done on the raw FADC
## data. Starting from conversion of FadcFiles -> FadcData
# sequence to store the indices needed to extract the 0 channel
let nEvents = fadcFiles.len
result.raw_fadc_data = newTensorUninit[uint16]([nEvents, all_ch_len()])
result.trigRecs = newSeq[int](nEvents)
result.eventNumber = newSeq[int](nEvents)
let t0 = epochTime()
for i, ev in fadcFiles:
result.raw_fadc_data[i, _] = ev.data.toTensor.unsqueeze(axis = 0)
result.trigRecs[i] = ev.trigRec
result.eventNumber[i] = ev.eventNumber
info "Calculation of $# events took $# seconds" % [$nEvents, $(epochTime() - t0)]
proc initFadcInH5(h5f: var H5File, runNumber, batchsize: int, filename: string) =
# proc to initialize the datasets etc in the HDF5 file for the FADC. Useful
# since we don't want to do this every time we call the write function
const
ch_len = ch_len()
all_ch_len = all_ch_len()
let groupName = fadcRawPath(runNumber)
template datasetCreation(h5f, name, shape, `type`: untyped): untyped =
## inserts the correct data set creation parameters
when typeof(shape) is tuple:
let chnkS = @[batchsize, shape[1]]
let mxS = @[int.high, shape[1]]
else:
let chnkS = @[batchSize]
let mxS = @[int.high]
h5f.create_dataset(name,
shape,
dtype = `type`,
chunksize = chnkS,
maxshape = mxS,
overwrite = true,
filter = filter)
var
runGroup = h5f.create_group(groupName)
# create the datasets for raw data etc
# NOTE: we initialize all datasets with a size of 0. This means we need to extend
# it immediately. However, this allows us to always (!) simply extend and write
# the data to dset.len onwards!
raw_fadc_dset = h5f.datasetCreation(rawFadcBasename(runNumber), (0, all_ch_len), uint16)
#fadc_dset = h5f.datasetCreation(fadcDataBasename(runNumber), (0, ch_len), float)
trigrec_dset = h5f.datasetCreation(trigrecBasename(runNumber), 0, int)
# dataset of eventNumber
eventNumber_dset = h5f.datasetCreation(eventNumberBasenameRaw(runNumber), 0, int)
# write attributes to FADC groups
# read the given FADC file and extract that information from it
let fadc_for_attrs = readFadcFile(filename)
# helper sequence to loop over both groups to write attrs
runGroup.attrs["posttrig"] = fadc_for_attrs.posttrig
runGroup.attrs["pretrig"] = fadc_for_attrs.pretrig
runGroup.attrs["n_channels"] = fadc_for_attrs.n_channels
runGroup.attrs["channel_mask"] = fadc_for_attrs.channel_mask
runGroup.attrs["frequency"] = fadc_for_attrs.frequency
runGroup.attrs["sampling_mode"] = fadc_for_attrs.sampling_mode
runGroup.attrs["pedestal_run"] = if fadc_for_attrs.pedestal_run == true: 1 else: 0
proc writeFadcDataToH5(h5f: var H5File, runNumber: int, f_proc: ProcessedFadcRun) =
# proc to write the current FADC data to the H5 file
# now write the data
let
raw_name = rawFadcBasename(runNumber)
trigRec_name = trigRecBasename(runNumber)
eventNumber_name = eventNumberBasenameRaw(runNumber)
ch_len = ch_len()
all_ch_len = all_ch_len()
# raw data tensor has N events as rows
nEvents = f_proc.raw_fadc_data.shape[0]
var
raw_fadc_dset = h5f[raw_name.dset_str]
trigRec_dset = h5f[trigRec_name.dset_str]
eventNumber_dset = h5f[eventNumber_name.dset_str]
# first need to extend the dataset, as we start with a size of 0.
let oldsize = raw_fadc_dset.shape[0]
let newsize = oldsize + nEvents
# TODO: currently this is somewhat problematic. We simply resize always. In a way this is
# fine, because we need to resize to append. But in case we start this program twice in
# a row, without deleting the file, we simply extend the dataset further, because we read
# the current (final!) shape from the file
info "Adding to FADC datasets, from $# to $#" % [$oldsize, $newsize]
# add raw FADC tensor by handing `ptr T` and `shape`
raw_fadc_dset.add(f_proc.raw_fadc_data.toUnsafeView(), @(f_proc.raw_fadc_data.shape))
trigRec_dset.add f_proc.trigRecs
eventNumber_dset.add f_proc.eventNumber
info "Done writing FADC data"
proc readWriteFadcData(run_folder: string, runNumber: int, h5f: var H5File) =
## given a run_folder it reads all fadc files (data<number>.txt-fadc),
## processes it (FadcFile -> FadcRaw) and writes it to the HDF5 file
# get a sorted list of files, sorted by inode
var
dataFiles = getListOfEventFiles(run_folder,
EventType.FadcType,
RunFolderKind.rfUnknown,
EventSortType.fname)
raw_fadc_data: seq[FadcFile]
# variable to store the processed FADC data
f_proc: ProcessedFadcRun
# in case of FADC data we cannot afford to read all files into memory before
# writing some to HDF5, because the memory overhead from storing all files
# in seq[string] is too large (17000 FADC files -> 10GB needed!)
# thus already perform batching here
files_read: seq[string]
if dataFiles.files.len == 0:
# in case there are no FADC files, return from this proc
return
const batchsize = 2500
# before we start iterating over the files, initialize the H5 file
var files = dataFiles.files
h5f.initFadcInH5(runNumber, batchsize, files[0])
batchFiles(files, batchsize):
# batch in 1000 file pieces
info "Starting with file $# and ending with file $#" % [$files[0], $files[^1]]
files_read = files_read.concat(files)
raw_fadc_data = readFileBatch[FadcFile](files)
# TODO: read FADC files also by inode and then sort the fadc
# we just read here. NOTE: for that have to change the writeFadcDataToH5
# proc to accomodate that!
# given read files, we now need to append this data to the HDF5 file, before
# we can process more data, otherwise we might run out of RAM
f_proc = raw_fadc_data.processFadcData()
info "Number of FADC files in this batch ", raw_fadc_data.len
h5f.writeFadcDataToH5(runNumber, f_proc)
info "Number of files read: ", files_read.toSet.len
# finally finish writing to the HDF5 file
# finishFadcWriteToH5(h5f, runNumber)
proc createChipGroups(h5f: var H5File,
runNumber: int,
nChips: int = 0): seq[H5Group] =
let chipGroupName = getGroupNameRaw(runNumber) & "/chip_$#"
result = mapIt(toSeq(0 ..< nChips), h5f.create_group(chipGroupName % $it))
proc getChipGroups(h5f: H5File, runNumber: int, nChips: int = 0): seq[H5Group] =
let chipGroupName = getGroupNameRaw(runNumber) & "/chip_$#"
result = mapIt(toSeq(0 ..< nChips), h5f[(chipGroupName % $it).grp_str])
proc initInGridInH5*(h5f: var H5File, runNumber, nChips,
batchsize: int,
createToADset = false) =
## This proc creates the groups and dataset for the InGrid data in the H5 file
## inputs:
## h5f: H5file = the H5 file object of the writeable HDF5 file
## ?
# create variables for group names (NOTE: dirty template!)
let groupName = getGroupNameRaw(runNumber)
let chipGroups = createChipGroups(h5f, runNumber, nChips)
let (ev_type_xy, ev_type_ch, eventHeaderKeys) = specialTypesAndEvKeys()
template datasetCreation(h5f: untyped, name: untyped, `type`: untyped) =
## inserts the correct data set creation parameters and creates the dataset.
## As we do not need it here, discard it and use for its side effect
discard h5f.create_dataset(name,
0,
dtype = `type`,
chunksize = @[batchsize],
maxshape = @[int.high],
overwrite = true,
filter = filter)
for chp in chipGroups:
# datasets are chunked in the batchsize we read. Size originally 0
h5f.datasetCreation(chp.name & "/raw_x", ev_type_xy)
h5f.datasetCreation(chp.name & "/raw_y", ev_type_xy)
h5f.datasetCreation(chp.name & "/raw_ch", ev_type_ch)
h5f.datasetCreation(chp.name & "/ToT", uint16)
h5f.datasetCreation(chp.name & "/Hits", uint16)
# use normal dataset creation proc, due to static size of occupancies
discard h5f.create_dataset(chp.name & "/Occupancy", (256, 256), int, filter = filter, overwrite = true)
if createToADset:
h5f.datasetCreation(chp.name & "/raw_toa", ev_type_ch)
h5f.datasetCreation(chp.name & "/raw_toa_combined", special_type(uint64))
h5f.datasetCreation(chp.name & "/raw_ftoa", special_type(uint8))
# datasets to store the header information for each event
for key in eventHeaderKeys:
h5f.datasetCreation(groupName & "/" & key, int)
# TODO: add string of datetime as well
#dateTimeDset = h5f.create_dataset(joinPath(group_name, "dateTime"), nEvents, string)
# other single column data
h5f.datasetCreation(joinPath(groupName, "eventDuration"), float)
proc getCenterChipAndName(run: ProcessedRun): (int, string) =
## returns the chip number and the name of the center chip
# TODO: Find nicer solution!
var centerChip = 0
case run.nChips
of 1:
centerChip = 0
of 7:
centerChip = 3
else:
warn &"This number of chips ({run.nChips}) currently unsupported for" &
" `centerChip` determination. Will be set to 0."
let centerName = run.chips[centerChip].name
result = (centerChip, centerName)
proc writeRawAttrs*(h5f: var H5File,
run: ProcessedRun,
rfKind: RunFolderKind,
runType: RunTypeKind) =
# finally write run type to base runs group
var rawG = h5f["runs".grp_str]
rawG.attrs["runType"] = $runType
rawG.attrs["runFolderKind"] = $rfKind
let (centerChip, centerName) = getCenterChipAndName(run)
rawG.attrs["centerChip"] = centerChip
rawG.attrs["centerChipName"] = centerName
rawG.attrs["numChips"] = run.nChips
## Write global variables of `raw_data_manipulation`
rawG.attrs["raw_data_manipulation_version"] = commitHash
rawG.attrs["raw_data_manipulation_compiled_on"] = compileDate
rawG.attrs["TimepixVersion"] = $run.timepix
## ToT cut parameters used
rawG.attrs["ToT_cutLow"] = run.totCut.low
rawG.attrs["ToT_cutHigh"] = run.totCut.high
proc writeRunGrpAttrs*(h5f: var H5File, group: var H5Group,
runType: RunTypeKind,
run: ProcessedRun,
toaClusterCutoff: int) =
## writes all attributes to given `group` that can be extracted from
## the `ProcessedRun`, `rfKind` and `runType`.
# now write attribute data (containing the event run header, for a start
# NOTE: unfortunately we cannot write all of it simply using applyIt,
# because we need to parse some numbers as ints, leave some as strings
# see https://github.com/Vindaar/TimepixAnalysis/issues/51 for a solution
var asInt: seq[string]
var asString: seq[string]
case run.timepix
of Timepix1:
asInt = @["runNumber", "runTime", "runTimeFrames", "numChips", "shutterTime",
"runMode", "fastClock", "externalTrigger"]
asString = @["pathName", "dateTime", "shutterMode"]
of Timepix3:
asInt = @["runNumber", "runTime", "runTimeFrames", "numChips",
"runMode", "fastClock", "externalTrigger"]
asString = @["pathName", "dateTime", "shutterMode", "shutterTime"]
# write run header
# Note: need to check for existence of the keys, because for old TOS data,
# not all keys are set!
for it in asInt:
if it in run.runHeader:
let att = parseInt(run.runHeader[it])
group.attrs[it] = att
for it in asString:
if it in run.runHeader:
let att = run.runHeader[it]
group.attrs[it] = att
# now write information about run length (in time)
let first = run.events[0]
let last = run.events[^1]
if "dateTime" in first.evHeader:
let start = first.evHeader["dateTime"]
let stop = last.evHeader["dateTime"]
group.attrs["runStart"] = start
group.attrs["runStop"] = stop
# NOTE: the run length will be wrong by the duration of the last event!
group.attrs["totalRunDuration"] = (parseTOSDateString(stop) -
parseTOSDateString(start)).inSeconds
else:
doAssert "timestamp" in first.evHeader, "Neither `dateTime` nor `timestamp` found in " &
"event header. Invalid!"
let tStart = first.evHeader["timestamp"].parseInt
let tStop = last.evHeader["timestamp"].parseInt
# Note: given that we may be processing a run in batches, we need to read the start / stop
# attributes and possibly overwrite if they are smaller / larger and recompute the total
# run duration!
var runStart = inZone(fromUnix(tStart), utc())
var runStop = inZone(fromUnix(tStop), utc())
if "runStart" in group.attrs:
# keep existing if smaller than new
let writtenRunStart = parse(group.attrs["runStart", string], "yyyy-MM-dd'T'HH:mm:sszzz")
if runStart > writtenRunStart:
runStart = writtenRunStart
if "runStop" in group.attrs:
# keep existing if larger than new
let writtenRunStop = parse(group.attrs["runStop", string], "yyyy-MM-dd'T'HH:mm:sszzz")
if runStop < writtenRunStop:
runStop = writtenRunStop
group.attrs["runStart"] = $runStart
group.attrs["runStop"] = $runStop
# NOTE: the run length will be wrong by the duration of the last event!
group.attrs["totalRunDuration"] = inSeconds(runStop - runStart)
if toaClusterCutoff > 0:
## dealing with Tpx3 data. Write ToA cluster cutoff
group.attrs["toaClusterCutoff"] = toaClusterCutoff
let (centerChip, centerName) = getCenterChipAndName(run)
group.attrs["centerChipName"] = centerName
group.attrs["centerChip"] = centerChip
# initialize the attribute for the current number of stored events to 0
group.attrs["numEventsStored"] = 0
group.attrs["runType"] = $runType
## Write global variables of `raw_data_manipulation`
group.attrs["raw_data_manipulation_version"] = commitHash
group.attrs["raw_data_manipulation_compiled_on"] = compileDate
## ToT cut parameters used
group.attrs["ToT_cutLow"] = run.totCut.low
group.attrs["ToT_cutHigh"] = run.totCut.high
group.attrs["ToT_numRemovedLow"] = run.totCut.rmLow
group.attrs["ToT_numRemovedHigh"] = run.totCut.rmHigh
proc writeChipAttrs*(h5f: var H5File,
chipGroups: var seq[H5Group],
run: ProcessedRun) =
# write attributes for each chip
for i, grp in mpairs(chip_groups):
grp.attrs["chipNumber"] = run.chips[i].number
grp.attrs["chipName"] = run.chips[i].name
# initialize the attribute for the current number of stored events to 0
grp.attrs["numEventsStored"] = 0
proc writeInGridAttrs*(h5f: var H5File, run: ProcessedRun,
rfKind: RunFolderKind, runType: RunTypeKind,
ingridInit: bool,
toaClusterCutoff = -1) =
## `ingridInit` is used to indicate whether we need to write the `RawAttrs`
# writes all attributes into the output file. This includes
# - "runs" group attributes
# - individual run group attributes
# - chip group attributes
# "runs" group
if not ingridInit:
writeRawAttrs(h5f, run, rfKind, runType)
# individual run group
let groupName = getGroupNameRaw(run.runNumber)
var group = h5f[groupName.grp_str]
writeRunGrpAttrs(h5f, group, runType, run, toaClusterCutoff)
# chip groups
var chipGroups = if not ingridInit: createChipGroups(h5f, run.runNumber, run.nChips)
else: getChipGroups(h5f, run.runNumber, run.nChips)
writeChipAttrs(h5f, chipGroups, run)
proc writeTpx3Dacs*(h5f: H5File, dacs: Tpx3Dacs, runNumber, chipNumber: int) =
## Writes the DAC settings to the attributes of chip `chipNumber` in run
## `runNumber`.
# 1. get the group of the chip
let grp = h5f[rawPath(runNumber, chipNumber)]
# 2. iterate all fields and write them as attributes
for field, val in fieldPairs(dacs):
grp.attrs[field] = val
proc fillDataForH5(x, y: var seq[seq[seq[uint8]]],
ch, toa: var seq[seq[seq[uint16]]],
toaCombined: var seq[seq[seq[uint64]]],
ftoa: var seq[seq[seq[uint8]]],
evHeaders: var Table[string, seq[int]],
duration: var seq[float],
events: seq[Event],
startEvent: int) =
doAssert events.len > 0, "Need at least one event to process!"
let
nEvents = events.len
# take 0 event to get number of chips, since same for whole run
nChips = events[0].nChips
hasToa = events[0].chips[0].version == Timepix3
for i in 0 ..< nChips:
x[i] = newSeq[seq[uint8]](nEvents)
y[i] = newSeq[seq[uint8]](nEvents)
ch[i] = newSeq[seq[uint16]](nEvents)
if hasToa:
toa[i] = newSeq[seq[uint16]](nEvents)
toaCombined[i] = newSeq[seq[uint64]](nEvents)
ftoa[i] = newSeq[seq[uint8]](nEvents)
for i, event in events:
duration[i] = event.length
# add event header information
for key in keys(evHeaders):
try:
evHeaders[key][i] = parseInt(event.evHeader[key])
except KeyError:
#echo "Event $# with evHeaders does not contain key $#" % [$event, $key]
discard
except IndexError:
logging.error "Event $# with evHeaders does not contain key $#" % [$event, $key]
# add raw chip pixel information
for chp in event.chips:
let
num = chp.chip.number
let hits = chp.pixels.len
var
xl: seq[uint8] = newSeq[uint8](hits)
yl: seq[uint8] = newSeq[uint8](hits)
chl: seq[uint16] = newSeq[uint16](hits)
for j, p in chp.pixels:
xl[j] = uint8(p[0])
yl[j] = uint8(p[1])
chl[j] = uint16(p[2])
x[num][i] = xl
y[num][i] = yl
ch[num][i] = chl
if hasToa:
# possibly assign ToA
toa[num][i] = chp.toa
toaCombined[num][i] = chp.toaCombined
ftoa[num][i] = chp.ftoa
proc writeProcessedRunToH5*(h5f: var H5File, run: ProcessedRun) =
## this procedure writes the data from the processed run to a HDF5
## (opened already) given by h5file_id
## inputs:
## h5f: H5file = the H5 file object of the writeable HDF5 file
## run: ProcessedRun = a tuple of the processed run data
let
nEvents = run.events.len
runNumber = run.runNumber
nChips = run.nChips
# TODO: write the run information into the meta data of the group
info "Create data to write to HDF5 file"
let t0 = epochTime()
# first write the raw data
# get the names of the groups
let groupName = getGroupNameRaw(runNumber)
var runGroup = h5f[groupName.grp_str]
var chipGroups = createChipGroups(h5f, runNumber, nChips)
let (ev_type_xy, ev_type_ch, eventHeaderKeys) = specialTypesAndEvKeys()
var
# get the datasets from the file in `chipGroups`
x_dsets = mapIt(chipGroups, h5f[(it.name & "/raw_x" ).dset_str])
y_dsets = mapIt(chipGroups, h5f[(it.name & "/raw_y" ).dset_str])
ch_dsets = mapIt(chipGroups, h5f[(it.name & "/raw_ch").dset_str])
# datasets to store the header information for each event
evHeadersDsetTab = eventHeaderKeys.mapIt(
(it, h5f[(groupName & "/" & it).dset_str])
).toTable
# TODO: add string of datetime as well
#dateTimeDset = h5f.create_dataset(joinPath(groupName, "dateTime"), nEvents, string)
# other single column data
durationDset = h5f[(joinPath(groupName, "eventDuration")).dset_str]
duration = newSeq[float](nEvents)
evHeaders = initTable[string, seq[int]]()
x = newSeq[seq[seq[uint8]]](nChips)
y = newSeq[seq[seq[uint8]]](nChips)
ch = newSeq[seq[seq[uint16]]](nChips)
let hasToa = run.timepix == Timepix3
var
toa_dsets: seq[H5Dataset]
toa_combined_dsets: seq[H5Dataset]
ftoa_dsets: seq[H5Dataset]
toa = newSeq[seq[seq[uint16]]](nChips)
toaCombined = newSeq[seq[seq[uint64]]](nChips)
ftoa = newSeq[seq[seq[uint8]]](nChips)
if hasToa:
# also write ToA
toa_dsets = chipGroups.mapIt(h5f[(it.name & "/raw_toa").dset_str])
toa_combined_dsets = chipGroups.mapIt(h5f[(it.name & "/raw_toa_combined").dset_str])
ftoa_dsets = chip_groups.mapIt(h5f[(it.name & "/raw_ftoa").dset_str])
# prepare event header keys and value (init seqs)
for key in eventHeaderKeys:
evHeaders[key] = newSeq[int](nEvents)
##############################
##### Fill the data seqs #####
##############################
# use
let oldsize = runGroup.attrs["numEventsStored", int]
let newsize = oldsize + nEvents
# set new size as attribute
runGroup.attrs["numEventsStored"] = newsize
# call proc to write the data from the events to the seqs, tables
fillDataForH5(x, y, ch, toa, toaCombined, ftoa, evHeaders, duration, run.events, oldsize)
##############################
###### Write the data ########
##############################
info "Writing all dset x data "
for i in 0 ..< nChips:
withDebug:
info "Writing dsets ", i, " size x ", x_dsets.len
x_dsets[i].add x[i]
y_dsets[i].add y[i]
ch_dsets[i].add ch[i]
withDebug:
info "Shape of x ", x[i].len, " ", x[i].shape
info "Shape of dset ", x_dsets[i].shape
if hasToa:
toa_dsets[i].add toa[i]
toa_combined_dsets[i].add toaCombined[i]
ftoa_dsets[i].add ftoa[i]
for key, dset in mpairs(evHeadersDsetTab):
withDebug:
info "Writing $# in $#" % [$key, $dset]
dset.add evHeaders[key]
# write other single column datasets
durationDset.add duration
info "took a total of $# seconds" % $(epochTime() - t0)
####################
# Reconstruction #
####################
# TODO: maybe this needs to be done in a pass after everything has been done?
# at least for occupancy?
info "ToTs shape is ", run.tots.shape
info "hits shape is ", run.hits.shape
# into the reco group name we now write the ToT and Hits information
var (totDsets, hitDsets, occDsets) = getTotHitOccDsets(h5f, chipGroups)
for chip in 0 ..< run.nChips:
# since not every chip has hits on each event, we need to create one group
# for each chip and store each chip's data in these
# in this chip group store:
# - occupancy
# - ToTs
# - Hits
# ... more later
let
tot = run.tots[chip]
hit = run.hits[chip]
occ = run.occupancies[chip, _, _].squeeze.clone
var
totDset = totDsets[chip]
hitDset = hitDsets[chip]
occDset = occDsets[chip]
totDset.add tot
hitDset.add hit
# before writing the occupancy dataset, we need to read the old, stack the current
# occupancy on it and finally write the result
# TODO: this does not seem to make sense to me. We're iterating over all chips in a whole run.
# Why would there be data in the occupancy dataset for us to read?
let stackOcc = occDset[int64].toTensor.reshape([256, 256]) +. occ
occDset.unsafeWrite(stackOcc.get_data_ptr, stackOcc.size)
#proc linkRawToReco(h5f: var H5File, runNumber, nChips: int) =
# ## perform linking from raw group to reco group
# let (groupName,
# recoGroupName,
# chipGroupName,
# combineGroupName) = inGridRawGroupNames(runNumber)
# let (_, _, eventHeaderKeys) = specialTypesAndEvKeys()
# let (totDsetNames,
# hitDsetNames,
# occDsetNames) = getTotHitOccDsetNames(chipGroupName, nChips)
# let
# durationDsetName = joinPath(groupName, "eventDuration")
#
# # link over to reconstruction group
# h5f.create_hardlink(durationDsetName, recoGroupName / extractFilename(durationDsetName))
# # create hard links of header data to reco group
# for key in eventHeaderKeys:
# h5f.create_hardlink(joinPath(groupName, key), joinPath(recoGroupName, key))
proc createRun(runHeader: Table[string, string],
runNumber: int,
events: seq[Event],
rfKind: RunFolderKind): Run =
## performs the conversion from a sequence `Event` plus meta
## information to a `Run`, which combines everything and deals
## with differences of data storage for SRS, old V6 and new V6
result.runHeader = runHeader
result.runNumber = runNumber
result.events = events
# now extract the chips correclty depending on data type
case rfKind
of rfOldTos, rfNewTos:
# just get it from any event
let ev = events[0]
# extract each `Chip` from each `ChipEvent`
result.chips = ev.chips.mapIt(it.chip)
of rfSrsTos:
# in this case need to extract information from the
# run header
if not result.runHeader.hasKey(SrsRunIncomplete) and
not result.runHeader.hasKey(SrsNoChipId):
let nChips = result.runHeader["numChips"].parseInt
result.chips = newSeq[Chip](nChips)
for i in 0 ..< nChips:
let name = result.runHeader[&"chip_{i}"]
result.chips[i] = (name, i)
else:
# in this case take bad information from chips too
let ev = events[0]
result.chips = ev.chips.mapIt(it.chip)
of rfUnknown:
raise newException(Exception, "Creation of a `Run` is impossible for an " &
"unknown run folder kind at the moment!")
proc calcTimeOfEvent(evDuration: float,
eventNumber: int,
timestamp: string,
runStart: DateTime): DateTime =
let
tstamp = timestamp
.align(count = 9, padding = '0')
.parse("HHmmssfff")
tdiff = (evDuration * eventNumber.float).round(places = 3)
result = runStart + initDuration(seconds = tdiff.round.int,
milliseconds = (tdiff - tdiff.trunc).round.int)
# replace time of evDate
result.second = tstamp.second
result.minute = tstamp.minute
result.hour = tstamp.hour
proc calcTimeOfEvent(runTime, totalEvents, eventNumber: int,
timestamp: string,
runStart: DateTime): DateTime =
let evDuration = runTime.float / totalEvents.float
result = calcTimeOfEvent(evDuration, eventNumber, timestamp, runStart)
proc fixOldTosTimestamps(runHeader: Table[string, string],
events: var seq[Event]) =
## applies the correct time stamp to the events in `events` for old TOS
## data, since each event only has the 24h time associated to it.
let
runTime = runHeader["runTime"].parseInt
totalEvents = runHeader["totalEvents"].parseInt
evDuration = runTime.float / totalEvents.float
runStart = runHeader["dateTime"].parse(TosDateString)
for ev in mitems(events):
# walk over all events and replace the `timestamp` field with a corrected value
let
evNumber = ev.evHeader["eventNumber"].parseInt