diff --git a/README.md b/README.md index 67001ae..1650c41 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,87 @@ -![Scio Logo](https://d347awuzx0kdse.cloudfront.net/vicomaus/content-image/Tek_Logo_RGB.png){width="50px"} +
-# tm_data_types +| | | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Testing** | [![Code testing status](https://github.com/tektronix/tm_data_types/actions/workflows/test-code.yml/badge.svg?branch=main)](https://github.com/tektronix/tm_data_types/actions/workflows/test-code.yml) [![Docs testing status](https://github.com/tektronix/tm_data_types/actions/workflows/test-docs.yml/badge.svg?branch=main)](https://github.com/tektronix/tm_data_types/actions/workflows/test-docs.yml) [![Coverage status](https://codecov.io/gh/tektronix/tm_data_types/branch/main/graph/badge.svg)](https://codecov.io/gh/tektronix/tm_data_types) | +| **Code Quality** | [![CodeQL status](https://github.com/tektronix/tm_data_types/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/tektronix/tm_data_types/actions/workflows/codeql-analysis.yml) [![CodeFactor grade](https://www.codefactor.io/repository/github/tektronix/tm_data_types/badge)](https://www.codefactor.io/repository/github/tektronix/tm_data_types) [![pre-commit status](https://results.pre-commit.ci/badge/github/tektronix/tm_data_types/main.svg)](https://results.pre-commit.ci/latest/github/tektronix/tm_data_types/main) | +| **Package** | [![PyPI: Package status](https://img.shields.io/pypi/status/tm_data_types?logo=pypi)](https://pypi.org/project/tm_data_types/) [![PyPI: Latest release version](https://img.shields.io/pypi/v/tm_data_types?logo=pypi)](https://pypi.org/project/tm_data_types/) [![PyPI: Supported Python versions](https://img.shields.io/pypi/pyversions/tm_data_types?logo=python)](https://pypi.org/project/tm_data_types/) [![PyPI: Downloads](https://pepy.tech/badge/tm_data_types)](https://pepy.tech/project/tm_data_types) [![License: Apache 2.0](https://img.shields.io/pypi/l/tm_data_types)](https://github.com/tektronix/tm_data_types/blob/main/LICENSE.md) [![Package build status](https://github.com/tektronix/tm_data_types/actions/workflows/package-build.yml/badge.svg?branch=main)](https://github.com/tektronix/tm_data_types/actions/workflows/package-build.yml) [![PyPI upload status](https://github.com/tektronix/tm_data_types/actions/workflows/package-release.yml/badge.svg?branch=main)](https://github.com/tektronix/tm_data_types/actions/workflows/package-release.yml) | +| **Documentation** | [![ReadtheDocs Status](https://img.shields.io/readthedocs/tm_data_types/stable?logo=readthedocs)](https://tm_data_types.readthedocs.io/stable) | +| **Code Style** | [![Test style: pytest](https://img.shields.io/badge/test%20style-pytest-blue)](https://github.com/pytest-dev/pytest) [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-black)](https://docs.astral.sh/ruff/formatter/) [![Docstring style: google](https://img.shields.io/badge/docstring%20style-google-tan)](https://google.github.io/styleguide/pyguide.html) | +| **Linting** | [![pre-commit enabled](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit) [![Docstring formatter: docformatter](https://img.shields.io/badge/docstring%20formatter-docformatter-tan)](https://github.com/PyCQA/docformatter)[![Linter: pylint](https://img.shields.io/badge/linter-pylint-purple)](https://github.com/pylint-dev/pylint) | -## Purpose +
-Python Instrument IO can be used to: +--- -- **Convert** CSV, WFM, and BIN format into the waveform object format, -- **Generate** analog waveforms with NRZ or PAM4 signal processes, +# tm_data_types: Test & Measurement Data Types + +`tm_data_types` provides tools to convert, edit, and write waveform data from Test & Measurement devices. +It simplifies handling waveform formats like CSV, WFM, and BIN in Python. + +`tm_data_types` can be used to: + +- **Convert** CSV, WFM, and BIN format into a waveform object, - **Add or edit** waveform metadata, - **Write** a valid waveform object to a file. -This interface assists the client and development team in parsing the format from Tek instrument and construct a -python object with features. The API is funneled into a single Python file, \[InstrumentIO\]{.title-ref}, which contains -all the functions required to read, write, and generate various instrument data. +## Supported File Formats -For additional documentation, visit our Wiki page. +
-- Supported files for reader interface function include: '.wfm', '.bin', '.csv', '.awg_wfm' -- Supported files for writer interface function include: '.wfm', '.csv', '.awg_wfm' +| Interface | File formats | +| --------- | -------------------- | +| Reader | **.bin, .csv, .wfm** | +| Writer | **.csv, .wfm** | -## Quick Start +
-Requirements: [Python 3.0 or above](https://www.python.org/download/releases/3.0/) +Currently, `tm_data_types` only supports the [Tektronix proprietary](https://download.tek.com/manual/Waveform-File-Format-Manual-077022011.pdf) `.wfm` format. +Support for other formats is planned for future releases. -Installed Library: \[datatime\]{.title-ref}, \[struct\]{.title-ref}, -\[numpy\]{.title-ref}, \[scipy\]{.title-ref}, \[pandas\]{.title-ref} (Test waveform plot with \[matplotlib\]{.title-ref}) +## Installation -```bash +```shell pip install tm_data_types ``` -## Waveform Object Attributes - -- `._vertical_data` → An array of binary values. -- `._size` → The size of the vertical data. Read-only, must be an integer. -- `._spacing` → The horizontal interval between two consecutive vertical data points. Must be an integer or float. -- `._trigger_index` → -- `.vertical_data` → vertical data -- `.horizontal_data` → horizontal data -- `.time_interval` → time intervals across vertical data -- `.trigger_position` → trigger position - -## Examples - -- `read_file_channels(file_path)` → return lists of channels for csv file only -- `read_file(file_path, channel (opt.))` → return waveform object -- `write_file(waveform obj, file_path, instrument_type (opt.))` → write file with extension format -- `initialize_waveform(size (opt.), sample_rate (opt.))` → return simple waveform with size and sample rate. -- `create_unique_filepath(file_path, file_template)` → return unique file path for converted/created file -- `random_bits(size)` → returns a random array of values 1 or 0 of user-inputted size. -- `random_symbols(size)` → returns a random array of values between 0 and 3 of user-inputted size. -- `nrz(bit_array, symbol_rate=1.0e9, sample_per_ui=10.12345, amplitude=1., offset=0.0, impairment=0.2, noise=0.01, repeats=1)` - → return NRZ waveform object based on user-inputted bit array. Array could be derived from InstrumentIO's \[random_bits\]{.title-ref} function, or from some other random number generator specified by the user (i.e. PBRS). -- `pam4(symbol_arr, data_rate=1.0e9, sample_per_ui=10.12345, amplitude=1.0, offset=0.0, impairment=0.2, noise=0.01, repeats=1)` - → return PAM4 waveform object based on user-inputted symbol array. Array could be derived from InstrumentIO's \[random_symbols\]{.title-ref} function, or from some other random number generator specified by the user (i.e. PBRS). -- `square(frequency=1000, repeat=5, amplitude=1, rec_length=1000)` → return square waveform object. -- `sawtooth(frequency=1000, repeat=5, amplitude=1, rec_length=1000)` → return sawtooth waveform object. -- `triangle(frequency=1000, repeat=5, amplitude=1, rec_length=1000)` → return triangle waveform object. +## Basic Usage -## Maintainers +### Write File -.. TODO: update email - tmdevicessupport@tektronix.com - For technical support and questions. +```python +from tm_data_types import AnalogWaveform, write_file -- - For open-source policy and license questions. -- Keith Rule +waveform = AnalogWaveform() +file_path = "waveform_1.wfm" +write_file(file_path, waveform) +``` + +### Read File -For more information about this repository, you can leave a question/comment on the [repository's Discussion board](https://github.com/tektronix/PythonInstrumentIO/discussions). +```python +from tm_data_types import read_file + +file_path = "waveform_1.wfm" +waveform = read_file(file_path) +``` + +## Documentation + +See the full documentation at + +## Maintainers + +Before reaching out to any maintainers directly, please first check if +your issue or question is already covered by any [open +issues](https://github.com/tektronix/tm_data_types/issues). If the issue or +question you have is not already covered, please [file a new +issue](https://github.com/tektronix/tm_data_types/issues/new/choose) or +start a +[discussion](https://github.com/tektronix/tm_data_types/discussions) and +the maintainers will review and respond there. + +- - For open-source policy and license + questions. ## Contributing @@ -90,10 +105,3 @@ The artifact attestations can also be directly downloaded from the ```shell gh attestation verify --owner tektronix ``` - -## Credits - -`tm_data_types` was created with -[cookiecutter](https://cookiecutter.readthedocs.io/en/latest/README.html) -and the `py-pkgs-cookiecutter` -[template](https://py-pkgs-cookiecutter.readthedocs.io/en/latest/). diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..f78f89d --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,6 @@ +{% +include-markdown "../CHANGELOG.md" +comments=false +rewrite-relative-urls=false + +%} diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3a38763 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,6 @@ +{% +include-markdown "../CODE_OF_CONDUCT.md" +comments=false +rewrite-relative-urls=false + +%} diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..8e39ea1 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,6 @@ +{% +include-markdown "../CONTRIBUTING.md" +comments=false +rewrite-relative-urls=false + +%} diff --git a/docs/LICENSE.md b/docs/LICENSE.md new file mode 100644 index 0000000..ecb14eb --- /dev/null +++ b/docs/LICENSE.md @@ -0,0 +1,8 @@ +# License + +{% +include-markdown "../LICENSE.md" +comments=false +rewrite-relative-urls=false + +%} diff --git a/docs/_static/css/theme_overrides.css b/docs/_static/css/theme_overrides.css index 4d433e4..a4843dc 100644 --- a/docs/_static/css/theme_overrides.css +++ b/docs/_static/css/theme_overrides.css @@ -3,7 +3,7 @@ white-space: nowrap !important; } -.rst-content table.device-support-table.docutils thead { +.rst-content table.support-table.docutils thead { vertical-align: middle; .line-block { @@ -11,29 +11,29 @@ } } -.device-support-table thead, .device-support-table tbody { +.support-table thead, .support-table tbody { border: 2px solid black } -.device-support-table table { +.support-table table { width: auto !important; display: table !important; margin: auto !important; } -.device-support-table.docutils tbody tr:has(th p), -.device-support-table tr:has(td:first-child:not(:empty)) { +.support-table.docutils tbody tr:has(th p), +.support-table tr:has(td:first-child:not(:empty)) { border-top-width: 2px; border-top-color: black; border-top-style: solid; } -.device-support-table tbody td[rowspan] { +.support-table tbody td[rowspan] { background-color: transparent !important; } -.device-support-table table td:first-child, -.device-support-table table th:first-child { +.support-table table td:first-child, +.support-table table th:first-child { font-weight: bolder; background-color: transparent !important; } diff --git a/docs/advanced/writing_to_a_file.md b/docs/advanced/writing_to_a_file.md deleted file mode 100644 index 900261e..0000000 --- a/docs/advanced/writing_to_a_file.md +++ /dev/null @@ -1,29 +0,0 @@ -write_file is a generic method which takes two arguments, the path to write to and the waveform -to write. The method begins by utilizing a lookup table -along with the waveform type to determine what type of behavior it will utilize to write. -Following this, the waveform is formatted and written to the path specified. - -Alternatively, write_files_in_parallel can be used instead. This method accepts two separate list for -lists for file paths and waveforms, where each value in one list index will correspond to the other. -Multiprocessing is leveraged by partitioning each list and sending them into their own -distinct process for saving. The write process is identical to the standard write_file. - -This writing process is involved, and is distinctly different between each format and waveform type -No transformations are done when saving to the .wfm format if the data is of the RawSample type. -This done as the express purpose of this library is to save and load .wfm files as fast as possible. -However if the data type is Normalized, a transformed is performed, as .wfm files are required to contain -digitized data with the spacing and offset being separate. - -read_file is a generic method which takes one argument, the path in which a waveform file exists that -should be read. The same lookup table for file extensions is used from write_file, however an additional -step is used, which is looking through small sections of the files data to determine what the waveform type is. -Following this, a waveform is provided from the read file, the type of which is dependant on how the data is -formatted. - -read_files_in_parallel handles the same way functionally as write_files_in_parallel, the main difference between -the two being what arguments are passed in and the queue of waveforms returned. - -Reading will reformat the waveform data to match what is required for the oscilloscope to display. -Waveform files will all return the waveform in the RawSample format. This process takes some time -as the conversion utilizes mathematical transformations -on the entire dataset, so the best way to utilize this library is to utilize the .wfm extension. diff --git a/docs/basic_usage.md b/docs/basic_usage.md index 6472fa8..fa6b3b5 100644 --- a/docs/basic_usage.md +++ b/docs/basic_usage.md @@ -5,7 +5,27 @@ project. ## Write Data +`tm_data_types` can be used for writing data to a file using [`write_file()`][tm_data_types.write_file]. + ```python # fmt: off --8<-- "examples/write_file.py" ``` + +## Type Conversion and Normalization of Data + +`tm_data_types` can be used for type conversion and normalization of analog waveform data. + +```python +# fmt: off +--8<-- "examples/type_conversion_example.py" +``` + +## Write Analog Waveform to CSV file + +`tm_data_types` can be used to write an analog waveform to a CSV file using the [`WaveformFileCSVAnalog`][tm_data_types.files_and_formats.csv.data_formats.analog.WaveformFileCSVAnalog] class. + +```python +# fmt: off +--8<-- "examples/write_csv_example.py" +``` diff --git a/docs/glossary.md b/docs/glossary.md new file mode 100644 index 0000000..d781988 --- /dev/null +++ b/docs/glossary.md @@ -0,0 +1,15 @@ +# Glossary + +A collection of terms and symbols used throughout the documentation and their definitions. + +BIN +: Binary format used for storing waveform data. + +CSV +: Comma-Separated Values + +Scope +: Oscilloscope + +WFM +: Waveform format used by Test & Measurement devices. diff --git a/docs/macros.py b/docs/macros.py index c8c53f1..1c7bd6d 100644 --- a/docs/macros.py +++ b/docs/macros.py @@ -22,6 +22,41 @@ #################################################################################################### # Helper functions #################################################################################################### +CONVERSION_PATTERN = re.compile( + r"> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|DANGER)]\s*>\s*(.*?)(?=\n[^>]|$)", + re.IGNORECASE | re.DOTALL, +) + + +def convert_gfm_alerts_to_admonitions(content: str) -> str: + """Convert GitHub Flavored Markdown (GFM) alerts to MkDocs admonitions. + + Args: + content: The content to convert. + + Returns: + The updated content with GFM alerts converted to markdown admonitions. + """ + + def replace_match(match: re.Match[str]) -> str: + """Replace the matched GFM alert with an admonition. + + Args: + match: The matched GFM alert. + + Returns: + The replacement text. + """ + alert_type = match.group(1).lower() + text = match.group(2).strip() + # Replace initial '>' from subsequent lines + text = text.replace("\n>", "\n") + # Replace with admonition format + return f"!!! {alert_type}\n " + text.replace("\n", "\n ") + + return re.sub(CONVERSION_PATTERN, replace_match, content) + + def import_object(objname: str) -> Any: """Import a python object by its qualified name. @@ -182,6 +217,8 @@ def on_post_page_macros(env: MacrosPlugin) -> None: # Check if all black format disable comments should be removed from the page if env.page.file.src_path in FILES_TO_REMOVE_BLACK_FORMATTER_DISABLE_COMMENT: # pyright: ignore[reportUnknownMemberType] env.markdown = env.markdown.replace("# fmt: off\n", "") # pyright: ignore[reportUnknownMemberType] + # Check if there are any admonitions to replace on the page + env.markdown = convert_gfm_alerts_to_admonitions(env.markdown) # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType] # Check if the title is correct if actual_title_match := HEADER_ONE_REGEX.search(env.markdown): # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType] actual_title = actual_title_match.group(1) diff --git a/mkdocs.yml b/mkdocs.yml index 1534b64..4269ca4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -152,6 +152,10 @@ nav: # The first entry in each section needs a hard-coded title to avoid being - index.md - The Basics: - Basic Usage: basic_usage.md - - Advanced Topics: - - Write to file: advanced\writing_to_a_file.md + - glossary.md + - CHANGELOG.md + - Contributing: + - Contributing Guidelines: CONTRIBUTING.md + - CODE_OF_CONDUCT.md + - LICENSE.md - API Reference: reference/ diff --git a/src/tm_data_types/datum/data_types.py b/src/tm_data_types/datum/data_types.py index c68fc44..1ca113a 100644 --- a/src/tm_data_types/datum/data_types.py +++ b/src/tm_data_types/datum/data_types.py @@ -43,7 +43,7 @@ def _check_type( def type_ratio(dtype_from: PossibleTypes, dtype_to: PossibleTypes) -> float: - """Calculate the ratio of ranges between two seperate types. + """Calculate the ratio of ranges between two separate types. Example: Converting from np.int16 to np.int8 will give you a ratio of 1/256. @@ -469,7 +469,7 @@ def _raw_sample_format_to_this_format( class Digitized(MeasuredData): - """Data that has been converted to seperate data streams along the 2nd axis.""" + """Data that has been converted to separate data streams along the 2nd axis.""" ################################################################################################ # Public Methods diff --git a/src/tm_data_types/datum/waveforms/analog_waveform.py b/src/tm_data_types/datum/waveforms/analog_waveform.py index 01c68dd..28ce0b1 100644 --- a/src/tm_data_types/datum/waveforms/analog_waveform.py +++ b/src/tm_data_types/datum/waveforms/analog_waveform.py @@ -38,7 +38,7 @@ class AnalogWaveformMetaInfo(WaveformMetaInfo): class AnalogWaveform(Waveform): - """Class which represents an analog waveform with a y and x axis.""" + """Class which represents an analog waveform with a y-axis and x-axis.""" ################################################################################################ # Dunder Methods @@ -72,7 +72,7 @@ def __setattr__(self, key: str, value: Any) -> None: self.__dict__.pop("normalized_vertical_values", None) if key == "y_axis_values": - # y axis values need to be typecase when set. + # y-axis values need to be typecase when set. if not isinstance(value, MeasuredData): super(Waveform, self).__setattr__("y_axis_values", RawSample(value)) else: @@ -139,7 +139,7 @@ def normalized_vertical_values(self) -> Normalized: # pyright: ignore [reportIn @property def y_axis_extent_magnitude(self) -> float: - """Get the magnitude extent of values that can be represented in the y axis units. + """Get the magnitude extent of values that can be represented in the y-axis units. Returns: A float value which represents the magnitude of what values which can be represented @@ -151,7 +151,7 @@ def y_axis_extent_magnitude(self) -> float: @y_axis_extent_magnitude.setter def y_axis_extent_magnitude(self, extent_magnitude: float) -> None: - """Set the spacing based on values that can be represented in the y axis units. + """Set the spacing based on values that can be represented in the y-axis units. Example: If the extent magnitude is 1.0 and the numpy type is a long, then it will diff --git a/src/tm_data_types/datum/waveforms/digital_waveform.py b/src/tm_data_types/datum/waveforms/digital_waveform.py index bb671e3..fc26580 100644 --- a/src/tm_data_types/datum/waveforms/digital_waveform.py +++ b/src/tm_data_types/datum/waveforms/digital_waveform.py @@ -30,7 +30,7 @@ class DigitalWaveformMetaInfo(WaveformMetaInfo): # pylint: disable=too-many-ins class DigitalWaveform(Waveform): - """Class which represents an digital waveform with a y and x axis.""" + """Class which represents a digital waveform with a y-axis and x-axis.""" ################################################################################################ # Dunder Methods diff --git a/src/tm_data_types/datum/waveforms/iq_waveform.py b/src/tm_data_types/datum/waveforms/iq_waveform.py index 0fe0425..47d7e5e 100644 --- a/src/tm_data_types/datum/waveforms/iq_waveform.py +++ b/src/tm_data_types/datum/waveforms/iq_waveform.py @@ -173,7 +173,7 @@ def iq_axis_extent_magnitude(self) -> float: @iq_axis_extent_magnitude.setter def iq_axis_extent_magnitude(self, extent_magnitude: float): - """Set the magnitude extent of values that can be represented in the y axis units. + """Set the magnitude extent of values that can be represented in the y-axis units. Args: extent_magnitude: A float value which represents the magnitude of what values which diff --git a/src/tm_data_types/datum/waveforms/waveform.py b/src/tm_data_types/datum/waveforms/waveform.py index 6af650d..2b2ea18 100644 --- a/src/tm_data_types/datum/waveforms/waveform.py +++ b/src/tm_data_types/datum/waveforms/waveform.py @@ -83,7 +83,7 @@ class Waveform(Datum, ABC): def __init__( self, ) -> None: - """Initialize the waveform's meta info and x axis specifications.""" + """Initialize the waveform's meta info and x-axis specifications.""" self.meta_info: Optional[WaveformMetaInfo] = None self.trigger_index: float = 0.0 self.source_name: Optional[str] = None diff --git a/src/tm_data_types/files_and_formats/csv/csv.py b/src/tm_data_types/files_and_formats/csv/csv.py index d9a3ad2..43468c4 100644 --- a/src/tm_data_types/files_and_formats/csv/csv.py +++ b/src/tm_data_types/files_and_formats/csv/csv.py @@ -90,7 +90,7 @@ def read_datum(self) -> WAVEFORM_TYPE: # pylint: disable=too-many-branches except ValueError: # otherwise the info is in the header if len(row) > 1: - # record length is special as it needs to be stored but it's not relevant + # record length is special as it needs to be stored, but it's not relevant # to the waveform values if row[0] == "Record Length": record_length = int(row[1]) @@ -168,7 +168,7 @@ def csv_writer(self) -> csv.writer: ################################################################################################ # Writing def _setup_generic_csv_header(self, waveform: Waveform) -> str: - """Setup the generic header values for the csv file during writing. + """Set up the generic header values for the csv file during writing. Args: waveform: The waveform used to get the generic header values. diff --git a/src/tm_data_types/files_and_formats/csv/data_formats/analog.py b/src/tm_data_types/files_and_formats/csv/data_formats/analog.py index a485474..3679d4a 100644 --- a/src/tm_data_types/files_and_formats/csv/data_formats/analog.py +++ b/src/tm_data_types/files_and_formats/csv/data_formats/analog.py @@ -63,7 +63,7 @@ def _set_waveform_values( # pyright: ignore [reportIncompatibleMethodOverride] Args: waveform: The analog waveform which is being formatted. - values_matrix: A matrix containing the x and y axis values. + values_matrix: A matrix containing the x-axis and y-axis values. """ normalized_vertical_values = Normalized(values_matrix[:, 1], as_type=np.float32) vertical_minimum = normalized_vertical_values.min() diff --git a/src/tm_data_types/files_and_formats/waveform_file.py b/src/tm_data_types/files_and_formats/waveform_file.py index d5a2e3b..8f4b80a 100644 --- a/src/tm_data_types/files_and_formats/waveform_file.py +++ b/src/tm_data_types/files_and_formats/waveform_file.py @@ -30,7 +30,7 @@ def __init__( Args: file_path: The path for the file to read/write from. - io_type: A file to represent what type of IO transferrence is occuring. + io_type: A file to represent what type of IO transference is occurring. """ self.file_path = file_path self.io_type = io_type @@ -74,7 +74,7 @@ def write(self, to_write_info: str) -> None: @staticmethod def update_bidict(original_bidict: bidict, operating_bidict: Dict[str, str]) -> bidict: - """Update a bi directional dict with new values. + """Update a bidirectional dict with new values. This may need to be a factory helper. diff --git a/src/tm_data_types/files_and_formats/wfm/wfm.py b/src/tm_data_types/files_and_formats/wfm/wfm.py index 7da14c9..86b4d59 100644 --- a/src/tm_data_types/files_and_formats/wfm/wfm.py +++ b/src/tm_data_types/files_and_formats/wfm/wfm.py @@ -121,7 +121,7 @@ def read_datum(self) -> DATUM_TYPE_VAR: formatted_data = WfmFormat() formatted_data.unpack_wfm_file(endian_prefix, version_number, self.fd) - waveform: DATUM_TYPE_VAR = self.DATUM_TYPE() # pylist: disable=abstract-class-instantiated + waveform: DATUM_TYPE_VAR = self.DATUM_TYPE() # pylint: disable=abstract-class-instantiated meta_data = self.META_DATA_TYPE( **self.META_DATA_TYPE.remap(self._META_DATA_LOOKUP.inverse, formatted_data.meta_data), ) @@ -152,10 +152,10 @@ def write_datum(self, waveform: Waveform) -> None: formatted_data = WfmFormat() if waveform.meta_info: - exlusive_meta_data = waveform.meta_info.operable_exclusive_metadata() + exclusive_meta_data = waveform.meta_info.operable_exclusive_metadata() formatted_data.meta_data = self.META_DATA_TYPE.remap( - self._META_DATA_LOOKUP, exlusive_meta_data + self._META_DATA_LOOKUP, exclusive_meta_data ) else: formatted_data.meta_data = self.META_DATA_TYPE.remap(self._META_DATA_LOOKUP, {}) @@ -190,7 +190,7 @@ def _check_metadata(self, meta_data: Dict[str, Union[str, Double, Long, Unsigned ) return True except TypeError as e: - # if have too many keywords, this format doesn't work + # if you have too many keywords, this format doesn't work if "unexpected keyword" in str(e): return False # if we are missing some keywords, that is fine diff --git a/src/tm_data_types/io_factory_methods.py b/src/tm_data_types/io_factory_methods.py index 49476f1..b984ca7 100644 --- a/src/tm_data_types/io_factory_methods.py +++ b/src/tm_data_types/io_factory_methods.py @@ -24,36 +24,62 @@ # pylint: disable=unused-argument def write_file( - file_path: str, - datum: Datum, + path: str, + waveform: Datum, product: InstrumentSeries = InstrumentSeries.TEKSCOPE, file_format: Optional[CSVFormats] = None, ) -> None: """Write a waveform to a provided file. + Process Overview: + 1. Lookup Table: The method begins by using a lookup table to determine the behavior based on the + waveform type. + 2. Formatting: The waveform is formatted according to its type. + 3. Writing: Finally, the formatted waveform is written to the specified file path. + + Special Cases: + - RawSample Type: No transformations are applied when saving data in the `.wfm` + format, if the waveform is of the [`RawSample`][tm_data_types.RawSample] type. + This is done to ensure that `.wfm` files are saved and loaded as quickly as possible. + - Normalized Type: If the waveform is of the [`Normalized`][tm_data_types.datum.data_types.Normalized] type, + a transformation is performed because `.wfm` files must contain digitized data, + with spacing and offset stored separately. + Args: - file_path: The path file to write to. - datum: The datum that is being written. + path: The path file to write to. + waveform: The waveform that is being written. product: The product being written to. file_format: A specialized file format we are writing as. """ - - _, path_extension = os.path.splitext(file_path) + _, path_extension = os.path.splitext(path) try: file_extension = FileExtensions[path_extension.replace(".", "").upper()] except KeyError as e: raise IOError(f"The {path_extension} extension cannot be written to.") from e # find the format based on the waveform extension - format_class: AbstractedFile = find_class_format(file_extension, type(datum)) + format_class: AbstractedFile = find_class_format(file_extension, type(waveform)) # using __init__ for instantiation due to pyright confusion - format_class = format_class(file_path, access_type(file_extension, write=True), product) + format_class = format_class(path, access_type(file_extension, write=True), product) with format_class as fd: - fd.write_datum(datum) + fd.write_datum(waveform) def read_file(file_path: str) -> DatumAlias: """Read a waveform from a provided file. + Process Overview: + 1. Lookup Table: Similar to [`write_file()`][tm_data_types.write_file], a lookup table is used to determine + the file extension. + 2. Type Detection: The method reads small sections of the file to identify the waveform type. + 3. Reformatting: The waveform is read and returned in the appropriate format, + depending on how the data is structured. + + Special Cases: + - All waveforms are returned in the [`RawSample`][tm_data_types.RawSample] format. + The data is reformatted for compatibility with the oscilloscope, which involves + mathematical transformations on the entire dataset. + This can be time-consuming, so using the `.wfm` format is recommended for efficiency. + Args: file_path: The path file to read from. """ @@ -117,6 +143,15 @@ def write_files_in_parallel( ) -> None: """Write a list of waveforms to a list of provided files in parallel. + This method offers a parallelized approach to writing multiple waveform files. + + Process Overview: + 1. Multiprocessing: The lists of file paths and waveforms are partitioned and processed in parallel. + 2. Writing: Each process uses the same method as [`write_file()`][tm_data_types.write_file] + to save its assigned waveforms. + + This method is particularly useful for saving multiple waveform files efficiently. + Args: file_paths: The path file to write to. datums: The datum that is being written. @@ -155,7 +190,8 @@ def _read_files(file_paths: str, file_queue: multiprocessing.Queue) -> None: """Read a waveform from a provided file. Args: - file_path: The file paths to read from. + file_paths: The file paths to read from. + file_queue: The queue to put the read data into. """ for file_path in file_paths: file_queue.put((file_path, read_file(file_path))) @@ -164,8 +200,16 @@ def _read_files(file_paths: str, file_queue: multiprocessing.Queue) -> None: def read_files_in_parallel(file_paths: List[str], force_process_count: int = 4) -> List[Datum]: """Read a list of files in parallel. + This method allows for the parallel reading of multiple waveform files. + + Process Overview: + 1. Multiprocessing: Similar to [`write_files_in_parallel()`][tm_data_types.write_files_in_parallel], + the file paths are partitioned and processed in parallel. + 2. Reading: The waveforms are read using the same process as [`read_file()`][tm_data_types.read_file], + and a queue of waveforms is returned. + Args: - file_paths: The file paths to read from. + file_paths: A list of file paths to read from. force_process_count: The number of processes that should be created for this operation. """ process_count = min(force_process_count, len(file_paths))