Coverage for src/thunderfish/files.py: 14%
44 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""" Files
3Helper functions for parsing and reading files, generated by thunderfish.
5## Functions
7- `parse_filename()`: parse components of an EOD analysis file name.
8- `open_from_zip()`: open file from corresponding zip file.
9- `close_zip()`: close stream and corresponding zip file.
11"""
13import io
15from pathlib import Path
16from zipfile import ZipFile
19def parse_filename(file_path):
20 """Parse components of an EOD analysis file name.
22 Analysis files generated by the `eodanalysis` module are named
23 according to
24 ```plain
25 PATH/RECORDING-CHANNEL-TIME-FTYPE-N.EXT
26 ```
28 Parameters
29 ----------
30 file_path: str or Path
31 Path of the file to be parsed.
33 Returns
34 -------
35 recording: str
36 Path and basename of the recording, i.e. 'PATH/RECORDING'.
37 A leading './' is removed.
38 base_path: Path
39 Path and basename of the analysis results,
40 i.e. 'PATH/RECORDING-CHANNEL-TIME'.
41 channel: int
42 Channel of the recording
43 ('CHANNEL' component of the file name if present).
44 -1 if not present in `file_path`.
45 time: float
46 Start time of analysis window in seconds
47 ('TIME' component of the file name if present).
48 `None` if not present in `file_path`.
49 ftype: str
50 Type of analysis file (e.g. 'wavespectrum', 'pulsephases', etc.),
51 ('FTYPE' component of the file name if present).
52 See `file_types` for a list of all supported file types.
53 Empty string if not present in `file_path`.
54 index: int
55 Index of the EOD.
56 ('N' component of the file name if present).
57 -1 if not present in `file_path`.
58 ext: str
59 File extension *without* leading period
60 ('EXT' component of the file name).
62 """
63 file_path = Path(file_path)
64 ext = file_path.suffix
65 ext = ext[1:]
66 parts = file_path.stem.split('-')
67 index = -1
68 if len(parts) > 0 and parts[-1].isdigit():
69 index = int(parts[-1])
70 parts = parts[:-1]
71 ftype = ''
72 if len(parts) > 0:
73 ftype = parts[-1]
74 parts = parts[:-1]
75 base_path = file_path.parent / '-'.join(parts)
76 time = None
77 if len(parts) > 0 and len(parts[-1]) > 0 and \
78 parts[-1][0] == 't' and parts[-1][-1] == 's' and \
79 parts[-1][1:-1].isdigit():
80 time = float(parts[-1][1:-1])
81 parts = parts[:-1]
82 channel = -1
83 if len(parts) > 0 and len(parts[-1]) > 0 and \
84 parts[-1][0] == 'c' and parts[-1][1:].isdigit():
85 channel = int(parts[-1][1:])
86 parts = parts[:-1]
87 recording = '-'.join(parts)
88 return recording, base_path, channel, time, ftype, index, ext
91def open_from_zip(file_handle):
92 """Open file from corresponding zip file.
94 Helper function used by the various load_*() functions
95 in the waveanalysis and pulseanalysis modules.
97 Parameters
98 ----------
99 file_handle: str, Path, or stream
100 The file to be opened for reading.
101 If str or Path and file does not exist, try to open
102 possible corresponding zip file for reading.
104 Returns
105 -------
106 file_handle: str, Path, or stream
107 The file to be read. If not a stream (e.g. does not have a
108 readline() function), it still needs to be opened.
109 If it is a stream from a zip file (`zf` is not None) it needs
110 to be closed after the file has been read.
111 zf: ZipFile or None
112 If zip file was found and opened, this is the zip file handler
113 that needs to be closed after the file has been read.
114 """
115 zf = None
116 if not hasattr(file_handle, 'readline') and not Path(file_handle).is_file():
117 _, base_path, _, _, _, _, _ = parse_filename(file_handle)
118 zip_file = base_path.with_suffix('.zip')
119 if zip_file.is_file():
120 zf = ZipFile(zip_file, 'r')
121 file_pathes = zf.namelist()
122 if file_handle in file_pathes:
123 file_handle = io.TextIOWrapper(zf.open(file_handle, 'r'))
124 else:
125 raise FileNotFoundError(f'"{file_handle}" is not contained in zip-file "{zip_file}"')
126 else:
127 raise FileNotFoundError(f'neither "{file_handle}" nor "{zip_file}" do existis')
128 return file_handle, zf
131def close_zip(file_handle, zf):
132 """Close stream and corresponding zip file.
134 Helper function used by the various load_*() functions
135 in the waveanalysis and pulseanalysis modules.
137 Parameters
138 ----------
139 file_handle: str, Path, or stream
140 If it is a stream from a zip file (`zf` is not None)
141 it is closed.
142 zf: ZipFile or None
143 If non None, this zip file is closed.
144 """
145 if zf is not None:
146 file_handle.close()
147 zf.close()