Coverage for src/thunderfish/collectfish.py: 0%

450 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-19 20:05 +0000

1""" 

2Collect data generated by thunderfish in a wavefish and a pulsefish table. 

3""" 

4 

5import os 

6import glob 

7import io 

8import sys 

9import argparse 

10import numpy as np 

11 

12from zipfile import ZipFile 

13 

14from thunderlab.configfile import ConfigFile 

15from thunderlab.tabledata import TableData, add_write_table_config, write_table_args 

16 

17from .version import __version__, __year__ 

18from .harmonics import add_harmonic_groups_config 

19from .pulseanalysis import pulse_quality, pulse_quality_args 

20from .waveanalysis import wave_quality, wave_quality_args 

21from .eodanalysis import wave_similarity, pulse_similarity 

22from .eodanalysis import load_species_waveforms, add_species_config 

23from .eodanalysis import add_eod_quality_config 

24from .eodanalysis import adjust_eodf 

25from .eodanalysis import parse_filename 

26 

27 

28def collect_fish(files, simplify_file=False, 

29 meta_data=None, meta_recordings=None, skip_recordings=False, 

30 temp_col=None, q10=1.62, max_fish=0, harmonics=None, 

31 phases0=None, phases1=None, cfg=None, verbose=0): 

32 """Combine all *-wavefish.* and/or *-pulsefish.* files into respective summary tables. 

33 

34 Data from the *-wavespectrum-*.* and the *-pulsephases-*.* files 

35 can be added as specified by `harmonics`, `phases0`, and 

36 `phases1`. 

37 

38 Meta data of the recordings can also be added via `meta_data` and 

39 `meta_recordings`. If `meta_data` contains a column with 

40 temperature, this column can be specified by the `temp_col` 

41 parameter. In this case, an 'T_adjust' and an 'EODf_adjust' column 

42 are inserted into the resulting tables containing the mean 

43 temperature and EOD frequencies adjusted to this temperature, 

44 respectively. For the temperature adjustment of EOD frequency 

45 a Q10 value can be supplied by the `q10` parameter. 

46 

47 Parameters 

48 ---------- 

49 files: list of strings 

50 Files to be combined. 

51 simplify_file: boolean 

52 Remove initial common directories from input files. 

53 meta_data: TableData or None 

54 Table with additional data for each of the recordings. 

55 The meta data are inserted into the summary table according to 

56 the name of the recording as specified in `meta_recordings`. 

57 meta_recordings: array of strings 

58 For each row in `meta_data` the name of the recording. 

59 This name is matched agains the basename of input `files`. 

60 skip_recordings: bool 

61 If True skip recordings that are not found in `meta_recordings`. 

62 temp_col: string or None 

63 A column in `meta_data` with temperatures to which EOD 

64 frequences should be adjusted. 

65 q10: float 

66 Q10 value describing temperature dependence of EOD 

67 frequencies. The default of 1.62 is from Dunlap, Smith, Yetka 

68 (2000) Brain Behav Evol, measured for Apteronotus 

69 lepthorhynchus in the lab. 

70 max_fish: int 

71 Maximum number of fish to be taken, if 0 take all. 

72 harmonics: int 

73 Number of harmonic to be added to the wave-type fish table 

74 (amplitude, relampl, phase). This data is read in from the 

75 corresponding *-wavespectrum-*.* files. 

76 phases0: int 

77 Index of the first phase of a EOD pulse to be added to the 

78 pulse-type fish table. This data is read in from the 

79 corresponding *-pulsephases-*.* files. 

80 phases1: int 

81 Index of the last phase of a EOD pulse to be added to the 

82 pulse-type fish table. This data is read in from the 

83 corresponding *-pulsephases-*.* files. 

84 cfg: ConfigFile 

85 Configuration parameter for EOD quality assessment and species 

86 assignment. 

87 verbose: int 

88 Verbose output: 

89  

90 1: print infos on meta data coverage. 

91 2: print additional infos on discarded recordings. 

92 

93 Returns 

94 ------- 

95 wave_table: TableData 

96 Summary table for all wave-type fish. 

97 pulse_table: TableData 

98 Summary table for all pulse-type fish. 

99 all_table: TableData 

100 Summary table for all wave-type and pulse-type fish. 

101 

102 """ 

