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
« 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"""
5import os
6import glob
7import io
8import sys
9import argparse
10import numpy as np
12from zipfile import ZipFile
14from thunderlab.configfile import ConfigFile
15from thunderlab.tabledata import TableData, add_write_table_config, write_table_args
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
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.
34 Data from the *-wavespectrum-*.* and the *-pulsephases-*.* files
35 can be added as specified by `harmonics`, `phases0`, and
36 `phases1`.
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.
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:
90 1: print infos on meta data coverage.
91 2: print additional infos on discarded recordings.
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.
102 """
103 def file_iter(files):
104 """ Iterate over analysis files.
106 Parameters
107 ----------
108 files: list of str
109 Input files.
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
137 def find_recording(recording, meta_recordings):
138 """ Find row of a recording in meta data.
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
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
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)
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
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
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))
646if __name__ == '__main__':
647 main()