2Contains functionality for interfacing files and network ports.
7from abc
import abstractmethod
8from collections
import OrderedDict
18 "The fosanalysis.protocols module is deprecated. " +
19 "This interface will be removed in the next release. " +
20 "Please update to the new interface: fosanalysis.datahandling.",
21 category=DeprecationWarning
26 A single record of the distributed fiber optic strain sensing data.
32 Constructs a SensorRecord object.
33 Being adictionary, it may hold further information.
34 \param data The actual data of the record.
35 \param **kwargs Any other properties can be passes as `kwargs`, such as `name`, or `timestamp`.
40 def to_tsv(self, itemsep: str =
"\t") -> str:
42 This function returns the TSV (tab separated values) representation of this record.
43 \param itemsep Separation character. Defaults to `"\t"` (tab).
45 data_str = [str(data)
for data
in self[
"data"]]
46 return itemsep.join([self[
"record_name"], self[
"message_type"], self[
"sensor_type"], *data_str])
50 Abstract class, which specifies the basic interfaces a protocol must implement.
52 \deprecated This class and its subclasses are deprecated and will be removed in the next release.
53 Please update to the new interface, see \ref datahandling.
58 Constructs a Protocol object.
59 Needs to be reimplemented by sub-classes.
65 "The fosanalysis.protocols.Protocol classes are deprecated. " +
66 "This interface will be removed in the next release. " +
67 "Please update to the new interface: fosanalysis.datahandling.",
73 Returns the values of the x-axis record (location data).
75 raise NotImplementedError()
79 Returns the table of the strain data.
81 raise NotImplementedError()
85 Data inferface for the `.tsv` measurement files exported by the
86 ODiSI 6100 series interrogators by Luna Inc \cite LunaInnovations2020.
87 Both gage files (`*_gages.tsv`) and full (`*_full.tsv`) are supported.
89 \deprecated This class and its subclasses are deprecated and will be removed in the next release.
90 Please update to the new interface, see \ref datahandling.
94 only_header: bool =
False,
98 Construct the interface object and parse a `.tsv` file.
99 \param file \copydoc file
100 It is immediately read by \ref read_file().
101 \param only_header \copydoc only_header
102 \param itemsep \copydoc file
103 \param *args Additional positional arguments, will be passed to the superconstructor.
104 \param **kwargs Additional keyword arguments, will be passed to the superconstructor.
143 Parse the content of \ref file and extract the measurement data.
144 It can be called multiple times for reading base data or the whole file.
145 The content is added to the \ref gages and \ref segments dictionaries.
146 The metadata is stored as dictionary in \ref metadata.
147 \param only_header \copydoc only_header
150 status_gages_segments =
None
151 gages = OrderedDict()
152 segments = OrderedDict()
153 with open(self.
file,
"r")
as f:
155 line_list = line.strip().split(self.
itemsep)
157 if not any(line_list):
161 if "---" in line_list[0]:
166 fieldname = line_list[0][:-1]
167 self.
metadata[fieldname] = line_list[1]
if len(line_list) > 1
else None
169 record_name, message_type, sensor_type, *data = line_list
171 if only_header
and message_type.lower() ==
"measurement":
173 if status_gages_segments
is None:
175 status_gages_segments = (record_name.lower() ==
"Gage/Segment Name".lower())
176 if status_gages_segments:
182 segments[
"full"] = {
"start": 0,
207 Read gage and segment line to discover the gages and segments.
208 The gages are written into \ref gages.
209 The segments are written into \ref segments.
210 This information is used later on to split the data by \ref _read_gage_segment_data().
211 \param gages Dictionary, to which data of named gages is written.
212 \param segments Dictionary, to which data of named segments is written.
213 \param data List of split line, assumed to contain the gage and segment names.
217 for index, value
in enumerate(data):
221 if segment_name
is not None:
223 segments[segment_name][
"end"] = index
224 segments[segment_name][
"length"] = index - segments[segment_name][
"start"]
226 segment_name = value.split(
"[")[0]
227 segments[segment_name] = {
"start": index,
"end":
None,
"x":
None,
"y_data": []}
228 elif segment_name
is None:
230 gages[value] = {
"index": index,
"x":
None,
"y_data": []}
232 if segment_name
is not None:
233 segments[segment_name][
"end"] = len(data)
234 segments[segment_name][
"length"] = len(data) - segments[segment_name][
"start"]
235 return gages, segments
244 Private method to run the extraction for all gages and segments.
245 \param gages Dictionary, containing gage information.
246 This includes, the position of the gage in the data.
247 \param segments Dictionary, containing segment information.
248 This includes, the start and the length of the segment.
249 \param record_name The first entry in line, passed to \ref _store_data().
250 Contains the information such as:
251 - `"x-axis"` for the coordinate line,
252 - `"tare"` for the tare data,
253 - a datetime string for regular measurement lines.
254 \param message_type The second entry in line, passed to \ref _store_data().
255 For regular measurement lines this is `"measurement"`.
257 \param sensor_type The third entry in line, passed to \ref _store_data().
258 For regular measurement lines this is `"strain"`.
260 \param data The rest of the line, split up as a list of `str`.
261 This contains the measurement data.
263 for gage
in gages.values():
264 self.
_store_data(gage, record_name, message_type, sensor_type, data)
265 for segment
in segments.values():
266 self.
_store_data(segment, record_name, message_type, sensor_type, data)
274 Private method to store the data into the dictionary.
275 Here, the differenciation between a gage and segment is done.
276 \param gage_segment Dictionary, containing information, which data to extract.
277 A segment is assumed, if the dictionary contains the key `"length"`.
278 Otherwise, it is assumed to be a gage.
279 \param record_name The first entry in line, passed to \ref _store_data().
280 Contains the information such as:
281 - `"x-axis"` for the coordinate line
282 - `"tare"` for the tare data
283 - a datetime string for regular measurement lines
284 \param message_type The second entry in line, passed to \ref _store_data().
285 For regular measurement lines this is `"measurement"`.
287 \param sensor_type The third entry in line, passed to \ref _store_data().
288 For regular measurement lines this is `"strain"`.
290 \param data The rest of the line, split up as a list of `str`.
291 This contains the measurement data.
293 data = np.asarray(data, dtype=float)
294 if "length" in gage_segment:
295 start = gage_segment[
"start"]
296 end = gage_segment[
"start"]+gage_segment[
"length"]
297 data = copy.deepcopy(data[start:end])
299 data = copy.deepcopy(data[gage_segment[
"index"]])
300 if record_name.lower() ==
"x-axis":
301 gage_segment[
"x"] = data
302 elif record_name.lower() ==
"tare":
303 gage_segment[
"tare"] = data
306 record_name=record_name.lower(),
307 timestamp=datetime.datetime.fromisoformat(record_name),
308 message_type=message_type.lower(),
309 sensor_type=sensor_type.lower(),
311 gage_segment[
"y_data"].append(record)
314 is_gage: bool =
False) -> dict:
316 Private method to return the dictionary matching the search criteria.
317 \param name Name of the gage or segment.
318 Defaults to the first gage or segment, depending on `is_gage`.
319 This name needs to exactly match the key in the dictionary.
320 \param is_gage Switch, whether `name` is a gage or a segment.
322 If `True`, look in \ref gages for `name`.
323 If `False`, look in \ref segments for `name`.
325 If no matching segment/gage is found, a `RuntimeError` is raised.
328 name = name
if name
is not None else next(iter(target))
329 result = target.get(name,
None)
331 requesttype =
"gage" if is_gage
else "segment"
332 message =
"No {} with the name '{}' known!"
333 raise RuntimeError(message.format(requesttype, name))
337 is_gage: bool =
False) -> np.array:
339 Returns the values of the tare record (calibration data).
340 \copydetails _get_dict()
343 return target.get(
"tare",
None)
346 is_gage: bool =
False) -> np.array:
348 Returns the values of the x-axis record (location data).
349 \copydetails _get_dict()
352 return target.get(
"x",
None)
355 is_gage: bool =
False,
356 record_list: list =
None) -> list:
358 Returns the table of the strain data.
359 \copydetails _get_dict()
360 \param record_list List of records, defaults to to the first segment found.
362 if record_list
is None:
364 record_list = target.get(
"y_data",
None)
365 return [record[
"data"]
for record
in record_list]
370 is_gage: bool =
False,
371 single: bool =
False,
374 Get the positional data (x-axis), timestamps and strain data for
375 a gage/segment and a time interval.
377 \copydetails get_record_slice()
379 \param single Switch, whether a single reading is requested.
380 Defaults to `False`, requesting a range of readings.
381 If set to `True`, only `start` is required, which is expected
382 either an `int` or a `datetime.datetime`.
383 For the datetime, the closest reading is returned.
384 The `strain` will then be a 1D array.
386 \return Returns a tuple like `(x, timestamps, strain)`.
387 \retval x Array of positional data for the chosen gage/segment.
388 \retval timestamps Array of time stamps for the chosen time interval.
389 \retval strain Array of strain data for the chosen gage/segment and time interval.
393 if isinstance(start, datetime.datetime):
395 elif isinstance(start, int):
397 record_list = target.get(
"y_data",
None)
398 record = record_list[start]
399 return x, record[
"timestamp"], record[
"data"]
403 strain = np.array(self.
get_y_table(record_list=record_slice))
404 return x, timestamps, strain
407 is_gage: bool =
False,
408 record_list: list =
None) -> list:
410 Get the time stamps of all stored records.
411 \copydetails _get_dict()
412 \param record_list List of records, defaults to to the first segment found.
414 if record_list
is None:
416 record_list = target.get(
"y_data",
None)
417 return [record[
"timestamp"]
for record
in record_list]
419 time_stamp: datetime.datetime,
421 is_gage: bool =
False,
422 position: str =
"closest",
425 Get the \ref SensorRecord and its index, which is closest to the given time_stamp.
426 \param time_stamp The time stamp, for which the closest \ref SensorRecord should be returned.
428 \copydetails _get_dict()
429 \param position Position of the data. Available options:
430 - `"closest"` (default) get the entry, which is closest to the given value.
431 - `"searchsorted"` get the entry as reported by `np.searchsorted`.
432 If the time_stamp is larger that any time stamp, the last
433 record is returned but the index is equal to the length
434 of the time stamp list (does not have a corresponding value).
436 \return Returns a tuple like `(sensor_record, index)` with
437 \retval sensor_record the \ref SensorRecord, which time stamp is closest to the given `time_stamp` and
438 \retval index the corresponding index in of the \ref SensorRecord.
442 if position ==
"closest":
443 index, accurate_time_stamp = utils.misc.find_closest_value(timestamps, time_stamp)
444 elif position ==
"searchsorted":
445 index = np.searchsorted(timestamps, time_stamp)
446 if index == len(timestamps):
447 record = target.get(
"y_data",
None)[-1]
449 record = target.get(
"y_data",
None)[index]
455 is_gage: bool =
False,) -> list:
457 Get a portion of the records in the table and return it as a list of \ref SensorRecord.
458 \param start The first record to be included.
459 Defaults to `None` (no restriction), i.e., the first reading.
460 \param end The first record to not be included anymore.
461 Defaults to `None` (no restriction), i.e., the last reading.
462 \copydetails _get_dict()
464 Both `start` and `end` can be of the following types and be combined arbitrarily.
465 - `int`: Index of the record according to Python indexing logic.
466 - `datetime.datetime`: The first record after the given `datetime.datetime`
467 is included for `start` and excluded for `end`.
468 - `datetime.timedelta`: Time duration, in relation to the other parameter.
469 If the parameter is `None`, it defaults to the first/last reading time.
470 This works both for the other parameter being `int` or `datetime.datetime`.
471 If both parameters are `datetime.timedelta`, the data section
472 runs from `start` after the first reading until `end` before the last reading.
475 In the following table, the possible combinations are shown.
477 \f$i\f$ is an index according to the Python indexing logic
478 (the first is \f$0\f$ and the last is \f$-1\f$),
479 \f$t\f$ is a time stamp (`datetime.datetime`),
480 \f$\Delta t\f$ is a time delta (`datetime.timedelta`),
481 \f$t(i)\f$ is the time stamp of the \f$i\f$th reading, and
482 \f$i(t)\f$ is index of the reading with the smallest time stamp bigger than \f$t\f$.
483 | `start` | `end` | Start index | End index |
484 |:---|:---|:---|:---|
485 | `None` | `None` | \f$0\f$ | \f$-1\f$ |
486 | `None` | \f$i_e\f$ | \f$0\f$ | \f$i_e\f$ |
487 | `None` | \f$t_e\f$ | \f$0\f$ | \f$i(t_e)\f$ |
488 | `None` | \f$\Delta t_e\f$ | \f$0\f$ | \f$i(t(0) + \Delta t_e)\f$ |
489 | \f$i_s\f$ | `None` | \f$i_s\f$ | \f$-1\f$ |
490 | \f$i_s\f$ | \f$i_e\f$ | \f$i_s\f$ | \f$i_e\f$ |
491 | \f$i_s\f$ | \f$t_e\f$ | \f$i_s\f$ | \f$i(t_e)\f$ |
492 | \f$i_s\f$ | \f$\Delta t_e\f$ | \f$i_s\f$ | \f$i(t(i_s) + \Delta t_e)\f$ |
493 | \f$t_s\f$ | `None` | \f$i(t_s)\f$ | \f$-1\f$ |
494 | \f$t_s\f$ | \f$i_e\f$ | \f$i(t_s)\f$ | \f$i_e\f$ |
495 | \f$t_s\f$ | \f$t_e\f$ | \f$i(t_s)\f$ | \f$i(t_e)\f$ |
496 | \f$t_s\f$ | \f$\Delta t_e\f$ | \f$i(t_s)\f$ | \f$i(t_s + \Delta t_e)\f$ |
497 | \f$\Delta t_s\f$ | `None` | \f$i(t(-1)-\Delta t_s)\f$ | -1 |
498 | \f$\Delta t_s\f$ | \f$i_e\f$ | \f$i(t(i_e)-\Delta t_s)\f$ | \f$i_e\f$ |
499 | \f$\Delta t_s\f$ | \f$t_e\f$ | \f$i(t_e - \Delta t_s)\f$ | \f$i(t_e)\f$ |
500 | \f$\Delta t_s\f$ | \f$\Delta t_e\f$ | \f$i(t(0) + \Delta t_s)\f$ | \f$i(t(-1) - \Delta t_e)\f$ |
503 record_list = target.get(
"y_data",
None)
504 if record_list
is None:
505 requesttype =
"gage" if is_gage
else "segment"
506 message =
"No data found for {} with the name '{}'!"
507 raise RuntimeError(message.format(requesttype, name))
509 if isinstance(start, int):
511 elif isinstance(start, datetime.datetime):
516 position=
"searchsorted",
518 elif isinstance(start, datetime.timedelta):
519 if isinstance(end, datetime.datetime):
520 start_tmp = end - start
521 elif isinstance(end, int):
523 elif isinstance(end, datetime.timedelta):
531 position=
"searchsorted",
536 if isinstance(end, int):
538 elif isinstance(end, datetime.datetime):
543 position=
"searchsorted",
545 elif isinstance(end, datetime.timedelta):
546 if isinstance(start, datetime.datetime):
547 end_tmp = start + end
548 elif isinstance(start, int):
550 elif isinstance(start, datetime.timedelta):
558 position=
"searchsorted",
561 end_index = len(record_list)
562 return record_list[start_index:end_index]
566 is_gage: bool =
False,) -> tuple:
568 Get the strain time series for a fixed position.
569 Therefore, the closest x-value to the given position is found and the according strain values are collected.
570 \param x Position, for which the time series should be retrieved.
571 This is used to search the nearest position in the segment.
572 For time series of gages (`is_gage=True`), this has no influence.
573 \copydetails _get_dict()
574 \return Returns a tuple of `(x_value, time_stamps, time_series)`.
575 \retval x_value The accurate position, that was found.
576 \retval time_stamps List of time stamps.
577 \retval time_series List of strain values for at the position of `x_value`.
583 iterator = iter(x_values)
589 index, x_value = utils.misc.find_closest_value(x_values, x)
590 time_series = np.array([data[index]
for data
in y_data])
591 return x_value, time_stamps, time_series
594 Get the metadata dictionary.
602 list_of_time_intervals: list,
605 Extracts the parts of the measurement data into smaller `.tsv` files.
606 The parts are given by time intervals.
607 The file header is copied as is.
608 A measurement line is copied to the `output_file` if its timestamp
609 \f$t\f$ falls within at least one of the given time intervals:
610 \f$t_{\mathrm{s}} \leq t \leq t_{\mathrm{e}}\f$.
611 Overlapping intervals are supported.
613 \note The `output_file` is overwritten without asking for confirmation!
615 \deprecated This function is deprecated and was moved to `fosanalysis.datahandling.tsvustils.extract_time_intervals()`.
616 Please use the updated function call
618 \param input_file String with the path of the input file (.tsv)
619 \param output_file String with the path of the output file (.tsv)
620 \param list_of_time_intervals List of time intervals.
621 Each interval is given as tuple of its the limits
622 (start time \f$t_{\mathrm{s}}\f$, end time \f$t_{\mathrm{e}}\f$).
623 Both limits can be either `datetime.datetime` or `None`.
624 Setting one or both of the limits to `None` results in an (half) unlimited open interval.
625 It is not necessary that the exact interval boundaries appear in the data.
628 "This function is deprecated and was moved to `fosanalysis.datahandling.tsvustils.extract_time_intervals()`. " +
629 "This interface will be removed in the next release. " +
630 "Please update to the new interface.",
633 with open(input_file,
"r")
as infile, open(output_file,
"w")
as outfile:
635 entries = line.split(
"\t")
637 timestamp = datetime.datetime.fromisoformat(entries[0].strip())
638 for start_time, end_time
in list_of_time_intervals:
639 if (start_time
is None or timestamp >= start_time)
and (end_time
is None or timestamp <= end_time):
Data inferface for the .tsv measurement files exported by the ODiSI 6100 series interrogators by Luna...
list get_time_stamps(self, str name=None, bool is_gage=False, list record_list=None)
Get the time stamps of all stored records.
dict _get_dict(self, str name=None, bool is_gage=False)
Private method to return the dictionary matching the search criteria.
list get_record_slice(self, start=None, end=None, str name=None, bool is_gage=False)
Get a portion of the records in the table and return it as a list of SensorRecord.
segments
Dictionary of segments.
_read_gage_segment_data(self, dict gages, dict segments, str record_name, str message_type, str sensor_type, list data)
Private method to run the extraction for all gages and segments.
tuple get_record_from_time_stamp(self, datetime.datetime time_stamp, str name=None, bool is_gage=False, str position="closest")
Get the SensorRecord and its index, which is closest to the given time_stamp.
read_file(self, bool only_header)
Parse the content of file and extract the measurement data.
file
Fully specified file path, from which the data is read.
_read_gage_segments_info(self, dict gages, dict segments, list data)
Read gage and segment line to discover the gages and segments.
np.array get_x_values(self, str name=None, bool is_gage=False)
Returns the values of the x-axis record (location data).
only_header
Switch to omit processing the complete file.
__init__(self, str file, bool only_header=False, str itemsep="\t", *args, **kwargs)
Construct the interface object and parse a .tsv file.
itemsep
String, which separates items (columns) in the file.
tuple get_data(self, start=None, end=None, str name=None, bool is_gage=False, bool single=False)
Get the positional data (x-axis), timestamps and strain data for a gage/segment and a time interval.
tuple get_time_series(self, float x=0.0, str name=None, bool is_gage=False)
Get the strain time series for a fixed position.
dict get_metadata(self)
Get the metadata dictionary.
np.array get_tare(self, str name=None, bool is_gage=False)
Returns the values of the tare record (calibration data).
gages
Dictionary of gages Each gage is stored as a sub-dictionary with its name as a key.
list get_y_table(self, str name=None, bool is_gage=False, list record_list=None)
Returns the table of the strain data.
_store_data(self, dict gage_segment, str record_name, str message_type, str sensor_type, list data)
Private method to store the data into the dictionary.
Abstract class, which specifies the basic interfaces a protocol must implement.
dict metadata
Dictionary containting metadata information.
np.array get_x_values(self)
Returns the values of the x-axis record (location data).
list get_y_table(self, *args, **kwargs)
Returns the table of the strain data.
__init__(self, *args, **kwargs)
Constructs a Protocol object.
A single record of the distributed fiber optic strain sensing data.
str to_tsv(self, str itemsep="\t")
This function returns the TSV (tab separated values) representation of this record.
__init__(self, list data, **kwargs)
Constructs a SensorRecord object.
Abstract base class, which deals with superflous constructor arguments.
None extract_time_intervals(str input_file, str output_file, list list_of_time_intervals)
Extracts the parts of the measurement data into smaller .tsv files.