103 def file_iter(files): 

104 """ Iterate over analysis files. 

105 

106 Parameters 

107 ---------- 

108 files: list of str 

109 Input files. 

110 

111 Yields 

112 ------ 

113 zf: ZipFile or None 

114 In case an input file is a zip archive, the open archive. 

115 file_path: str 

116 The full path of a single file to be processed. 

117 I.e. a '*-wavefish.*' or '*-pulsefish.*' file. 

118 fish_type: str 

119 Either 'wave' or 'pulse'. 

120 """ 

121 for file_path in files: 

122 _, _, _, _, ftype, _, ext = parse_filename(file_path) 

123 if ext == 'zip': 

124 zf = ZipFile(file_path) 

125 file_pathes = sorted(zf.namelist()) 

126 for zfile in file_pathes: 

127 _, _, _, _, ftype, _, _ = parse_filename(zfile) 

128 if ftype in ['wavefish', 'pulsefish']: 

129 yield zf, zfile, ftype[:-4] 

130 zf.close() 

131 elif ftype in ['wavefish', 'pulsefish']: 

132 yield None, file_path, ftype[:-4] 

133 else: 

134 continue 

135 

136 

137 def find_recording(recording, meta_recordings): 

138 """ Find row of a recording in meta data. 

139 

140 Parameters 

141 ---------- 

142 recording: string 

143 Path and base name of a recording. 

144 meta_recordings: list of string 

145 List of meta data recordings where to find `recording`. 

146 """ 

147 if meta_data is not None: 

148 rec = os.path.splitext(os.path.basename(recording))[0] 

149 for i in range(len(meta_recordings)): 

150 # TODO: strip extension! 

151 if rec == meta_recordings[i]: 

152 return i 

153 return -1 

154 

155 # prepare meta recodings names: 

156 meta_recordings_used = None 

157 if meta_recordings is not None: 

158 meta_recordings_used = np.zeros(len(meta_recordings), dtype=bool) 

159 for r in range(len(meta_recordings)): 

160 meta_recordings[r] = os.path.splitext(os.path.basename(meta_recordings[r]))[0] 

161 # prepare adjusted temperatures: 

162 if meta_data is not None and temp_col is not None: 

163 temp_idx = meta_data.index(temp_col) 

164 temp = meta_data[:,temp_idx] 

165 mean_tmp = np.round(np.nanmean(temp)/0.1)*0.1 

166 meta_data.insert(temp_idx+1, 'T_adjust', 'C', '%.1f') 

167 meta_data.append_data_column([mean_tmp]*meta_data.rows(), temp_idx+1) 

168 # prepare species distances: 

169 wave_names, wave_eods, pulse_names, pulse_eods = \ 

170 load_species_waveforms(cfg.value('speciesFile')) 

171 wave_max_rms = cfg.value('maximumWaveSpeciesRMS') 

172 pulse_max_rms = cfg.value('maximumPulseSpeciesRMS') 

173 # load data:  

174 wave_table = None 

175 pulse_table = None 

176 all_table = None 

177 file_pathes = [] 

178 for zf, file_name, fish_type in file_iter(files): 

179 # file name: 

180 table = None 

181 window_time = None 

182 recording, base_path, channel, start_time, _, _, file_ext = \ 

183 parse_filename(file_name) 

184 file_ext = os.extsep + file_ext 

185 file_pathes.append(os.path.normpath(recording).split(os.path.sep)) 

186 if verbose > 2: 

187 print('processing %s (%s):' % (file_name, recording)) 

188 # find row in meta_data: 

189 mr = -1 

190 if meta_data is not None: 

191 mr = find_recording(recording, meta_recordings) 

192 if mr < 0: 

193 if skip_recordings: 

194 if verbose > 0: 

