diff --git a/asammdf/blocks/bus_logging_utils.py b/asammdf/blocks/bus_logging_utils.py index 554ccca62..ed39e07cd 100644 --- a/asammdf/blocks/bus_logging_utils.py +++ b/asammdf/blocks/bus_logging_utils.py @@ -7,18 +7,24 @@ def apply_conversion(vals, signal, ignore_value2text_conversion): a, b = float(signal.factor), float(signal.offset) - if signal.values and not ignore_value2text_conversion: + if signal.values: + if ignore_value2text_conversion: + if (a, b) != (1, 0): + vals = vals * a + if b: + vals += b + else: - conv = {} - for i, (val, text) in enumerate(signal.values.items()): - conv[f"upper_{i}"] = val - conv[f"lower_{i}"] = val - conv[f"text_{i}"] = text + conv = {} + for i, (val, text) in enumerate(signal.values.items()): + conv[f"upper_{i}"] = val + conv[f"lower_{i}"] = val + conv[f"text_{i}"] = text - conv["default"] = from_dict({"a": a, "b": b}) + conv["default"] = from_dict({"a": a, "b": b}) - conv = from_dict(conv) - vals = conv.convert(vals) + conv = from_dict(conv) + vals = conv.convert(vals) else: @@ -331,7 +337,7 @@ def extract_mux( "samples": samples if raw else apply_conversion(samples, sig, ignore_value2text_conversion), "t": t_, "invalidation_bits": ( - np.isclose(samples, max_val) + np.isclose(apply_conversion(samples, sig, ignore_value2text_conversion=True), max_val) if len(samples.shape) == 1 else np.zeros(len(samples), dtype=bool) ), diff --git a/asammdf/blocks/cutils.c b/asammdf/blocks/cutils.c index b8b0e674c..1297de848 100644 --- a/asammdf/blocks/cutils.c +++ b/asammdf/blocks/cutils.c @@ -149,17 +149,16 @@ static PyObject* sort_data_block(PyObject* self, PyObject* args) static PyObject* extract(PyObject* self, PyObject* args) { int i=0, count, max=0; - bool is_byte_array; int pos=0; int size; - PyObject *signal_data; + PyObject *signal_data, *is_byte_array; unsigned char *buf; PyArrayObject *vals; PyArray_Descr *descr; void *addr; unsigned char * addr2; - if(!PyArg_ParseTuple(args, "Op", &signal_data, &is_byte_array)) + if(!PyArg_ParseTuple(args, "OO", &signal_data, &is_byte_array)) { snprintf(err_string, 1024, "extract was called with wrong parameters"); PyErr_SetString(PyExc_ValueError, err_string); @@ -180,7 +179,7 @@ static PyObject* extract(PyObject* self, PyObject* args) count++; } - if (is_byte_array) + if (PyObject_IsTrue(is_byte_array)) { npy_intp dims[2]; diff --git a/asammdf/blocks/mdf_common.py b/asammdf/blocks/mdf_common.py index 21c43c0a2..d454d35b7 100644 --- a/asammdf/blocks/mdf_common.py +++ b/asammdf/blocks/mdf_common.py @@ -122,13 +122,22 @@ def _validate_channel_selection( entries = self.channels_db[name] if len(entries) > 1: - message = ( - f'Multiple occurrences for channel "{name}": {entries}. ' - 'Provide both "group" and "index" arguments' - " to select another data group" - ) - logger.exception(message) - raise MdfException(message) + if self._raise_on_mutiple_occurences: + message = ( + f'Multiple occurrences for channel "{name}": {entries}. ' + 'Provide both "group" and "index" arguments' + " to select another data group" + ) + logger.exception(message) + raise MdfException(message) + else: + message = ( + f'Multiple occurrences for channel "{name}": {entries}. ' + 'Returning the first occurence since the MDF obejct was ' + 'configured to not raise an exception in this case.' + ) + logger.warning(message) + gp_nr, ch_nr = entries[0] else: gp_nr, ch_nr = entries[0] @@ -153,12 +162,22 @@ def _validate_channel_selection( raise MdfException(message) else: - message = ( - f'Multiple occurrences for channel "{name}" in group {group}. ' - 'Provide also the "index" argument' - " to select the desired channel" - ) - raise MdfException(message) + if self._raise_on_mutiple_occurences: + message = ( + f'Multiple occurrences for channel "{name}": {entries}. ' + 'Provide both "group" and "index" arguments' + " to select another data group" + ) + logger.exception(message) + raise MdfException(message) + else: + message = ( + f'Multiple occurrences for channel "{name}": {entries}. ' + 'Returning the first occurence since the MDF obejct was ' + 'configured to not raise an exception in this case.' + ) + logger.warning(message) + gp_nr, ch_nr = entries[0] else: if (group, index) in self.channels_db[name]: ch_nr = index diff --git a/asammdf/blocks/mdf_v3.py b/asammdf/blocks/mdf_v3.py index 0079f3fb8..8aec07a0b 100644 --- a/asammdf/blocks/mdf_v3.py +++ b/asammdf/blocks/mdf_v3.py @@ -170,6 +170,11 @@ def __init__(self, name=None, version="3.30", channels=(), **kwargs): self._write_fragment_size = 4 * 2 ** 20 self._single_bit_uint_as_bool = False self._integer_interpolation = 0 + self._float_interpolation = 1 + self._raise_on_multiple_occurrences = True + self._use_display_names = False + self.copy_on_get = False + self.raise_on_multiple_occurrences = True self._si_map = {} self._cc_map = {} @@ -942,15 +947,28 @@ def _read(self, mapped=False): def configure( self, *, + from_other=None, read_fragment_size=None, write_fragment_size=None, use_display_names=None, single_bit_uint_as_bool=None, integer_interpolation=None, copy_on_get=None, + float_interpolation=None, + raise_on_multiple_occurrences=None, ): """configure MDF parameters + The default values for the options are the following: + * read_fragment_size = 0 + * write_fragment_size = 4MB + * use_display_names = False + * single_bit_uint_as_bool = False + * integer_interpolation = 0 (ffill - use previous sample) + * float_interpolation = 1 (linear interpolation) + * copy_on_get = False + * raise_on_multiple_occurrences = True + Parameters ---------- read_fragment_size : int @@ -981,8 +999,37 @@ def configure( copy_on_get : bool copy arrays in the get method + float_interpolation : int + interpolation mode for float channels: + + * 0 - repeat previous sample + * 1 - use linear interpolation + + .. versionadded:: 6.2.0 + + raise_on_multiple_occurrences : bool + raise exception when there are multiple channel occurrences in the file and + the `get` call is ambiguos; default True + + .. versionadded:: 6.2.0 + + from_other : MDF + copy configuration options from other MDF + + .. versionadded:: 6.2.0 + """ + if from_other is not None: + self._read_fragment_size = from_other._read_fragment_size + self._write_fragment_size = from_other._write_fragment_size + self._use_display_names = from_other._use_display_names + self._single_bit_uint_as_bool = from_other._single_bit_uint_as_bool + self._integer_interpolation = from_other._integer_interpolation + self.copy_on_get = from_other.copy_on_get + self._float_interpolation = from_other._float_interpolation + self._raise_on_multiple_occurrences = from_other._raise_on_multiple_occurrences + if read_fragment_size is not None: self._read_fragment_size = int(read_fragment_size) @@ -1001,6 +1048,12 @@ def configure( if copy_on_get is not None: self.copy_on_get = copy_on_get + if float_interpolation in (0, 1): + self._float_interpolation = int(float_interpolation) + + if raise_on_multiple_occurrences is not None: + self._raise_on_multiple_occurrences = bool(raise_on_multiple_occurrences) + def add_trigger(self, group, timestamp, pre_time=0, post_time=0, comment=""): """add trigger to data group @@ -1147,7 +1200,8 @@ def append( return version = self.version - interp_mode = self._integer_interpolation + integer_interp_mode = self._integer_interpolation + float_interp_mode = self._float_interpolation # check if the signals have a common timebase # if not interpolate the signals using the union of all timbases @@ -1165,7 +1219,11 @@ def append( times = [s.timestamps for s in signals] timestamps = unique(concatenate(times)).astype(float64) signals = [ - s.interp(timestamps, interpolation_mode=interp_mode) + s.interp( + timestamps, + integer_interpolation_mode=integer_interp_mode, + float_interpolation_mode=float_interp_mode, + ) for s in signals ] times = None @@ -2829,7 +2887,11 @@ def get( vals = ( Signal(vals, timestamps, name="_") - .interp(t, interpolation_mode=self._integer_interpolation) + .interp( + t, + integer_interpolation_mode=self._integer_interpolation, + float_interpolation_mode=self._float_interpolation + ) .samples ) @@ -2964,7 +3026,11 @@ def get( vals = ( Signal(vals, timestamps, name="_") - .interp(t, interpolation_mode=self._integer_interpolation) + .interp( + t, + integer_interpolation_mode=self._integer_interpolation, + float_interpolation_mode=self._float_interpolation, + ) .samples ) diff --git a/asammdf/blocks/mdf_v4.py b/asammdf/blocks/mdf_v4.py index befa2dad2..c8a456223 100755 --- a/asammdf/blocks/mdf_v4.py +++ b/asammdf/blocks/mdf_v4.py @@ -340,6 +340,7 @@ def __init__(self, name=None, version="4.10", channels=(), **kwargs): self._tempfile = TemporaryFile() self._file = None + self._raise_on_multiple_occurrences = True self._read_fragment_size = 0 * 2 ** 20 self._write_fragment_size = 4 * 2 ** 20 self._use_display_names = kwargs.get("use_display_names", False) @@ -350,8 +351,10 @@ def __init__(self, name=None, version="4.10", channels=(), **kwargs): self._decryption_function = kwargs.get("decryption_function", None) self.copy_on_get = kwargs.get("copy_on_get", True) self.compact_vlsd = kwargs.get("compact_vlsd", False) + self.raise_on_multiple_occurrences = True self._single_bit_uint_as_bool = False self._integer_interpolation = 0 + self._float_interpolation = 1 self.virtual_groups = {} # master group 2 referencing groups self.virtual_groups_map = {} # group index 2 master group @@ -2353,14 +2356,27 @@ def get_invalidation_bits(self, group_index, channel, fragment): def configure( self, *, + from_other=None, read_fragment_size=None, write_fragment_size=None, use_display_names=None, single_bit_uint_as_bool=None, integer_interpolation=None, copy_on_get=None, + float_interpolation=None, + raise_on_multiple_occurrences=None, ): - """configure MDF parameters + """configure MDF parameters. + + The default values for the options are the following: + * read_fragment_size = 0 + * write_fragment_size = 4MB + * use_display_names = False + * single_bit_uint_as_bool = False + * integer_interpolation = 0 (ffill - use previous sample) + * float_interpolation = 1 (linear interpolation) + * copy_on_get = False + * raise_on_multiple_occurrences = True Parameters ---------- @@ -2392,8 +2408,37 @@ def configure( copy_on_get : bool copy arrays in the get method + float_interpolation : int + interpolation mode for float channels: + + * 0 - repeat previous sample + * 1 - use linear interpolation + + .. versionadded:: 6.2.0 + + raise_on_multiple_occurrences : bool + raise exception when there are multiple channel occurrences in the file and + the `get` call is ambiguos; default True + + .. versionadded:: 6.2.0 + + from_other : MDF + copy configuration options from other MDF + + .. versionadded:: 6.2.0 + """ + if from_other is not None: + self._read_fragment_size = from_other._read_fragment_size + self._write_fragment_size = from_other._write_fragment_size + self._use_display_names = from_other._use_display_names + self._single_bit_uint_as_bool = from_other._single_bit_uint_as_bool + self._integer_interpolation = from_other._integer_interpolation + self.copy_on_get = from_other.copy_on_get + self._float_interpolation = from_other._float_interpolation + self.raise_on_multiple_occurrences = from_other.raise_on_multiple_occurrences + if read_fragment_size is not None: self._read_fragment_size = int(read_fragment_size) @@ -2412,6 +2457,12 @@ def configure( if copy_on_get is not None: self.copy_on_get = copy_on_get + if float_interpolation in (0, 1): + self._float_interpolation = int(float_interpolation) + + if raise_on_multiple_occurrences is not None: + self.raise_on_multiple_occurrences = bool(raise_on_multiple_occurrences) + def append( self, signals, @@ -2496,8 +2547,6 @@ def append( if not signals: return - interp_mode = self._integer_interpolation - prepare_record = True # check if the signals have a common timebase @@ -2516,7 +2565,12 @@ def append( times = [s.timestamps for s in signals] t = unique(concatenate(times)).astype(float64) signals = [ - s.interp(t, interpolation_mode=interp_mode) for s in signals + s.interp( + t, + integer_interpolation_mode=self._integer_interpolation, + float_interpolation_mode=self._float_interpolation, + ) + for s in signals ] times = None else: @@ -6368,7 +6422,11 @@ def _get_structure( vals = Signal( vals, timestamps, name="_", invalidation_bits=invalidation_bits - ).interp(t, interpolation_mode=self._integer_interpolation) + ).interp( + t, + integer_interpolation_mode=self._integer_interpolation, + float_interpolation_mode=self._float_interpolation, + ) vals, timestamps, invalidation_bits = ( vals.samples, @@ -6669,7 +6727,11 @@ def _get_array( vals = Signal( vals, timestamps, name="_", invalidation_bits=invalidation_bits - ).interp(t, interpolation_mode=self._integer_interpolation) + ).interp( + t, + integer_interpolation_mode=self._integer_interpolation, + float_interpolation_mode=self._float_interpolation, + ) vals, timestamps, invalidation_bits = ( vals.samples, @@ -6811,7 +6873,11 @@ def _get_scalar( vals = Signal( vals, timestamps, name="_", invalidation_bits=invalidation_bits - ).interp(t, interpolation_mode=self._integer_interpolation) + ).interp( + t, + integer_interpolation_mode=self._integer_interpolation, + float_interpolation_mode=self._float_interpolation, + ) vals, timestamps, invalidation_bits = ( vals.samples, @@ -7098,7 +7164,11 @@ def _get_scalar( vals = Signal( vals, timestamps, name="_", invalidation_bits=invalidation_bits - ).interp(t, interpolation_mode=self._integer_interpolation) + ).interp( + t, + integer_interpolation_mode=self._integer_interpolation, + float_interpolation_mode=self._float_interpolation, + ) vals, timestamps, invalidation_bits = ( vals.samples, diff --git a/asammdf/blocks/utils.py b/asammdf/blocks/utils.py index fa145573d..448438394 100644 --- a/asammdf/blocks/utils.py +++ b/asammdf/blocks/utils.py @@ -197,14 +197,16 @@ def get_text_v3(address, stream, mapped=False, decode=True): if block_id != b"TX": return "" if decode else b"" (size,) = UINT16_uf(stream, address + 2) - text_bytes = stream[address + 4 : address + size].strip(b" \r\t\n\0") + text_bytes = ( + stream[address + 4 : address + size].split(b"\0")[0].rstrip(b" \r\t\n\0") + ) else: stream.seek(address) block_id = stream.read(2) if block_id != b"TX": return "" if decode else b"" size = UINT16_u(stream.read(2))[0] - 4 - text_bytes = stream.read(size).strip(b" \r\t\n\0") + text_bytes = stream.read(size).split(b"\0")[0].rstrip(b" \r\t\n\0") if decode: try: text = text_bytes.decode("latin-1") @@ -244,13 +246,15 @@ def get_text_v4(address, stream, mapped=False, decode=True): block_id, size = BLK_COMMON_uf(stream, address) if block_id not in (b"##TX", b"##MD"): return "" if decode else b"" - text_bytes = stream[address + 24 : address + size].strip(b" \r\t\n\0") + text_bytes = ( + stream[address + 24 : address + size].split(b"\0")[0].rstrip(b" \r\t\n\0") + ) else: stream.seek(address) block_id, size = BLK_COMMON_u(stream.read(24)) if block_id not in (b"##TX", b"##MD"): return "" if decode else b"" - text_bytes = stream.read(size - 24).strip(b" \r\t\n\0") + text_bytes = stream.read(size - 24).split(b"\0")[0].rstrip(b" \r\t\n\0") if decode: try: diff --git a/asammdf/gui/ui/batch_widget.py b/asammdf/gui/ui/batch_widget.py index 51762774a..cea58821e 100644 --- a/asammdf/gui/ui/batch_widget.py +++ b/asammdf/gui/ui/batch_widget.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'batch_widget.ui' +# Form implementation generated from reading ui file 'batch_widget.UI' # # Created by: PyQt5 UI code generator 5.15.1 # @@ -377,6 +377,82 @@ def setupUi(self, batch_widget): self.raw_mat.setObjectName("raw_mat") self.gridLayout_18.addWidget(self.raw_mat, 3, 0, 1, 1) self.output_options.addWidget(self.MAT_2) + self.CSV = QtWidgets.QWidget() + self.CSV.setObjectName("CSV") + self.gridLayout_2 = QtWidgets.QGridLayout(self.CSV) + self.gridLayout_2.setContentsMargins(2, 2, 2, 2) + self.gridLayout_2.setSpacing(2) + self.gridLayout_2.setObjectName("gridLayout_2") + self.single_time_base_csv = QtWidgets.QCheckBox(self.CSV) + self.single_time_base_csv.setObjectName("single_time_base_csv") + self.gridLayout_2.addWidget(self.single_time_base_csv, 0, 0, 1, 2) + self.time_from_zero_csv = QtWidgets.QCheckBox(self.CSV) + self.time_from_zero_csv.setObjectName("time_from_zero_csv") + self.gridLayout_2.addWidget(self.time_from_zero_csv, 1, 0, 1, 2) + self.time_as_date_csv = QtWidgets.QCheckBox(self.CSV) + self.time_as_date_csv.setObjectName("time_as_date_csv") + self.gridLayout_2.addWidget(self.time_as_date_csv, 2, 0, 1, 2) + self.raw_csv = QtWidgets.QCheckBox(self.CSV) + self.raw_csv.setObjectName("raw_csv") + self.gridLayout_2.addWidget(self.raw_csv, 3, 0, 1, 2) + self.line_34 = QtWidgets.QFrame(self.CSV) + self.line_34.setFrameShape(QtWidgets.QFrame.HLine) + self.line_34.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_34.setObjectName("line_34") + self.gridLayout_2.addWidget(self.line_34, 4, 0, 1, 2) + self.use_display_names_csv = QtWidgets.QCheckBox(self.CSV) + self.use_display_names_csv.setObjectName("use_display_names_csv") + self.gridLayout_2.addWidget(self.use_display_names_csv, 5, 0, 1, 2) + self.label_67 = QtWidgets.QLabel(self.CSV) + self.label_67.setObjectName("label_67") + self.gridLayout_2.addWidget(self.label_67, 6, 0, 1, 1) + self.empty_channels_csv = QtWidgets.QComboBox(self.CSV) + self.empty_channels_csv.setObjectName("empty_channels_csv") + self.gridLayout_2.addWidget(self.empty_channels_csv, 6, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.CSV) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 7, 0, 1, 1) + self.delimiter = QtWidgets.QLineEdit(self.CSV) + self.delimiter.setMaxLength(1) + self.delimiter.setClearButtonEnabled(False) + self.delimiter.setObjectName("delimiter") + self.gridLayout_2.addWidget(self.delimiter, 7, 1, 1, 1) + self.doublequote = QtWidgets.QCheckBox(self.CSV) + self.doublequote.setChecked(True) + self.doublequote.setObjectName("doublequote") + self.gridLayout_2.addWidget(self.doublequote, 8, 0, 1, 1) + self.label_15 = QtWidgets.QLabel(self.CSV) + self.label_15.setObjectName("label_15") + self.gridLayout_2.addWidget(self.label_15, 9, 0, 1, 1) + self.escapechar = QtWidgets.QLineEdit(self.CSV) + self.escapechar.setInputMask("") + self.escapechar.setMaxLength(1) + self.escapechar.setObjectName("escapechar") + self.gridLayout_2.addWidget(self.escapechar, 9, 1, 1, 1) + self.label_4 = QtWidgets.QLabel(self.CSV) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 10, 0, 1, 1) + self.lineterminator = QtWidgets.QLineEdit(self.CSV) + self.lineterminator.setObjectName("lineterminator") + self.gridLayout_2.addWidget(self.lineterminator, 10, 1, 1, 1) + self.label_5 = QtWidgets.QLabel(self.CSV) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 11, 0, 1, 1) + self.quotechar = QtWidgets.QLineEdit(self.CSV) + self.quotechar.setMaxLength(1) + self.quotechar.setObjectName("quotechar") + self.gridLayout_2.addWidget(self.quotechar, 11, 1, 1, 1) + self.label_6 = QtWidgets.QLabel(self.CSV) + self.label_6.setObjectName("label_6") + self.gridLayout_2.addWidget(self.label_6, 12, 0, 1, 1) + self.quoting = QtWidgets.QComboBox(self.CSV) + self.quoting.setObjectName("quoting") + self.quoting.addItem("") + self.quoting.addItem("") + self.quoting.addItem("") + self.quoting.addItem("") + self.gridLayout_2.addWidget(self.quoting, 12, 1, 1, 1) + self.output_options.addWidget(self.CSV) self.verticalLayout_21.addWidget(self.output_options) self.verticalLayout_15.addWidget(self.groupBox_11) self.groupBox = QtWidgets.QGroupBox(self.convert_tab) @@ -478,42 +554,85 @@ def setupUi(self, batch_widget): self.gridLayout_10.setContentsMargins(2, 2, 2, 2) self.gridLayout_10.setSpacing(2) self.gridLayout_10.setObjectName("gridLayout_10") + self.ignore_invalid_signals_csv = QtWidgets.QCheckBox(self.groupBox_3) + self.ignore_invalid_signals_csv.setObjectName("ignore_invalid_signals_csv") + self.gridLayout_10.addWidget(self.ignore_invalid_signals_csv, 3, 1, 1, 1) self.time_from_zero_bus = QtWidgets.QCheckBox(self.groupBox_3) self.time_from_zero_bus.setObjectName("time_from_zero_bus") self.gridLayout_10.addWidget(self.time_from_zero_bus, 1, 1, 1, 1) + self.empty_channels_bus = QtWidgets.QComboBox(self.groupBox_3) + self.empty_channels_bus.setObjectName("empty_channels_bus") + self.gridLayout_10.addWidget(self.empty_channels_bus, 5, 2, 1, 2) + self.label_8 = QtWidgets.QLabel(self.groupBox_3) + self.label_8.setObjectName("label_8") + self.gridLayout_10.addWidget(self.label_8, 6, 1, 1, 1) self.label_28 = QtWidgets.QLabel(self.groupBox_3) self.label_28.setObjectName("label_28") self.gridLayout_10.addWidget(self.label_28, 4, 1, 1, 1) - self.line_13 = QtWidgets.QFrame(self.groupBox_3) - self.line_13.setFrameShape(QtWidgets.QFrame.HLine) - self.line_13.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line_13.setObjectName("line_13") - self.gridLayout_10.addWidget(self.line_13, 7, 1, 1, 3) - self.export_raster_bus = QtWidgets.QDoubleSpinBox(self.groupBox_3) - self.export_raster_bus.setDecimals(6) - self.export_raster_bus.setObjectName("export_raster_bus") - self.gridLayout_10.addWidget(self.export_raster_bus, 4, 2, 1, 2) self.extract_bus_csv_btn = QtWidgets.QPushButton(self.groupBox_3) icon9 = QtGui.QIcon() icon9.addPixmap(QtGui.QPixmap(":/csv.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.extract_bus_csv_btn.setIcon(icon9) self.extract_bus_csv_btn.setObjectName("extract_bus_csv_btn") - self.gridLayout_10.addWidget(self.extract_bus_csv_btn, 8, 1, 1, 3) + self.gridLayout_10.addWidget(self.extract_bus_csv_btn, 13, 1, 1, 3) + self.export_raster_bus = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.export_raster_bus.setDecimals(6) + self.export_raster_bus.setObjectName("export_raster_bus") + self.gridLayout_10.addWidget(self.export_raster_bus, 4, 2, 1, 2) + self.lineterminator_bus = QtWidgets.QLineEdit(self.groupBox_3) + self.lineterminator_bus.setObjectName("lineterminator_bus") + self.gridLayout_10.addWidget(self.lineterminator_bus, 9, 2, 1, 1) + self.label_14 = QtWidgets.QLabel(self.groupBox_3) + self.label_14.setObjectName("label_14") + self.gridLayout_10.addWidget(self.label_14, 10, 1, 1, 1) + self.quotechar_bus = QtWidgets.QLineEdit(self.groupBox_3) + self.quotechar_bus.setMaxLength(1) + self.quotechar_bus.setObjectName("quotechar_bus") + self.gridLayout_10.addWidget(self.quotechar_bus, 10, 2, 1, 1) self.single_time_base_bus = QtWidgets.QCheckBox(self.groupBox_3) self.single_time_base_bus.setObjectName("single_time_base_bus") self.gridLayout_10.addWidget(self.single_time_base_bus, 0, 1, 1, 1) - self.ignore_invalid_signals_csv = QtWidgets.QCheckBox(self.groupBox_3) - self.ignore_invalid_signals_csv.setObjectName("ignore_invalid_signals_csv") - self.gridLayout_10.addWidget(self.ignore_invalid_signals_csv, 3, 1, 1, 1) - self.empty_channels_bus = QtWidgets.QComboBox(self.groupBox_3) - self.empty_channels_bus.setObjectName("empty_channels_bus") - self.gridLayout_10.addWidget(self.empty_channels_bus, 5, 2, 1, 2) self.label_29 = QtWidgets.QLabel(self.groupBox_3) self.label_29.setObjectName("label_29") self.gridLayout_10.addWidget(self.label_29, 5, 1, 1, 1) + self.doublequote_bus = QtWidgets.QCheckBox(self.groupBox_3) + self.doublequote_bus.setChecked(True) + self.doublequote_bus.setObjectName("doublequote_bus") + self.gridLayout_10.addWidget(self.doublequote_bus, 7, 1, 1, 1) + self.delimiter_bus = QtWidgets.QLineEdit(self.groupBox_3) + self.delimiter_bus.setMaxLength(1) + self.delimiter_bus.setClearButtonEnabled(False) + self.delimiter_bus.setObjectName("delimiter_bus") + self.gridLayout_10.addWidget(self.delimiter_bus, 6, 2, 1, 1) + self.line_13 = QtWidgets.QFrame(self.groupBox_3) + self.line_13.setFrameShape(QtWidgets.QFrame.HLine) + self.line_13.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_13.setObjectName("line_13") + self.gridLayout_10.addWidget(self.line_13, 12, 1, 1, 3) + self.escapechar_bus = QtWidgets.QLineEdit(self.groupBox_3) + self.escapechar_bus.setInputMask("") + self.escapechar_bus.setMaxLength(1) + self.escapechar_bus.setObjectName("escapechar_bus") + self.gridLayout_10.addWidget(self.escapechar_bus, 8, 2, 1, 1) self.bus_time_as_date = QtWidgets.QCheckBox(self.groupBox_3) self.bus_time_as_date.setObjectName("bus_time_as_date") self.gridLayout_10.addWidget(self.bus_time_as_date, 2, 1, 1, 1) + self.label_13 = QtWidgets.QLabel(self.groupBox_3) + self.label_13.setObjectName("label_13") + self.gridLayout_10.addWidget(self.label_13, 9, 1, 1, 1) + self.label_9 = QtWidgets.QLabel(self.groupBox_3) + self.label_9.setObjectName("label_9") + self.gridLayout_10.addWidget(self.label_9, 8, 1, 1, 1) + self.label_7 = QtWidgets.QLabel(self.groupBox_3) + self.label_7.setObjectName("label_7") + self.gridLayout_10.addWidget(self.label_7, 11, 1, 1, 1) + self.quoting_bus = QtWidgets.QComboBox(self.groupBox_3) + self.quoting_bus.setObjectName("quoting_bus") + self.quoting_bus.addItem("") + self.quoting_bus.addItem("") + self.quoting_bus.addItem("") + self.quoting_bus.addItem("") + self.gridLayout_10.addWidget(self.quoting_bus, 11, 2, 1, 1) self.gridLayout_11.addWidget(self.groupBox_3, 2, 1, 1, 1) self.groupBox_2 = QtWidgets.QGroupBox(self.extract_bus_tab) self.groupBox_2.setObjectName("groupBox_2") @@ -589,8 +708,10 @@ def setupUi(self, batch_widget): self.gridLayout_9.addWidget(self.splitter, 1, 0, 1, 1) self.retranslateUi(batch_widget) - self.aspects.setCurrentIndex(0) + self.aspects.setCurrentIndex(1) self.output_options.setCurrentIndex(0) + self.quoting.setCurrentIndex(1) + self.quoting_bus.setCurrentIndex(1) self.tabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(batch_widget) @@ -662,6 +783,26 @@ def retranslateUi(self, batch_widget): self.time_as_date_mat.setText(_translate("batch_widget", "Time as date")) self.label_74.setText(_translate("batch_widget", "Empty channels")) self.raw_mat.setText(_translate("batch_widget", "Raw values")) + self.single_time_base_csv.setText(_translate("batch_widget", "Single time base")) + self.time_from_zero_csv.setText(_translate("batch_widget", "Time from 0s")) + self.time_as_date_csv.setText(_translate("batch_widget", "Time as date")) + self.raw_csv.setText(_translate("batch_widget", "Raw values")) + self.use_display_names_csv.setText(_translate("batch_widget", "Use display names")) + self.label_67.setText(_translate("batch_widget", "Empty channels")) + self.label_2.setText(_translate("batch_widget", "Delimiter")) + self.delimiter.setText(_translate("batch_widget", ",")) + self.doublequote.setText(_translate("batch_widget", "Double quote")) + self.label_15.setText(_translate("batch_widget", "Escape Char")) + self.escapechar.setPlaceholderText(_translate("batch_widget", "None")) + self.label_4.setText(_translate("batch_widget", "Line Terminator")) + self.lineterminator.setText(_translate("batch_widget", "\\r\\n")) + self.label_5.setText(_translate("batch_widget", "Quote Char")) + self.quotechar.setText(_translate("batch_widget", "\"")) + self.label_6.setText(_translate("batch_widget", "Quoting")) + self.quoting.setItemText(0, _translate("batch_widget", "ALL")) + self.quoting.setItemText(1, _translate("batch_widget", "MINIMAL")) + self.quoting.setItemText(2, _translate("batch_widget", "NONNUMERIC")) + self.quoting.setItemText(3, _translate("batch_widget", "NONE")) self.groupBox.setTitle(_translate("batch_widget", "Output folder")) self.modify_output_folder.setPlaceholderText(_translate("batch_widget", "please select an output folder")) self.apply_btn.setText(_translate("batch_widget", "Apply")) @@ -677,15 +818,29 @@ def retranslateUi(self, batch_widget): self.stack_btn.setText(_translate("batch_widget", "Stack")) self.aspects.setTabText(self.aspects.indexOf(self.stack_tab), _translate("batch_widget", "Stack")) self.groupBox_3.setTitle(_translate("batch_widget", "CSV")) + self.ignore_invalid_signals_csv.setToolTip(_translate("batch_widget", "checks if all samples are eauql to the maximum teoretical signal value")) + self.ignore_invalid_signals_csv.setText(_translate("batch_widget", "Ignore invalid signals")) self.time_from_zero_bus.setText(_translate("batch_widget", "Time from 0s")) + self.label_8.setText(_translate("batch_widget", "Delimiter")) self.label_28.setText(_translate("batch_widget", "Raster")) - self.export_raster_bus.setSuffix(_translate("batch_widget", "s")) self.extract_bus_csv_btn.setText(_translate("batch_widget", "Export to CSV ")) + self.export_raster_bus.setSuffix(_translate("batch_widget", "s")) + self.lineterminator_bus.setText(_translate("batch_widget", "\\r\\n")) + self.label_14.setText(_translate("batch_widget", "Quote Char")) + self.quotechar_bus.setText(_translate("batch_widget", "\"")) self.single_time_base_bus.setText(_translate("batch_widget", "Single time base")) - self.ignore_invalid_signals_csv.setToolTip(_translate("batch_widget", "checks if all samples are eauql to the maximum teoretical signal value")) - self.ignore_invalid_signals_csv.setText(_translate("batch_widget", "Ignore invalid signals")) self.label_29.setText(_translate("batch_widget", "Empty channels")) + self.doublequote_bus.setText(_translate("batch_widget", "Double quote")) + self.delimiter_bus.setText(_translate("batch_widget", ",")) + self.escapechar_bus.setPlaceholderText(_translate("batch_widget", "None")) self.bus_time_as_date.setText(_translate("batch_widget", "Time as date")) + self.label_13.setText(_translate("batch_widget", "Line Terminator")) + self.label_9.setText(_translate("batch_widget", "Escape Char")) + self.label_7.setText(_translate("batch_widget", "Quoting")) + self.quoting_bus.setItemText(0, _translate("batch_widget", "ALL")) + self.quoting_bus.setItemText(1, _translate("batch_widget", "MINIMAL")) + self.quoting_bus.setItemText(2, _translate("batch_widget", "NONNUMERIC")) + self.quoting_bus.setItemText(3, _translate("batch_widget", "NONE")) self.groupBox_2.setTitle(_translate("batch_widget", "MDF")) self.extract_bus_btn.setText(_translate("batch_widget", "Extract Bus signals")) self.label__1.setText(_translate("batch_widget", "Compression")) diff --git a/asammdf/gui/ui/batch_widget.ui b/asammdf/gui/ui/batch_widget.ui index 03de48a1a..accc7e0f4 100644 --- a/asammdf/gui/ui/batch_widget.ui +++ b/asammdf/gui/ui/batch_widget.ui @@ -24,7 +24,7 @@ QTabWidget::West - 0 + 1 false @@ -871,6 +871,192 @@ + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Single time base + + + + + + + Time from 0s + + + + + + + Time as date + + + + + + + Raw values + + + + + + + Qt::Horizontal + + + + + + + Use display names + + + + + + + Empty channels + + + + + + + + + + Delimiter + + + + + + + , + + + 1 + + + false + + + + + + + Double quote + + + true + + + + + + + Escape Char + + + + + + + + + + 1 + + + None + + + + + + + Line Terminator + + + + + + + \r\n + + + + + + + Quote Char + + + + + + + " + + + 1 + + + + + + + Quoting + + + + + + + 1 + + + + ALL + + + + + MINIMAL + + + + + NONNUMERIC + + + + + NONE + + + + + + @@ -1102,6 +1288,16 @@ 2 + + + + checks if all samples are eauql to the maximum teoretical signal value + + + Ignore invalid signals + + + @@ -1109,6 +1305,16 @@ + + + + + + + Delimiter + + + @@ -1116,10 +1322,14 @@ - - - - Qt::Horizontal + + + + Export to CSV + + + + :/csv.png:/csv.png @@ -1133,14 +1343,27 @@ - - + + - Export to CSV + \r\n - - - :/csv.png:/csv.png + + + + + + Quote Char + + + + + + + " + + + 1 @@ -1151,23 +1374,53 @@ - - - - checks if all samples are eauql to the maximum teoretical signal value - + + - Ignore invalid signals + Empty channels - - + + + + Double quote + + + true + + - - + + - Empty channels + , + + + 1 + + + false + + + + + + + Qt::Horizontal + + + + + + + + + + 1 + + + None @@ -1178,6 +1431,54 @@ + + + + Line Terminator + + + + + + + Escape Char + + + + + + + Quoting + + + + + + + 1 + + + + ALL + + + + + MINIMAL + + + + + NONNUMERIC + + + + + NONE + + + + diff --git a/asammdf/gui/ui/file_widget.py b/asammdf/gui/ui/file_widget.py index eaf8cd9dc..786e25db1 100644 --- a/asammdf/gui/ui/file_widget.py +++ b/asammdf/gui/ui/file_widget.py @@ -381,6 +381,82 @@ def setupUi(self, file_widget): self.raw_mat.setObjectName("raw_mat") self.gridLayout_3.addWidget(self.raw_mat, 3, 0, 1, 1) self.output_options.addWidget(self.MAT) + self.CSV = QtWidgets.QWidget() + self.CSV.setObjectName("CSV") + self.gridLayout = QtWidgets.QGridLayout(self.CSV) + self.gridLayout.setContentsMargins(2, 2, 2, 2) + self.gridLayout.setSpacing(2) + self.gridLayout.setObjectName("gridLayout") + self.quotechar = QtWidgets.QLineEdit(self.CSV) + self.quotechar.setMaxLength(1) + self.quotechar.setObjectName("quotechar") + self.gridLayout.addWidget(self.quotechar, 11, 1, 1, 2) + self.label_5 = QtWidgets.QLabel(self.CSV) + self.label_5.setObjectName("label_5") + self.gridLayout.addWidget(self.label_5, 11, 0, 1, 1) + self.use_display_names_csv = QtWidgets.QCheckBox(self.CSV) + self.use_display_names_csv.setObjectName("use_display_names_csv") + self.gridLayout.addWidget(self.use_display_names_csv, 5, 0, 1, 3) + self.single_time_base_csv = QtWidgets.QCheckBox(self.CSV) + self.single_time_base_csv.setObjectName("single_time_base_csv") + self.gridLayout.addWidget(self.single_time_base_csv, 0, 0, 1, 3) + self.lineterminator = QtWidgets.QLineEdit(self.CSV) + self.lineterminator.setObjectName("lineterminator") + self.gridLayout.addWidget(self.lineterminator, 10, 1, 1, 2) + self.time_as_date_csv = QtWidgets.QCheckBox(self.CSV) + self.time_as_date_csv.setObjectName("time_as_date_csv") + self.gridLayout.addWidget(self.time_as_date_csv, 2, 0, 1, 3) + self.raw_csv = QtWidgets.QCheckBox(self.CSV) + self.raw_csv.setObjectName("raw_csv") + self.gridLayout.addWidget(self.raw_csv, 3, 0, 1, 3) + self.label_2 = QtWidgets.QLabel(self.CSV) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 7, 0, 1, 1) + self.label_3 = QtWidgets.QLabel(self.CSV) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 9, 0, 1, 1) + self.escapechar = QtWidgets.QLineEdit(self.CSV) + self.escapechar.setInputMask("") + self.escapechar.setMaxLength(1) + self.escapechar.setObjectName("escapechar") + self.gridLayout.addWidget(self.escapechar, 9, 1, 1, 2) + self.line_32 = QtWidgets.QFrame(self.CSV) + self.line_32.setFrameShape(QtWidgets.QFrame.HLine) + self.line_32.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_32.setObjectName("line_32") + self.gridLayout.addWidget(self.line_32, 4, 0, 1, 3) + self.time_from_zero_csv = QtWidgets.QCheckBox(self.CSV) + self.time_from_zero_csv.setObjectName("time_from_zero_csv") + self.gridLayout.addWidget(self.time_from_zero_csv, 1, 0, 1, 3) + self.label_4 = QtWidgets.QLabel(self.CSV) + self.label_4.setObjectName("label_4") + self.gridLayout.addWidget(self.label_4, 10, 0, 1, 1) + self.doublequote = QtWidgets.QCheckBox(self.CSV) + self.doublequote.setChecked(True) + self.doublequote.setObjectName("doublequote") + self.gridLayout.addWidget(self.doublequote, 8, 0, 1, 1) + self.label_6 = QtWidgets.QLabel(self.CSV) + self.label_6.setObjectName("label_6") + self.gridLayout.addWidget(self.label_6, 12, 0, 1, 1) + self.quoting = QtWidgets.QComboBox(self.CSV) + self.quoting.setObjectName("quoting") + self.quoting.addItem("") + self.quoting.addItem("") + self.quoting.addItem("") + self.quoting.addItem("") + self.gridLayout.addWidget(self.quoting, 12, 1, 1, 2) + self.delimiter = QtWidgets.QLineEdit(self.CSV) + self.delimiter.setMaxLength(1) + self.delimiter.setClearButtonEnabled(False) + self.delimiter.setObjectName("delimiter") + self.gridLayout.addWidget(self.delimiter, 7, 1, 1, 2) + self.label_66 = QtWidgets.QLabel(self.CSV) + self.label_66.setObjectName("label_66") + self.gridLayout.addWidget(self.label_66, 6, 0, 1, 1) + self.empty_channels_csv = QtWidgets.QComboBox(self.CSV) + self.empty_channels_csv.setObjectName("empty_channels_csv") + self.gridLayout.addWidget(self.empty_channels_csv, 6, 1, 1, 2) + self.output_options.addWidget(self.CSV) self.verticalLayout_20.addWidget(self.output_options) self.verticalLayout_3.addWidget(self.groupBox_10) spacerItem6 = QtWidgets.QSpacerItem(20, 2, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) @@ -402,46 +478,90 @@ def setupUi(self, file_widget): self.gridLayout_8.setObjectName("gridLayout_8") self.groupBox_3 = QtWidgets.QGroupBox(self.extract_bus_tab) self.groupBox_3.setObjectName("groupBox_3") - self.gridLayout_7 = QtWidgets.QGridLayout(self.groupBox_3) - self.gridLayout_7.setContentsMargins(2, 2, 2, 2) - self.gridLayout_7.setSpacing(2) - self.gridLayout_7.setObjectName("gridLayout_7") - self.line_13 = QtWidgets.QFrame(self.groupBox_3) - self.line_13.setFrameShape(QtWidgets.QFrame.HLine) - self.line_13.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line_13.setObjectName("line_13") - self.gridLayout_7.addWidget(self.line_13, 7, 1, 1, 3) - self.label_25 = QtWidgets.QLabel(self.groupBox_3) - self.label_25.setObjectName("label_25") - self.gridLayout_7.addWidget(self.label_25, 5, 1, 1, 1) + self.gridLayout_6 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_6.setContentsMargins(-1, 2, 2, 2) + self.gridLayout_6.setSpacing(2) + self.gridLayout_6.setObjectName("gridLayout_6") + self.label_10 = QtWidgets.QLabel(self.groupBox_3) + self.label_10.setObjectName("label_10") + self.gridLayout_6.addWidget(self.label_10, 11, 0, 1, 1) self.single_time_base_bus = QtWidgets.QCheckBox(self.groupBox_3) self.single_time_base_bus.setObjectName("single_time_base_bus") - self.gridLayout_7.addWidget(self.single_time_base_bus, 0, 1, 1, 1) + self.gridLayout_6.addWidget(self.single_time_base_bus, 0, 0, 1, 4) + self.doublequote_bus = QtWidgets.QCheckBox(self.groupBox_3) + self.doublequote_bus.setChecked(True) + self.doublequote_bus.setObjectName("doublequote_bus") + self.gridLayout_6.addWidget(self.doublequote_bus, 7, 0, 1, 1) self.label_23 = QtWidgets.QLabel(self.groupBox_3) self.label_23.setObjectName("label_23") - self.gridLayout_7.addWidget(self.label_23, 4, 1, 1, 1) + self.gridLayout_6.addWidget(self.label_23, 4, 0, 1, 1) + self.label_25 = QtWidgets.QLabel(self.groupBox_3) + self.label_25.setObjectName("label_25") + self.gridLayout_6.addWidget(self.label_25, 5, 0, 1, 2) + self.label_9 = QtWidgets.QLabel(self.groupBox_3) + self.label_9.setObjectName("label_9") + self.gridLayout_6.addWidget(self.label_9, 8, 0, 1, 1) + self.label_7 = QtWidgets.QLabel(self.groupBox_3) + self.label_7.setObjectName("label_7") + self.gridLayout_6.addWidget(self.label_7, 12, 0, 1, 1) + self.label_11 = QtWidgets.QLabel(self.groupBox_3) + self.label_11.setObjectName("label_11") + self.gridLayout_6.addWidget(self.label_11, 10, 0, 1, 2) + self.bus_time_as_date = QtWidgets.QCheckBox(self.groupBox_3) + self.bus_time_as_date.setObjectName("bus_time_as_date") + self.gridLayout_6.addWidget(self.bus_time_as_date, 2, 0, 1, 3) + self.time_from_zero_bus = QtWidgets.QCheckBox(self.groupBox_3) + self.time_from_zero_bus.setObjectName("time_from_zero_bus") + self.gridLayout_6.addWidget(self.time_from_zero_bus, 1, 0, 1, 3) + self.label_8 = QtWidgets.QLabel(self.groupBox_3) + self.label_8.setObjectName("label_8") + self.gridLayout_6.addWidget(self.label_8, 6, 0, 1, 1) + self.ignore_invalid_signals_csv = QtWidgets.QCheckBox(self.groupBox_3) + self.ignore_invalid_signals_csv.setObjectName("ignore_invalid_signals_csv") + self.gridLayout_6.addWidget(self.ignore_invalid_signals_csv, 3, 0, 1, 3) self.export_raster_bus = QtWidgets.QDoubleSpinBox(self.groupBox_3) self.export_raster_bus.setDecimals(6) self.export_raster_bus.setObjectName("export_raster_bus") - self.gridLayout_7.addWidget(self.export_raster_bus, 4, 2, 1, 2) - self.ignore_invalid_signals_csv = QtWidgets.QCheckBox(self.groupBox_3) - self.ignore_invalid_signals_csv.setObjectName("ignore_invalid_signals_csv") - self.gridLayout_7.addWidget(self.ignore_invalid_signals_csv, 3, 1, 1, 1) + self.gridLayout_6.addWidget(self.export_raster_bus, 4, 3, 1, 1) self.empty_channels_bus = QtWidgets.QComboBox(self.groupBox_3) self.empty_channels_bus.setObjectName("empty_channels_bus") - self.gridLayout_7.addWidget(self.empty_channels_bus, 5, 2, 1, 2) - self.time_from_zero_bus = QtWidgets.QCheckBox(self.groupBox_3) - self.time_from_zero_bus.setObjectName("time_from_zero_bus") - self.gridLayout_7.addWidget(self.time_from_zero_bus, 1, 1, 1, 1) - self.bus_time_as_date = QtWidgets.QCheckBox(self.groupBox_3) - self.bus_time_as_date.setObjectName("bus_time_as_date") - self.gridLayout_7.addWidget(self.bus_time_as_date, 2, 1, 1, 1) + self.gridLayout_6.addWidget(self.empty_channels_bus, 5, 3, 1, 1) + self.delimiter_bus = QtWidgets.QLineEdit(self.groupBox_3) + self.delimiter_bus.setMaxLength(1) + self.delimiter_bus.setClearButtonEnabled(False) + self.delimiter_bus.setObjectName("delimiter_bus") + self.gridLayout_6.addWidget(self.delimiter_bus, 6, 3, 1, 1) + self.escapechar_bus = QtWidgets.QLineEdit(self.groupBox_3) + self.escapechar_bus.setInputMask("") + self.escapechar_bus.setMaxLength(1) + self.escapechar_bus.setObjectName("escapechar_bus") + self.gridLayout_6.addWidget(self.escapechar_bus, 8, 3, 1, 1) + self.lineterminator_bus = QtWidgets.QLineEdit(self.groupBox_3) + self.lineterminator_bus.setObjectName("lineterminator_bus") + self.gridLayout_6.addWidget(self.lineterminator_bus, 10, 3, 1, 1) + self.quotechar_bus = QtWidgets.QLineEdit(self.groupBox_3) + self.quotechar_bus.setMaxLength(1) + self.quotechar_bus.setObjectName("quotechar_bus") + self.gridLayout_6.addWidget(self.quotechar_bus, 11, 3, 1, 1) + self.quoting_bus = QtWidgets.QComboBox(self.groupBox_3) + self.quoting_bus.setObjectName("quoting_bus") + self.quoting_bus.addItem("") + self.quoting_bus.addItem("") + self.quoting_bus.addItem("") + self.quoting_bus.addItem("") + self.gridLayout_6.addWidget(self.quoting_bus, 12, 3, 1, 1) self.extract_bus_csv_btn = QtWidgets.QPushButton(self.groupBox_3) icon8 = QtGui.QIcon() icon8.addPixmap(QtGui.QPixmap(":/csv.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.extract_bus_csv_btn.setIcon(icon8) self.extract_bus_csv_btn.setObjectName("extract_bus_csv_btn") - self.gridLayout_7.addWidget(self.extract_bus_csv_btn, 8, 1, 1, 3) + self.gridLayout_6.addWidget(self.extract_bus_csv_btn, 14, 3, 1, 1) + self.line_13 = QtWidgets.QFrame(self.groupBox_3) + self.line_13.setFrameShape(QtWidgets.QFrame.HLine) + self.line_13.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_13.setObjectName("line_13") + self.gridLayout_6.addWidget(self.line_13, 13, 0, 1, 4) + self.gridLayout_6.setColumnStretch(0, 1) self.gridLayout_8.addWidget(self.groupBox_3, 3, 1, 1, 1) self.output_info_bus = QtWidgets.QTextEdit(self.extract_bus_tab) self.output_info_bus.setReadOnly(True) @@ -453,38 +573,40 @@ def setupUi(self, file_widget): self.gridLayout_5.setContentsMargins(2, 2, 2, 2) self.gridLayout_5.setSpacing(2) self.gridLayout_5.setObjectName("gridLayout_5") + self.label_26 = QtWidgets.QLabel(self.groupBox_2) + self.label_26.setText("") + self.label_26.setObjectName("label_26") + self.gridLayout_5.addWidget(self.label_26, 3, 1, 1, 1) self.extract_bus_btn = QtWidgets.QPushButton(self.groupBox_2) icon9 = QtGui.QIcon() icon9.addPixmap(QtGui.QPixmap(":/down.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.extract_bus_btn.setIcon(icon9) self.extract_bus_btn.setObjectName("extract_bus_btn") - self.gridLayout_5.addWidget(self.extract_bus_btn, 5, 0, 1, 2) + self.gridLayout_5.addWidget(self.extract_bus_btn, 5, 1, 1, 2) + spacerItem7 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_5.addItem(spacerItem7, 5, 0, 1, 1) self.label__1 = QtWidgets.QLabel(self.groupBox_2) self.label__1.setObjectName("label__1") self.gridLayout_5.addWidget(self.label__1, 0, 0, 1, 1) + self.label_24 = QtWidgets.QLabel(self.groupBox_2) + self.label_24.setObjectName("label_24") + self.gridLayout_5.addWidget(self.label_24, 1, 0, 1, 1) + self.ignore_invalid_signals_mdf = QtWidgets.QCheckBox(self.groupBox_2) + self.ignore_invalid_signals_mdf.setObjectName("ignore_invalid_signals_mdf") + self.gridLayout_5.addWidget(self.ignore_invalid_signals_mdf, 2, 0, 1, 1) self.extract_bus_compression = QtWidgets.QComboBox(self.groupBox_2) self.extract_bus_compression.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.extract_bus_compression.setObjectName("extract_bus_compression") - self.gridLayout_5.addWidget(self.extract_bus_compression, 0, 1, 1, 1) + self.gridLayout_5.addWidget(self.extract_bus_compression, 0, 1, 1, 2) self.extract_bus_format = QtWidgets.QComboBox(self.groupBox_2) self.extract_bus_format.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.extract_bus_format.setObjectName("extract_bus_format") - self.gridLayout_5.addWidget(self.extract_bus_format, 1, 1, 1, 1) - self.label_24 = QtWidgets.QLabel(self.groupBox_2) - self.label_24.setObjectName("label_24") - self.gridLayout_5.addWidget(self.label_24, 1, 0, 1, 1) - self.ignore_invalid_signals_mdf = QtWidgets.QCheckBox(self.groupBox_2) - self.ignore_invalid_signals_mdf.setObjectName("ignore_invalid_signals_mdf") - self.gridLayout_5.addWidget(self.ignore_invalid_signals_mdf, 2, 0, 1, 2) - self.label_26 = QtWidgets.QLabel(self.groupBox_2) - self.label_26.setText("") - self.label_26.setObjectName("label_26") - self.gridLayout_5.addWidget(self.label_26, 3, 0, 1, 1) + self.gridLayout_5.addWidget(self.extract_bus_format, 1, 1, 1, 2) self.line_12 = QtWidgets.QFrame(self.groupBox_2) self.line_12.setFrameShape(QtWidgets.QFrame.HLine) self.line_12.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_12.setObjectName("line_12") - self.gridLayout_5.addWidget(self.line_12, 4, 0, 1, 2) + self.gridLayout_5.addWidget(self.line_12, 4, 0, 1, 3) self.gridLayout_8.addWidget(self.groupBox_2, 3, 0, 1, 1) self.tabWidget = QtWidgets.QTabWidget(self.extract_bus_tab) self.tabWidget.setObjectName("tabWidget") @@ -517,6 +639,10 @@ def setupUi(self, file_widget): self.verticalLayout_7.addWidget(self.lin_database_list) self.tabWidget.addTab(self.tab_2, "") self.gridLayout_8.addWidget(self.tabWidget, 2, 0, 1, 2) + self.gridLayout_8.setColumnStretch(0, 1) + self.gridLayout_8.setColumnStretch(1, 1) + self.gridLayout_8.setColumnStretch(2, 1) + self.gridLayout_8.setColumnStretch(3, 1) self.aspects.addTab(self.extract_bus_tab, icon9, "") self.info_tab = QtWidgets.QWidget() self.info_tab.setObjectName("info_tab") @@ -542,8 +668,10 @@ def setupUi(self, file_widget): self.verticalLayout.addWidget(self.aspects) self.retranslateUi(file_widget) - self.aspects.setCurrentIndex(0) + self.aspects.setCurrentIndex(1) self.output_options.setCurrentIndex(0) + self.quoting.setCurrentIndex(1) + self.quoting_bus.setCurrentIndex(1) self.tabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(file_widget) @@ -619,17 +747,51 @@ def retranslateUi(self, file_widget): self.time_as_date_mat.setText(_translate("file_widget", "Time as date")) self.label_68.setText(_translate("file_widget", "Empty channels")) self.raw_mat.setText(_translate("file_widget", "Raw values")) + self.quotechar.setText(_translate("file_widget", "\"")) + self.label_5.setText(_translate("file_widget", "Quote Char")) + self.use_display_names_csv.setText(_translate("file_widget", "Use display names")) + self.single_time_base_csv.setText(_translate("file_widget", "Single time base")) + self.lineterminator.setText(_translate("file_widget", "\\r\\n")) + self.time_as_date_csv.setText(_translate("file_widget", "Time as date")) + self.raw_csv.setText(_translate("file_widget", "Raw values")) + self.label_2.setText(_translate("file_widget", "Delimiter")) + self.label_3.setText(_translate("file_widget", "Escape Char")) + self.escapechar.setPlaceholderText(_translate("file_widget", "None")) + self.time_from_zero_csv.setText(_translate("file_widget", "Time from 0s")) + self.label_4.setText(_translate("file_widget", "Line Terminator")) + self.doublequote.setText(_translate("file_widget", "Double quote")) + self.label_6.setText(_translate("file_widget", "Quoting")) + self.quoting.setItemText(0, _translate("file_widget", "ALL")) + self.quoting.setItemText(1, _translate("file_widget", "MINIMAL")) + self.quoting.setItemText(2, _translate("file_widget", "NONNUMERIC")) + self.quoting.setItemText(3, _translate("file_widget", "NONE")) + self.delimiter.setText(_translate("file_widget", ",")) + self.label_66.setText(_translate("file_widget", "Empty channels")) self.apply_btn.setText(_translate("file_widget", "Apply")) self.aspects.setTabText(self.aspects.indexOf(self.modify), _translate("file_widget", "Modify && Export")) self.groupBox_3.setTitle(_translate("file_widget", "CSV")) - self.label_25.setText(_translate("file_widget", "Empty channels")) + self.label_10.setText(_translate("file_widget", "Quote Char")) self.single_time_base_bus.setText(_translate("file_widget", "Single time base")) + self.doublequote_bus.setText(_translate("file_widget", "Double quote")) self.label_23.setText(_translate("file_widget", "Raster")) - self.export_raster_bus.setSuffix(_translate("file_widget", "s")) + self.label_25.setText(_translate("file_widget", "Empty channels")) + self.label_9.setText(_translate("file_widget", "Escape Char")) + self.label_7.setText(_translate("file_widget", "Quoting")) + self.label_11.setText(_translate("file_widget", "Line Terminator")) + self.bus_time_as_date.setText(_translate("file_widget", "Time as date")) + self.time_from_zero_bus.setText(_translate("file_widget", "Time from 0s")) + self.label_8.setText(_translate("file_widget", "Delimiter")) self.ignore_invalid_signals_csv.setToolTip(_translate("file_widget", "checks if all samples are eauql to the maximum teoretical signal value")) self.ignore_invalid_signals_csv.setText(_translate("file_widget", "Ignore invalid signals")) - self.time_from_zero_bus.setText(_translate("file_widget", "Time from 0s")) - self.bus_time_as_date.setText(_translate("file_widget", "Time as date")) + self.export_raster_bus.setSuffix(_translate("file_widget", "s")) + self.delimiter_bus.setText(_translate("file_widget", ",")) + self.escapechar_bus.setPlaceholderText(_translate("file_widget", "None")) + self.lineterminator_bus.setText(_translate("file_widget", "\\r\\n")) + self.quotechar_bus.setText(_translate("file_widget", "\"")) + self.quoting_bus.setItemText(0, _translate("file_widget", "ALL")) + self.quoting_bus.setItemText(1, _translate("file_widget", "MINIMAL")) + self.quoting_bus.setItemText(2, _translate("file_widget", "NONNUMERIC")) + self.quoting_bus.setItemText(3, _translate("file_widget", "NONE")) self.extract_bus_csv_btn.setText(_translate("file_widget", "Export to CSV ")) self.groupBox_2.setTitle(_translate("file_widget", "MDF")) self.extract_bus_btn.setText(_translate("file_widget", "Extract Bus signals")) diff --git a/asammdf/gui/ui/file_widget.ui b/asammdf/gui/ui/file_widget.ui index 5f63de194..8239c55da 100644 --- a/asammdf/gui/ui/file_widget.ui +++ b/asammdf/gui/ui/file_widget.ui @@ -38,7 +38,7 @@ QTabWidget::West - 0 + 1 false @@ -895,6 +895,192 @@ + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + " + + + 1 + + + + + + + Quote Char + + + + + + + Use display names + + + + + + + Single time base + + + + + + + \r\n + + + + + + + Time as date + + + + + + + Raw values + + + + + + + Delimiter + + + + + + + Escape Char + + + + + + + + + + 1 + + + None + + + + + + + Qt::Horizontal + + + + + + + Time from 0s + + + + + + + Line Terminator + + + + + + + Double quote + + + true + + + + + + + Quoting + + + + + + + 1 + + + + ALL + + + + + MINIMAL + + + + + NONNUMERIC + + + + + NONE + + + + + + + + , + + + 1 + + + false + + + + + + + Empty channels + + + + + + + + @@ -936,16 +1122,13 @@ Bus Logging - + CSV - - - 2 - + 2 @@ -958,45 +1141,87 @@ 2 - - - - Qt::Horizontal + + + + Quote Char - - + + - Empty channels + Single time base - - + + - Single time base + Double quote + + + true - + Raster - - - - s + + + + Empty channels - - 6 + + + + + + Escape Char - + + + + Quoting + + + + + + + Line Terminator + + + + + + + Time as date + + + + + + + Time from 0s + + + + + + + Delimiter + + + + checks if all samples are eauql to the maximum teoretical signal value @@ -1006,24 +1231,90 @@ - + + + + s + + + 6 + + + + - - + + - Time from 0s + , + + + 1 + + + false - - + + + + + + + 1 + + + None + + + + + - Time as date + \r\n + + + + + + + " + + 1 + + + + + + + 1 + + + + ALL + + + + + MINIMAL + + + + + NONNUMERIC + + + + + NONE + + - + Export to CSV @@ -1034,6 +1325,13 @@ + + + + Qt::Horizontal + + + @@ -1065,7 +1363,14 @@ 2 - + + + + + + + + Extract Bus signals @@ -1076,6 +1381,19 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -1083,20 +1401,6 @@ - - - - QComboBox::AdjustToContents - - - - - - - QComboBox::AdjustToContents - - - @@ -1104,7 +1408,7 @@ - + checks if all samples are eauql to the maximum teoretical signal value @@ -1114,14 +1418,21 @@ - - - - + + + + QComboBox::AdjustToContents + + + + + + + QComboBox::AdjustToContents - + Qt::Horizontal diff --git a/asammdf/gui/widgets/batch.py b/asammdf/gui/widgets/batch.py index f98f998ce..8eb5b7513 100644 --- a/asammdf/gui/widgets/batch.py +++ b/asammdf/gui/widgets/batch.py @@ -23,7 +23,7 @@ class BatchWidget(Ui_batch_widget, QtWidgets.QWidget): - def __init__(self, ignore_value2text_conversions=False, integer_interpolation=2, *args, **kwargs): + def __init__(self, ignore_value2text_conversions=False, integer_interpolation=2, float_interpolation=1, *args, **kwargs): super().__init__(*args, **kwargs) self.setupUi(self) @@ -32,6 +32,7 @@ def __init__(self, ignore_value2text_conversions=False, integer_interpolation=2, self.ignore_value2text_conversions = ignore_value2text_conversions self.integer_interpolation = integer_interpolation + self.float_interpolation = float_interpolation self.progress = None self.files_list = MinimalListWidget() @@ -346,6 +347,12 @@ def extract_bus_csv_logging(self, event): empty_channels = self.empty_channels_bus.currentText() raster = self.export_raster_bus.value() or None time_as_date = self.bus_time_as_date.checkState() == QtCore.Qt.Checked + delimiter = self.delimiter_bus.text() or ',' + doublequote = self.doublequote_bus.checkState() == QtCore.Qt.Checked + escapechar = self.escapechar_bus.text() or None + lineterminator = self.lineterminator_bus.text().replace("\\r", "\r").replace("\\n", "\n") + quotechar = self.quotechar_bus.text() or '"' + quoting = self.quoting_bus.currentText() count = self.files_list.count() @@ -451,7 +458,10 @@ def extract_bus_csv_logging(self, event): f'Saving extracted Bus logging file {i+1} to "{file_name}"' ) - mdf_.configure(integer_interpolation=self.integer_interpolation) + mdf_.configure( + integer_interpolation=self.integer_interpolation, + float_interpolation=self.float_interpolation, + ) target = mdf_.export kwargs = { @@ -463,6 +473,12 @@ def extract_bus_csv_logging(self, event): "raster": raster or None, "time_as_date": time_as_date, "ignore_value2text_conversions": self.ignore_value2text_conversions, + "delimiter": delimiter, + "doublequote": doublequote, + "escapechar": escapechar, + "lineterminator": lineterminator, + "quotechar": quotechar, + "quoting": quoting, } run_thread_with_progress( @@ -1062,6 +1078,30 @@ def _current_options(self): "raw": self.raw_mat.checkState() == QtCore.Qt.Checked, } + elif output_format == "CSV": + + new = { + "single_time_base": self.single_time_base_csv.checkState() + == QtCore.Qt.Checked, + "time_from_zero": self.time_from_zero_csv.checkState() + == QtCore.Qt.Checked, + "time_as_date": self.time_as_date_csv.checkState() == QtCore.Qt.Checked, + "use_display_names": self.use_display_names_csv.checkState() + == QtCore.Qt.Checked, + "reduce_memory_usage": False, + "compression": False, + "empty_channels": self.empty_channels_csv.currentText(), + "raw": self.raw_csv.checkState() == QtCore.Qt.Checked, + "delimiter": self.delimiter.text() or ',', + "doublequote": self.doublequote.checkState() == QtCore.Qt.Checked, + "escapechar": self.escapechar.text() or None, + "lineterminator": self.lineterminator.text().replace("\\r", "\r").replace("\\n", "\n"), + "quotechar": self.quotechar.text() or '"', + 'quoting': self.quoting.currentText(), + "mat_format": None, + "oned_as": None, + } + else: new = { @@ -1189,6 +1229,7 @@ def apply_processing(self, event): mdf_file.configure( read_fragment_size=split_size, integer_interpolation=self.integer_interpolation, + float_interpolation=self.float_interpolation, ) mdf = None @@ -1229,6 +1270,7 @@ def apply_processing(self, event): read_fragment_size=split_size, write_fragment_size=split_size, integer_interpolation=self.integer_interpolation, + float_interpolation=self.float_interpolation, ) if opts.needs_cut: @@ -1284,6 +1326,7 @@ def apply_processing(self, event): read_fragment_size=split_size, write_fragment_size=split_size, integer_interpolation=self.integer_interpolation, + float_interpolation=self.float_interpolation, ) if opts.needs_resample: @@ -1344,6 +1387,7 @@ def apply_processing(self, event): read_fragment_size=split_size, write_fragment_size=split_size, integer_interpolation=self.integer_interpolation, + float_interpolation=self.float_interpolation, ) if output_format == "MDF": @@ -1396,6 +1440,7 @@ def apply_processing(self, event): read_fragment_size=split_size, write_fragment_size=split_size, integer_interpolation=self.integer_interpolation, + float_interpolation=self.float_interpolation, ) if output_folder is not None: diff --git a/asammdf/gui/widgets/file.py b/asammdf/gui/widgets/file.py index e4133cc93..8a04f5fcf 100644 --- a/asammdf/gui/widgets/file.py +++ b/asammdf/gui/widgets/file.py @@ -223,6 +223,8 @@ def __init__( progress.setValue(90) + self.output_options.setCurrentIndex(0) + self.mdf_version.insertItems(0, SUPPORTED_VERSIONS) self.mdf_compression.insertItems( 0, ("no compression", "deflate", "transposed deflate") @@ -246,6 +248,7 @@ def __init__( self.empty_channels.insertItems(0, ("skip", "zeros")) self.empty_channels_bus.insertItems(0, ("skip", "zeros")) self.empty_channels_mat.insertItems(0, ("skip", "zeros")) + self.empty_channels_csv.insertItems(0, ("skip", "zeros")) self.mat_format.insertItems(0, ("4", "5", "7.3")) self.oned_as.insertItems(0, ("row", "column")) @@ -676,6 +679,9 @@ def output_format_changed(self, name): self.export_compression_mat.clear() self.export_compression_mat.addItems(["enabled", "disabled"]) self.export_compression_mat.setCurrentIndex(0) + elif name == "CSV": + self.output_options.setCurrentIndex(3) + else: self.output_options.setCurrentIndex(1) if name == "Parquet": @@ -1515,6 +1521,12 @@ def extract_bus_csv_logging(self, event): empty_channels = self.empty_channels_bus.currentText() raster = self.export_raster_bus.value() time_as_date = self.bus_time_as_date.checkState() == QtCore.Qt.Checked + delimiter = self.delimiter_bus.text() or ',' + doublequote = self.doublequote_bus.checkState() == QtCore.Qt.Checked + escapechar = self.escapechar_bus.text() or None + lineterminator = self.lineterminator_bus.text().replace("\\r", "\r").replace("\\n", "\n") + quotechar = self.quotechar_bus.text() or '"' + quoting = self.quoting_bus.currentText() file_name, _ = QtWidgets.QFileDialog.getSaveFileName( self, @@ -1557,7 +1569,10 @@ def extract_bus_csv_logging(self, event): # then save it progress.setLabelText(f'Saving file to "{file_name}"') - mdf.configure(integer_interpolation=self.mdf._integer_interpolation) + mdf.configure( + integer_interpolation=self.mdf._integer_interpolation, + float_interpolation=self.mdf._float_interpolation, + ) target = mdf.export kwargs = { @@ -1569,6 +1584,12 @@ def extract_bus_csv_logging(self, event): "raster": raster or None, "time_as_date": time_as_date, "ignore_value2text_conversions": self.ignore_value2text_conversions, + "delimiter": delimiter, + "doublequote": doublequote, + "escapechar": escapechar, + "lineterminator": lineterminator, + "quotechar": quotechar, + "quoting": quoting, } run_thread_with_progress( @@ -1831,6 +1852,30 @@ def _current_options(self): "raw": self.raw_mat.checkState() == QtCore.Qt.Checked, } + elif output_format == "CSV": + + new = { + "single_time_base": self.single_time_base_csv.checkState() + == QtCore.Qt.Checked, + "time_from_zero": self.time_from_zero_csv.checkState() + == QtCore.Qt.Checked, + "time_as_date": self.time_as_date_csv.checkState() == QtCore.Qt.Checked, + "use_display_names": self.use_display_names_csv.checkState() + == QtCore.Qt.Checked, + "reduce_memory_usage": False, + "compression": False, + "empty_channels": self.empty_channels_csv.currentText(), + "raw": self.raw_csv.checkState() == QtCore.Qt.Checked, + "delimiter": self.delimiter.text() or ',', + "doublequote": self.doublequote.checkState() == QtCore.Qt.Checked, + "escapechar": self.escapechar.text() or None, + "lineterminator": self.lineterminator.text().replace("\\r", "\r").replace("\\n", "\n"), + "quotechar": self.quotechar.text() or '"', + 'quoting': self.quoting.currentText(), + "mat_format": None, + "oned_as": None, + } + else: new = { @@ -2002,6 +2047,7 @@ def apply_processing(self, event): mdf = None progress = None integer_interpolation = self.mdf._integer_interpolation + float_interpolation = self.mdf._float_interpolation if needs_filter: @@ -2038,6 +2084,7 @@ def apply_processing(self, event): read_fragment_size=split_size, write_fragment_size=split_size, integer_interpolation=integer_interpolation, + float_interpolation=float_interpolation, ) if opts.needs_cut: @@ -2093,6 +2140,7 @@ def apply_processing(self, event): read_fragment_size=split_size, write_fragment_size=split_size, integer_interpolation=integer_interpolation, + float_interpolation=float_interpolation, ) if opts.needs_resample: @@ -2151,6 +2199,7 @@ def apply_processing(self, event): read_fragment_size=split_size, write_fragment_size=split_size, integer_interpolation=integer_interpolation, + float_interpolation=float_interpolation, ) if output_format == "MDF": @@ -2198,6 +2247,7 @@ def apply_processing(self, event): read_fragment_size=split_size, write_fragment_size=split_size, integer_interpolation=integer_interpolation, + float_interpolation=float_interpolation, ) # then save it diff --git a/asammdf/gui/widgets/main.py b/asammdf/gui/widgets/main.py index d43451ec8..3249300f0 100644 --- a/asammdf/gui/widgets/main.py +++ b/asammdf/gui/widgets/main.py @@ -37,7 +37,17 @@ def __init__(self, files=None, *args, **kwargs): )[0] ) - self.batch = BatchWidget(self.ignore_value2text_conversions, self.integer_interpolation) + self.float_interpolation = int( + self._settings.value( + "float_interpolation", "1 - linear interpolation" + )[0] + ) + + self.batch = BatchWidget( + self.ignore_value2text_conversions, + self.integer_interpolation, + self.float_interpolation, + ) self.stackedWidget.addWidget(self.batch) widget = QtWidgets.QWidget() @@ -246,6 +256,25 @@ def __init__(self, files=None, *args, **kwargs): submenu.setToolTipsVisible(True) menu.addMenu(submenu) + # float interpolation menu + theme_option = QtWidgets.QActionGroup(self) + + for option in ("0 - repeat previous sample", "1 - linear interpolation"): + + action = QtWidgets.QAction(option, menu) + action.setCheckable(True) + theme_option.addAction(action) + action.triggered.connect(partial(self.set_float_interpolation, option)) + + if option == self._settings.value("float_interpolation", "1 - linear interpolation"): + action.setChecked(True) + action.triggered.emit() + + submenu = QtWidgets.QMenu("Float interpolation", self.menubar) + submenu.addActions(theme_option.actions()) + submenu.setToolTipsVisible(True) + menu.addMenu(submenu) + # plot option menu plot_actions = QtWidgets.QActionGroup(self) @@ -669,6 +698,19 @@ def set_integer_interpolation(self, option): self.batch.integer_interpolation = option + def set_float_interpolation(self, option): + self._settings.setValue("float_interpolation", option) + + option = int(option[0]) + self.float_interpolation = option + + count = self.files.count() + + for i in range(count): + self.files.widget(i).mdf.configure(float_interpolation=option) + + self.batch.float_interpolation = option + def set_plot_xaxis(self, option): self._settings.setValue("plot_xaxis", option) if option == "seconds": diff --git a/asammdf/mdf.py b/asammdf/mdf.py index a51772efe..1de3b7b7a 100644 --- a/asammdf/mdf.py +++ b/asammdf/mdf.py @@ -516,8 +516,9 @@ def convert(self, version): **self._kwargs ) - interpolation_mode = self._integer_interpolation - out.configure(integer_interpolation=interpolation_mode) + integer_interpolation_mode = self._integer_interpolation + float_interpolation_mode = self._float_interpolation + out.configure(from_other=self) out.header.start_time = self.header.start_time @@ -619,8 +620,9 @@ def cut( **self._kwargs, ) - interpolation_mode = self._integer_interpolation - out.configure(integer_interpolation=interpolation_mode) + integer_interpolation_mode = self._integer_interpolation + float_interpolation_mode = self._float_interpolation + out.configure(from_other=self) self.configure(copy_on_get=False) @@ -743,7 +745,8 @@ def cut( fragment_start, fragment_stop, include_ends, - interpolation_mode=interpolation_mode, + integer_interpolation_mode=integer_interpolation_mode, + float_interpolation_mode=float_interpolation_mode, ) .timestamps ) @@ -756,7 +759,8 @@ def cut( master[0], master[-1], include_ends=include_ends, - interpolation_mode=interpolation_mode, + integer_interpolation_mode=integer_interpolation_mode, + float_interpolation_mode=float_interpolation_mode, ) for sig in signals ] @@ -836,7 +840,7 @@ def cut( return out def export(self, fmt, filename=None, **kwargs): - """export *MDF* to other formats. The *MDF* file name is used is + r"""export *MDF* to other formats. The *MDF* file name is used is available, else the *filename* argument must be provided. The *pandas* export option was removed. you should use the method @@ -866,7 +870,7 @@ def export(self, fmt, filename=None, **kwargs): filename : string | pathlib.Path export file name - **kwargs + \*\*kwargs * `single_time_base`: resample all channels to common time base, default *False* @@ -884,23 +888,23 @@ def export(self, fmt, filename=None, **kwargs): component channels. If *True* this can be very slow. If *False* only the component channels are saved, and their names will be prefixed with the parent channel. - * reduce_memory_usage : bool + * `reduce_memory_usage` : bool reduce memory usage by converting all float columns to float32 and searching for minimum dtype that can reprezent the values found in integer columns; default *False* - * compression : str + * `compression` : str compression to be used * for ``parquet`` : "GZIP" or "SANPPY" * for ``hfd5`` : "gzip", "lzf" or "szip" * for ``mat`` : bool - * time_as_date (False) : bool + * `time_as_date` (False) : bool export time as local timezone datetimee; only valid for CSV export .. versionadded:: 5.8.0 - * ignore_value2text_conversions (False) : bool + * `ignore_value2text_conversions` (False) : bool valid only for the channels that have value to text conversions and if *raw=False*. If this is True then the raw numeric values will be used, and the conversion will not be applied. @@ -912,6 +916,37 @@ def export(self, fmt, filename=None, **kwargs): .. versionadded:: 6.0.0 + * delimiter (',') : str + only valid for CSV: see cpython documentation for csv.Dialect.delimiter + + .. versionadded:: 6.2.0 + + * doublequote (True) : bool + only valid for CSV: see cpython documentation for csv.Dialect.doublequote + + .. versionadded:: 6.2.0 + + * escapechar (None) : str + only valid for CSV: see cpython documentation for csv.Dialect.escapechar + + .. versionadded:: 6.2.0 + + * lineterminator ("\\r\\n") : str + only valid for CSV: see cpython documentation for csv.Dialect.lineterminator + + .. versionadded:: 6.2.0 + + * quotechar ('"') : str + only valid for CSV: see cpython documentation for csv.Dialect.quotechar + + .. versionadded:: 6.2.0 + + * quoting ("MINIMAL") : str + only valid for CSV: see cpython documentation for csv.Dialect.quoting. Use the + last part of the quoting constant name + + .. versionadded:: 6.2.0 + """ @@ -1174,6 +1209,24 @@ def export(self, fmt, filename=None, **kwargs): self._callback(i + 1, groups_nr) elif fmt == "csv": + fmtparams = { + "delimiter": kwargs.get("delimiter", ",")[0], + "doublequote": kwargs.get("doublequote", True), + "lineterminator": kwargs.get("lineterminator", '\r\n'), + "quotechar": kwargs.get("quotechar", '"')[0], + } + + quoting = kwargs.get("quoting", "MINIMAL").upper() + quoting = getattr(csv, f"QUOTE_{quoting}") + + fmtparams["quoting"] = quoting + + escapechar = kwargs.get("escapechar", None) + if escapechar is not None: + escapechar = escapechar[0] + + fmtparams["escapechar"] = escapechar + if single_time_base: filename = filename.with_suffix(".csv") message = f'Writing csv export to file "{filename}"' @@ -1213,7 +1266,7 @@ def export(self, fmt, filename=None, **kwargs): with open(filename, "w", newline="") as csvfile: - writer = csv.writer(csvfile) + writer = csv.writer(csvfile, **fmtparams) names_row = [df.index.name, *df.columns] writer.writerow(names_row) @@ -1293,7 +1346,7 @@ def export(self, fmt, filename=None, **kwargs): df.index.name = "timestamps" with open(group_csv_name, "w", newline="") as csvfile: - writer = csv.writer(csvfile) + writer = csv.writer(csvfile, **fmtparams) if hasattr(self, "can_logging_db") and self.can_logging_db: @@ -1325,9 +1378,6 @@ def export(self, fmt, filename=None, **kwargs): df.index.to_list(), *(df[name].to_list() for name in df), ] - count = len(df.index) - - count = len(df.index) for i, row in enumerate(zip(*vals)): writer.writerow(row) @@ -1536,17 +1586,18 @@ def filter(self, channels, version=None): # group channels by group index gps = self.included_channels(channels=channels) - self.configure(copy_on_get=False) - mdf = MDF( version=version, **self._kwargs, ) - interpolation_mode = self._integer_interpolation - mdf.configure(integer_interpolation=interpolation_mode) + integer_interpolation_mode = self._integer_interpolation + float_interpolation_mode = self._float_interpolation + mdf.configure(from_other=self) mdf.header.start_time = self.header.start_time + self.configure(copy_on_get=False) + if self.name: origin = self.name.name else: @@ -1805,8 +1856,9 @@ def concatenate( **kwargs, ) - interpolation_mode = mdf._integer_interpolation - merged.configure(integer_interpolation=interpolation_mode) + integer_interpolation_mode = mdf._integer_interpolation + float_interpolation_mode = mdf._float_interpolation + merged.configure(from_other=mdf) merged.header.start_time = oldest @@ -2080,8 +2132,10 @@ def stack(files, version="4.10", sync=True, **kwargs): callback=callback, **kwargs, ) - interpolation_mode = mdf._integer_interpolation - stacked.configure(integer_interpolation=interpolation_mode) + integer_interpolation_mode = mdf._integer_interpolation + float_interpolation_mode = mdf._float_interpolation + stacked.configure(from_other=mdf) + if sync: stacked.header.start_time = oldest @@ -2252,7 +2306,7 @@ def iter_groups( * a channel name who's timestamps will be used as raster (starting with asammdf 5.5.0) * an array (starting with asammdf 5.5.0) - see `resample` for examples of urisng this argument + see `resample` for examples of using this argument .. versionadded:: 5.21.0 @@ -2430,8 +2484,9 @@ def resample(self, raster, version=None, time_from_zero=False): **self._kwargs, ) - interpolation_mode = self._integer_interpolation - mdf.configure(integer_interpolation=interpolation_mode) + integer_interpolation_mode = self._integer_interpolation + float_interpolation_mode = self._float_interpolation + mdf.configure(from_other=self) mdf.header.start_time = self.header.start_time @@ -2472,7 +2527,11 @@ def resample(self, raster, version=None, time_from_zero=False): sigs = self.select(channels, raw=True) sigs = [ - sig.interp(raster, interpolation_mode=interpolation_mode) + sig.interp( + raster, + integer_interpolation_mode=integer_interpolation_mode, + float_interpolation_mode=float_interpolation_mode, + ) for sig in sigs ] @@ -3100,7 +3159,7 @@ def get_group( * a channel name who's timestamps will be used as raster (starting with asammdf 5.5.0) * an array (starting with asammdf 5.5.0) - see `resample` for examples of urisng this argument + see `resample` for examples of using this argument Returns ------- @@ -3155,7 +3214,13 @@ def iter_to_dataframe( Parameters ---------- channels : list - filter a subset of channels; default *None* + list of items to be filtered (default None); each item can be : + + * a channel name string + * (channel name, group index, channel index) list or tuple + * (channel name, group index) list or tuple + * (None, group index, channel index) list or tuple + raster : float | np.array | str new raster that can be @@ -3163,7 +3228,7 @@ def iter_to_dataframe( * a channel name who's timestamps will be used as raster (starting with asammdf 5.5.0) * an array (starting with asammdf 5.5.0) - see `resample` for examples of urisng this argument + see `resample` for examples of using this argument time_from_zero : bool adjust time channel to start from 0; default *True* @@ -3530,7 +3595,13 @@ def to_dataframe( Parameters ---------- channels : list - filter a subset of channels; default *None* + list of items to be filtered (default None); each item can be : + + * a channel name string + * (channel name, group index, channel index) list or tuple + * (channel name, group index) list or tuple + * (None, group index, channel index) list or tuple + raster : float | np.array | str new raster that can be @@ -3538,7 +3609,7 @@ def to_dataframe( * a channel name who's timestamps will be used as raster (starting with asammdf 5.5.0) * an array (starting with asammdf 5.5.0) - see `resample` for examples of urisng this argument + see `resample` for examples of using this argument time_from_zero : bool adjust time channel to start from 0; default *True* @@ -4632,9 +4703,12 @@ def whereis(self, channel, source_name=None, source_path=None): () """ - occurrences = self._filter_occurrences( - self.channels_db[channel], source_name=source_name, source_path=source_path - ) + try: + occurrences = self._filter_occurrences( + self.channels_db[channel], source_name=source_name, source_path=source_path + ) + except: + occurrences = tuple() return tuple(occurrences) diff --git a/asammdf/signal.py b/asammdf/signal.py index 7352225ec..aa7209afa 100644 --- a/asammdf/signal.py +++ b/asammdf/signal.py @@ -389,7 +389,15 @@ def update(val): except Exception as err: print(err) - def cut(self, start=None, stop=None, include_ends=True, interpolation_mode=0): + def cut( + self, + start=None, + stop=None, + include_ends=True, + interpolation_mode=None, + integer_interpolation_mode=None, + float_interpolation_mode=1, + ): """ Cuts the signal according to the *start* and *stop* values, by using the insertion indexes in the signal's *time* axis. @@ -405,10 +413,36 @@ def cut(self, start=None, stop=None, include_ends=True, interpolation_mode=0): If *start* and *stop* are found in the original timestamps, then the new samples will be computed using interpolation. Default *True* interpolation_mode : int + interpolation mode for integer signals; default 0. You should use the new *integer_interpolation_mode* + argument since this will be deprecated in a later release + + * 0 - repeat previous samples + * 1 - linear interpolation + * 2 - hybrid interpolation: channels with integer data type (raw values) that have a + conversion that outputs float values will use linear interpolation, otherwise + the previous sample is used + + .. versionchanged:: 6.2.0 + added hybrid mode interpolation + + integer_interpolation_mode : int interpolation mode for integer signals; default 0 * 0 - repeat previous samples * 1 - linear interpolation + * 2 - hybrid interpolation: channels with integer data type (raw values) that have a + conversion that outputs float values will use linear interpolation, otherwise + the previous sample is used + + .. versionadded:: 6.2.0 + + float_interpolation_mode : int + interpolation mode for float channels; default 1 + + * 0 - repeat previous sample + * 1 - use linear interpolation + + .. versionadded:: 6.2.0 Returns ------- @@ -422,6 +456,20 @@ def cut(self, start=None, stop=None, include_ends=True, interpolation_mode=0): 0.98, 10.48 """ + if integer_interpolation_mode is None: + if interpolation_mode is not None: + integer_interpolation_mode = interpolation_mode + else: + integer_interpolation_mode = 0 + else: + integer_interpolation_mode = 0 + + if integer_interpolation_mode not in (0, 1, 2): + raise MdfException("Integer interpolation mode should be one of (0, 1, 2)") + + if float_interpolation_mode not in (0, 1): + raise MdfException("Float interpolation mode should be one of (0, 1)") + ends = (start, stop) if len(self) == 0: result = Signal( @@ -498,7 +546,9 @@ def cut(self, start=None, stop=None, include_ends=True, interpolation_mode=0): and ends[-1] < self.timestamps[-1] ): interpolated = self.interp( - [ends[1]], interpolation_mode=interpolation_mode + [ends[1]], + integer_interpolation_mode=integer_interpolation_mode, + float_interpolation_mode=float_interpolation_mode, ) samples = np.append( self.samples[:stop], interpolated.samples, axis=0 @@ -568,7 +618,9 @@ def cut(self, start=None, stop=None, include_ends=True, interpolation_mode=0): and ends[0] > self.timestamps[0] ): interpolated = self.interp( - [ends[0]], interpolation_mode=interpolation_mode + [ends[0]], + integer_interpolation_mode=integer_interpolation_mode, + float_interpolation_mode=float_interpolation_mode, ) samples = np.append( interpolated.samples, self.samples[start:], axis=0 @@ -636,7 +688,9 @@ def cut(self, start=None, stop=None, include_ends=True, interpolation_mode=0): if start == stop: if include_ends: interpolated = self.interp( - np.unique(ends), interpolation_mode=interpolation_mode + np.unique(ends), + integer_interpolation_mode=integer_interpolation_mode, + float_interpolation_mode=float_interpolation_mode, ) samples = interpolated.samples timestamps = np.array( @@ -666,7 +720,9 @@ def cut(self, start=None, stop=None, include_ends=True, interpolation_mode=0): and ends[-1] < self.timestamps[-1] ): interpolated = self.interp( - [ends[1]], interpolation_mode=interpolation_mode + [ends[1]], + integer_interpolation_mode=integer_interpolation_mode, + float_interpolation_mode=float_interpolation_mode, ) samples = np.append(samples, interpolated.samples, axis=0) timestamps = np.append(timestamps, ends[1]) @@ -681,7 +737,9 @@ def cut(self, start=None, stop=None, include_ends=True, interpolation_mode=0): and ends[0] > self.timestamps[0] ): interpolated = self.interp( - [ends[0]], interpolation_mode=interpolation_mode + [ends[0]], + integer_interpolation_mode=integer_interpolation_mode, + float_interpolation_mode=float_interpolation_mode, ) samples = np.append(interpolated.samples, samples, axis=0) timestamps = np.append(ends[0], timestamps) @@ -776,7 +834,13 @@ def extend(self, other): return result - def interp(self, new_timestamps, interpolation_mode=0): + def interp( + self, + new_timestamps, + interpolation_mode=None, + integer_interpolation_mode=None, + float_interpolation_mode=1, + ): """returns a new *Signal* interpolated using the *new_timestamps* Parameters @@ -784,7 +848,8 @@ def interp(self, new_timestamps, interpolation_mode=0): new_timestamps : np.array timestamps used for interpolation interpolation_mode : int - interpolation mode for integer signals; default 0 + interpolation mode for integer signals; default 0. You should use the new *integer_interpolation_mode* + argument since this will be deprecated in a later release * 0 - repeat previous samples * 1 - linear interpolation @@ -795,6 +860,25 @@ def interp(self, new_timestamps, interpolation_mode=0): .. versionchanged:: 6.2.0 added hybrid mode interpolation + integer_interpolation_mode : int + interpolation mode for integer signals; default 0 + + * 0 - repeat previous samples + * 1 - linear interpolation + * 2 - hybrid interpolation: channels with integer data type (raw values) that have a + conversion that outputs float values will use linear interpolation, otherwise + the previous sample is used + + .. versionadded:: 6.2.0 + + float_interpolation_mode : int + interpolation mode for float channels; default 1 + + * 0 - repeat previous sample + * 1 - use linear interpolation + + .. versionadded:: 6.2.0 + Returns ------- signal : Signal @@ -802,6 +886,20 @@ def interp(self, new_timestamps, interpolation_mode=0): """ + if integer_interpolation_mode is None: + if interpolation_mode is not None: + integer_interpolation_mode = interpolation_mode + else: + integer_interpolation_mode = 0 + else: + integer_interpolation_mode = 0 + + if integer_interpolation_mode not in (0, 1, 2): + raise MdfException("Integer interpolation mode should be one of (0, 1, 2)") + + if float_interpolation_mode not in (0, 1): + raise MdfException("Float interpolation mode should be one of (0, 1)") + if not len(self.samples) or not len(new_timestamps): return Signal( self.samples[:0].copy(), @@ -837,28 +935,43 @@ def interp(self, new_timestamps, interpolation_mode=0): kind = self.samples.dtype.kind if kind == "f": - s = np.interp(new_timestamps, self.timestamps, self.samples) - - if self.invalidation_bits is not None: + if float_interpolation_mode == 0: idx = np.searchsorted( self.timestamps, new_timestamps, side="right" ) idx -= 1 idx = np.clip(idx, 0, idx[-1]) - invalidation_bits = self.invalidation_bits[idx] + s = self.samples[idx] + + if self.invalidation_bits is not None: + invalidation_bits = self.invalidation_bits[idx] + else: + invalidation_bits = None + else: - invalidation_bits = None + s = np.interp(new_timestamps, self.timestamps, self.samples) + + if self.invalidation_bits is not None: + idx = np.searchsorted( + self.timestamps, new_timestamps, side="right" + ) + idx -= 1 + idx = np.clip(idx, 0, idx[-1]) + invalidation_bits = self.invalidation_bits[idx] + else: + invalidation_bits = None + elif kind in "ui": - if interpolation_mode == 2: + if integer_interpolation_mode == 2: if self.raw and self.conversion: kind = self.conversion.convert(self.samples[:1]).dtype.kind if kind == "f": - interpolation_mode = 1 + integer_interpolation_mode = 1 - if interpolation_mode == 2: - interpolation_mode = 0 + if integer_interpolation_mode == 2: + integer_interpolation_mode = 0 - if interpolation_mode == 1: + if integer_interpolation_mode == 1: s = np.interp( new_timestamps, self.timestamps, self.samples ).astype(self.samples.dtype) @@ -871,7 +984,7 @@ def interp(self, new_timestamps, interpolation_mode=0): invalidation_bits = self.invalidation_bits[idx] else: invalidation_bits = None - elif interpolation_mode == 0: + elif integer_interpolation_mode == 0: idx = np.searchsorted( self.timestamps, new_timestamps, side="right" )