195 print('skip recording %s: no metadata found' % recording) 

196 continue 

197 elif verbose > 0: 

198 print('no metadata found for recording %s' % recording) 

199 else: 

200 meta_recordings_used[mr] = True 

201 # data: 

202 if zf is not None: 

203 file_name = io.TextIOWrapper(zf.open(os.fspath(file_name), 'r')) 

204 data = TableData(file_name) 

205 if 'twin' in data: 

206 start_time = data[0, 'twin'] 

207 window_time = data[0, 'window'] 

208 data.remove(['twin', 'window']) 

209 table = wave_table if fish_type == 'wave' else pulse_table 

210 # prepare tables: 

211 if not table: 

212 df = TableData(data) 

213 df.clear_data() 

214 if meta_data is not None: 

215 if data.nsecs > 0: 

216 df.insert_section(0, 'metadata') 

217 for c in range(meta_data.columns()): 

218 df.insert(c, *meta_data.column_head(c)) 

219 df.insert(0, ['recording']*data.nsecs + ['file'], '', '%-s') 

220 if window_time is not None: 

221 df.insert(1, 'window', 's', '%.2f') 

222 if start_time is not None: 

223 df.insert(1, 'time', 's', '%.2f') 

224 if channel >= 0: 

225 df.insert(1, 'channel', '', '%d') 

226 if fish_type == 'wave': 

227 if harmonics is not None: 

228 fn = base_path.with_name(base_path.stem + '-wavespectrum-0' + file_ext) 

229 if zf is not None: 

230 fn = io.TextIOWrapper(zf.open(os.fspath(fn), 'r')) 

231 wave_spec = TableData(fn) 

232 if data.nsecs > 0: 

233 df.append_section('harmonics') 

234 for h in range(min(harmonics, wave_spec.rows())+1): 

235 df.append('ampl%d' % h, wave_spec.unit('amplitude'), 

236 wave_spec.format('amplitude')) 

237 if h > 0: 

238 df.append('relampl%d' % h, '%', '%.2f') 

239 df.append('relpower%d' % h, '%', '%.2f') 

240 df.append('phase%d' % h, 'rad', '%.3f') 

241 if len(wave_names) > 0: 

242 if data.nsecs > 0: 

243 df.append_section('species') 

244 for species in wave_names: 

245 df.append(species, '%', '%.0f') 

246 df.append('species', '', '%-s') 

247 else: 

248 if phases0 is not None: 

249 fn = base_path.with_name(base_path.stem + '-pulsephases-0' + file_ext) 

250 if zf is not None: 

251 fn = io.TextIOWrapper(zf.open(os.fspath(fn), 'r')) 

252 pulse_phases = TableData(fn) 

253 if data.nsecs > 0: 

254 df.append_section('phases') 

255 for p in range(phases0, phases1+1): 

256 if p != 1: 

257 df.append('P%dtime' % p, 'ms', '%.3f') 

258 df.append('P%dampl' % p, pulse_phases.unit('amplitude'), 

259 pulse_phases.format('amplitude')) 

260 if p != 1: 

261 df.append('P%drelampl' % p, '%', '%.2f') 

262 df.append('P%dwidth' % p, 'ms', '%.3f') 

263 if len(pulse_names) > 0: 

264 if data.nsecs > 0: 

265 df.append_section('species') 

266 for species in pulse_names: 

267 df.append(species, '%', '%.0f') 

268 df.append('species', '', '%-s') 

269 if fish_type == 'wave': 

270 wave_table = df 

271 table = wave_table 

272 else: 

273 pulse_table = df 

274 table = pulse_table 

275 if not all_table: 

276 df = TableData() 

277 df.append('file', '', '%-s') 

278 if channel >= 0: 

279 df.append('channel', '', '%d') 

280 if start_time is not None: 

281 df.append('time', 's', '%.1f') 

282 if window_time is not None: 

283 df.append('window', 's', '%.1f') 

284 if meta_data is not None: 

285 for c in range(meta_data.columns()): 

286 df.append(*meta_data.column_head(c)) 

287 df.append('index', '', '%d') 

288 df.append('EODf', 'Hz', '%.1f') 

289 df.append('type', '', '%-5s') 

290 if len(wave_names) + len(pulse_names) > 0: 

291 df.append('species', '', '%-s') 

292 all_table = df 

293 # fill tables: 

294 n = data.rows() if not max_fish or max_fish > data.rows() else max_fish 

295 for r in range(n): 

296 # fish index: 

297 idx = r 

298 if 'index' in data: 

299 idx = int(data[r, 'index']) 

300 # check quality: 

301 skips = '' 

302 if fish_type == 'wave': 

303 fn = base_path.with_name(base_path.stem + f'-wavespectrum-{idx}' + file_ext) 

304 if zf is not None: 

305 fn = io.TextIOWrapper(zf.open(os.fspath(fn), 'r')) 

306 wave_spec = TableData(fn) 

307 if cfg is not None: 

308 spec_data = wave_spec.array() 

309 props = data.row_dict(r) 

310 if 'clipped' in props: 

311 props['clipped'] *= 0.01 

312 if 'noise' in props: 

313 props['noise'] *= 0.01 

314 if 'rmserror' in props: 

315 props['rmserror'] *= 0.01 

316 if 'thd' in props: 

317 props['thd'] *= 0.01 

318 _, skips, msg = wave_quality(props, 0.01*spec_data[1:,3], 

319 **wave_quality_args(cfg)) 

320 else: 

321 if cfg is not None: 

322 props = data.row_dict(r) 

323 if 'clipped' in props: 

324 props['clipped'] *= 0.01 

325 if 'noise' in props: 

326 props['noise'] *= 0.01 

327 skips, msg, _ = pulse_quality(props, **pulse_quality_args(cfg)) 

328 if len(skips) > 0: 

329 if verbose > 1: 

330 print('skip fish %2d from %s: %s' % (idx, recording, skips)) 

331 continue 

332 # fill in data: 

333 data_col = 0 

334 table.add(recording, data_col) 

335 all_table.add(recording, data_col) 

336 data_col += 1 

337 if channel >= 0: 

338 table.add(channel, data_col) 

339 all_table.add(channel, data_col) 

340 data_col += 1 

341 if start_time is not None: 

342 table.add(start_time, data_col) 

343 all_table.add(start_time, data_col) 

344 data_col += 1 

345 if window_time is not None: 

346 table.add(window_time, data_col) 

347 all_table.add(window_time, data_col) 

348 data_col += 1 

349 # meta data: 

350 if mr >= 0: 

351 for c in range(meta_data.columns()): 

352 table.add(meta_data[mr,c], data_col) 

353 all_table.add(meta_data[mr,c], data_col) 

354 data_col += 1 

355 elif meta_data is not None: 

356 data_col += meta_data.columns() 

357 table.add(data[r,:].array(), data_col) 

358 eodf = data[r,'EODf'] 

359 all_table.add(data[r,'index'], data_col) 

360 all_table.add(eodf) 

361 all_table.add(fish_type) 

362 species_name = 'unknown' 

363 species_rms = 1.0e12 

364 if fish_type == 'wave': 

365 if harmonics is not None: 

366 for h in range(min(harmonics, wave_spec.rows())+1): 

367 table.add(wave_spec[h,'amplitude']) 

368 if h > 0: 

369 table.add(wave_spec[h,'relampl']) 

370 table.add(wave_spec[h,'relpower']) 

371 table.add(wave_spec[h,'phase']) 

372 if len(wave_names) > 0: 

373 fn = base_path.with_name(base_path.stem + f'-eodwaveform-{idx}' + file_ext) 

374 if zf is not None: 

375 fn = io.TextIOWrapper(zf.open(os.fspath(fn), 'r')) 

376 wave_eod = TableData(fn).array() 

377 wave_eod[:,0] *= 0.001 

378 for species, eod in zip(wave_names, wave_eods): 

379 rms = wave_similarity(eod, wave_eod, 1.0, eodf) 

380 if rms < species_rms and rms < wave_max_rms: 

381 species_name = species 

382 species_rms = rms 

383 table.add(100.0*rms) 

384 table.add(species_name) 

385 else: 

386 if phases0 is not None: 

387 fn = base_path.with_name(base_path.stem + f'-pulsephases-{idx}' + file_ext) 

388 if zf is not None: 

389 fn = io.TextIOWrapper(zf.open(os.fspath(fn), 'r')) 

390 pulse_phases = TableData(fn) 

391 for p in range(phases0, phases1 + 1): 

392 for pr in range(pulse_phases.rows()): 

393 if pulse_phases[pr, 'index'] == p: 

394 break 

395 else: 

396 continue 

397 if p != 1: 

398 table.add(pulse_phases[pr,'time'], f'P{p}time') 

399 table.add(pulse_phases[pr,'amplitude'], f'P{p}ampl') 

400 if p != 1: 

401 table.add(pulse_phases[pr,'relampl'], f'P{p}relampl') 

402 table.add(pulse_phases[pr,'width'], f'P{p}width') 

403 if len(pulse_names) > 0: 

404 fn = base_path.with_name(base_path.stem + f'-eodwaveform-{idx}' + file_ext) 

405 if zf is not None: 

406 fn = io.TextIOWrapper(zf.open(os.fspath(fn), 'r')) 

407 pulse_eod = TableData(fn).array() 

408 pulse_eod[:,0] *= 0.001 

409 for species, eod in zip(pulse_names, pulse_eods): 

410 rms = pulse_similarity(eod, pulse_eod) 

411 if rms < species_rms and rms < pulse_max_rms: 

412 species_name = species 

413 species_rms = rms 

414 table.add(100.0*rms) 

415 table.add(species_name) 

416 #if len(wave_names) + len(pulse_names) > 0: 

417 # all_table.add(species_name) 

418 table.fill_data() 

419 all_table.fill_data() 

420 # check coverage of meta data: 

421 if meta_recordings_used is not None: 

422 if np.all(meta_recordings_used): 

423 if verbose > 0: 

424 print('found recordings for all meta data') 

425 else: 

426 if verbose > 0: 

427 print('no recordings found for:') 

428 for mr in range(len(meta_recordings)): 

429 recording = meta_recordings[mr] 

430 if not meta_recordings_used[mr]: 

431 if verbose > 0: 

432 print(recording) 

433 all_table.set_column(0) 

434 all_table.add(recording) 

435 for c in range(meta_data.columns()): 

436 all_table.add(meta_data[mr,c]) 

437 all_table.add(np.nan) # index 

438 all_table.add(np.nan) # EODf 

439 all_table.add('none') # type 

440 # adjust EODf to mean temperature: 

441 for table in [wave_table, pulse_table, all_table]: 

442 if table is not None and temp_col is not None: 

443 eodf_idx = table.index('EODf') 

444 table.insert(eodf_idx+1, 'EODf_adjust', 'Hz', '%.1f') 

445 table.fill_data() 

446 temp_idx = table.index(temp_col) 

447 tadjust_idx = table.index('T_adjust') 

448 for r in range(table.rows()): 

449 eodf = table[r,eodf_idx] 

450 if np.isfinite(table[r,temp_col]) and np.isfinite(table[r,tadjust_idx]): 

451 eodf = adjust_eodf(eodf, table[r,temp_col], table[r,tadjust_idx], q10) 

452 table[r,eodf_idx+1] = eodf 

453 # add wavefish species (experimental): 

454 # simplify pathes: 

455 if simplify_file and len(file_pathes) > 1: 

456 fp0 = file_pathes[0] 

457 for fi in range(len(fp0)): 

458 is_same = True 

459 for fp in file_pathes[1:]: 

460 if fi >= len(fp) or fp[fi] != fp0[fi]: 

461 is_same = False 

462 break 

463 if not is_same: 

464 break 

465 for table in [wave_table, pulse_table, all_table]: 

466 if table is not None: 

467 for k in range(table.rows()): 

468 idx = table.index('file') 

469 fps = os.path.normpath(table[k,idx]).split(os.path.sep) 

470 table[k,idx] = os.path.sep.join(fps[fi:]) 

471 return wave_table, pulse_table, all_table 

472 

473 

474def rangestr(string): 

475 """ 

476 Parse string of the form N:M . 

477 """ 

478 if string[0] == '=': 

479 string = '-' + string[1:] 

480 ss = string.split(':') 

481 v0 = v1 = None 

482 if len(ss) == 1: 

483 v0 = int(string) 

484 v1 = v0 

485 else: 

486 v0 = int(ss[0]) 

487 v1 = int(ss[1]) 

488 return (v0, v1) 

489 

490 

491def main(cargs=None): 

492 # command line arguments: 

493 if cargs is None: 

494 cargs = sys.argv[1:] 

495 parser = argparse.ArgumentParser(add_help=True, 

496 description='Collect data generated by thunderfish in a wavefish and a pulsefish table.', 

497 epilog='version %s by Benda-Lab (2019-%s)' % (__version__, __year__)) 

498 parser.add_argument('--version', action='version', version=__version__) 

499 parser.add_argument('-v', action='count', dest='verbose', default=0, 

500 help='verbosity level: -v for meta data coverage, -vv for additional info on discarded recordings.') 

501 parser.add_argument('-t', dest='table_type', default=None, choices=['wave', 'pulse'], 

502 help='wave-type or pulse-type fish') 

503 parser.add_argument('-c', dest='simplify_file', action='store_true', 

504 help='remove initial common directories from input files') 

505 parser.add_argument('-m', dest='max_fish', type=int, metavar='N', 

506 help='maximum number of fish to be taken from each recording') 

507 parser.add_argument('-p', dest='pulse_phases', type=rangestr, 

508 default=(0, 1), metavar='N:M', 

509 help='add properties of phase N to M of pulse-type EODs to the table') 

510 parser.add_argument('-w', dest='harmonics', type=int, default=3, metavar='N', 

511 help='add properties of first N harmonics of wave-type EODs to the table') 

512 parser.add_argument('-r', dest='remove_cols', action='append', default=[], metavar='COLUMN', 

513 help='columns to be removed from output table') 

514 parser.add_argument('-s', dest='statistics', action='store_true', 

515 help='also write table with statistics') 

516 parser.add_argument('-i', dest='meta_file', metavar='FILE:REC:TEMP', default='', type=str, 

517 help='insert rows from metadata table in FILE matching recording in colum REC. The optional TEMP specifies a column with temperatures to which EOD frequencies should be adjusted') 

518 parser.add_argument('-q', dest='q10', metavar='Q10', default=1.62, type=float, 

519 help='Q10 value for adjusting EOD frequencies to a common temperature') 

520 parser.add_argument('-S', dest='skip', action='store_true', 

521 help='skip recordings that are not contained in metadata table') 

522 parser.add_argument('-n', dest='file_suffix', metavar='NAME', default='', type=str, 

523 help='name for summary files that is appended to "wavefish" or "pulsefish"') 

524 parser.add_argument('-o', dest='out_path', metavar='PATH', default='.', type=str, 

525 help='path where to store summary tables') 

526 parser.add_argument('-f', dest='format', default='auto', type=str, 

527 choices=TableData.formats + ['same'], 

528 help='file format used for saving summary tables ("same" uses same format as input files)') 

529 parser.add_argument('file', nargs='+', default='', type=str, 

530 help='a *-wavefish.* or *-pulsefish.* file as generated by thunderfish') 

531 # fix minus sign issue: 

532 ca = [] 

533 pa = False 

534 for a in cargs: 

535 if pa and a[0] == '-': 

536 a = '=' + a[1:] 

537 pa = False 

538 if a == '-p': 

539 pa = True 

540 ca.append(a) 

541 # read in command line arguments:  

542 args = parser.parse_args(ca) 

543 verbose = args.verbose 

544 table_type = args.table_type 

545 remove_cols = args.remove_cols 

546 statistics = args.statistics 

547 meta_file = args.meta_file 

548 file_suffix = args.file_suffix 

549 out_path = args.out_path 

550 data_format = args.format 

551 

552 # expand wildcard patterns: 

553 files = [] 

554 if os.name == 'nt': 

555 for fn in args.file: 

556 files.extend(glob.glob(fn)) 

557 else: 

558 files = args.file 

559 

560 # read configuration: 

561 cfgfile = __package__ + '.cfg' 

562 cfg = ConfigFile() 

563 add_harmonic_groups_config(cfg) 

564 add_eod_quality_config(cfg) 

565 add_species_config(cfg) 

566 add_write_table_config(cfg, table_format='csv', unit_style='row', 

567 align_columns=True, shrink_width=False) 

568 cfg.load_files(cfgfile, files[0], 3) 

569 # output format: 

570 if data_format == 'same': 

571 ext = os.path.splitext(files[0])[1][1:] 

572 if ext in TableData.ext_formats: 

573 data_format = TableData.ext_formats[ext] 

574 else: 

575 data_format = 'dat' 

576 if data_format != 'auto': 

577 cfg.set('fileFormat', data_format) 

578 # create output folder: 

579 if not os.path.exists(out_path): 

580 os.makedirs(out_path) 

581 # read in meta file: 

582 md = None 

583 rec_data = None 

584 temp_col = None 

585 if len(meta_file) > 0: 

586 mds = meta_file.split(':') 

587 meta_data = mds[0] 

588 if not os.path.isfile(meta_data): 

589 print('meta data file "%s" not found.' % meta_data) 

590 exit() 

591 md = TableData(meta_data) 

592 if len(mds) < 2: 

593 print('no recording column specified for the table in %s. Choose one of' % meta_data) 

594 for k in md.keys(): 

595 print(' ', k) 

596 exit() 

597 rec_col = mds[1] 

598 if rec_col not in md: 

599 print('%s is not a valid key for the table in %s. Choose one of' % (rec_col, meta_data)) 

600 for k in md.keys(): 

601 print(' ', k) 

602 exit() 

603 else: 

604 rec_data = md[:,rec_col] 

605 del md[:,rec_col] 

606 if len(mds) > 2: 

607 temp_col = mds[2] 

608 if temp_col not in md: 

609 print('%s is not a valid key for the table in %s. Choose one of' % (temp_col, meta_data)) 

610 for k in md.keys(): 

611 print(' ', k) 

612 exit() 

613 # collect files: 

614 wave_table, pulse_table, all_table = collect_fish(files, args.simplify_file, 

615 md, rec_data, args.skip, 

616 temp_col, args.q10, 

617 args.max_fish, args.harmonics, 

618 args.pulse_phases[0], args.pulse_phases[1], 

619 cfg, verbose) 

620 # write tables: 

621 if len(file_suffix) > 0 and file_suffix[0] != '-': 

622 file_suffix = '-' + file_suffix 

623 tables = [] 

624 table_names = [] 

625 if pulse_table and (not table_type or table_type == 'pulse'): 

626 tables.append(pulse_table) 

627 table_names.append('pulse') 

628 if wave_table and (not table_type or table_type == 'wave'): 

629 tables.append(wave_table) 

630 table_names.append('wave') 

631 if all_table and not table_type: 

632 tables.append(all_table) 

633 table_names.append('all') 

634 for table, name in zip(tables, table_names): 

635 for rc in remove_cols: 

636 if rc in table: 

637 table.remove(rc) 

638 table.write(os.path.join(out_path, '%sfish%s' % (name, file_suffix)), 

639 **write_table_args(cfg)) 

640 if statistics: 

641 s = table.statistics() 

642 s.write(os.path.join(out_path, '%sfish%s-statistics' % (name, file_suffix)), 

643 **write_table_args(cfg)) 

644 

645 

646if __name__ == '__main__': 

647 main()