{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# `TableParser`, `DelimTxtParser`, and `NdaParser`\n", "\n", "A `TableParser` reads tabular data from a file and normalises the column names to BDF standards.\n", "Two concrete parsers are shown here:\n", "- **`DelimTxtParser`** \u2013 reads delimiter-separated text files (CSV, TSV, etc.)\n", "- **`NdaParser`** \u2013 reads Neware's binary `.nda` / `.ndax` format\n", "\n", "Both expose the same interface: `matches_ext()`, `read_column_headings()`, `normalizer_score()`, and `read()`." ] }, { "cell_type": "code", "execution_count": null, "id": "1", "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "from bdf.table_parsers import DelimTxtParser, NdaParser\n", "from bdf.table_normalizers import TableNormalizer, Syn, DateTimeSyn\n", "from bdf.file_utils import resolve_source\n", "\n", "BIOLOGIC_URL = (\n", " \"https://zenodo.org/api/records/18986774/files/\"\n", " \"SINTEF__NaCR32140-MP10-04__2025-08-25__GITT_0p05C_25degC__BioLogic.mpt/content\"\n", ")\n", "NEWARE_NDA_URL = (\n", " \"https://zenodo.org/api/records/18986774/files/\"\n", " \"SINTEF__G20M7-202512-Gru6mV__20251228__C30__25degC__Neware.nda/content\"\n", ")" ] }, { "cell_type": "markdown", "id": "2", "metadata": {}, "source": [ "## `DelimTxtParser` \u2014 delimited text files\n", "\n", "Construct a `DelimTxtParser` with a `TableNormalizer` that maps raw column names to BDF fields.\n", "The Biologic BT-Lab file has tab-separated columns including `time/s`, `Ecell/V`, `I/mA`, and `cycle number`.\n", "Unit-template syns like `Syn(hdr='Ecell/{unit}')` match any column whose name fits the pattern with a recognised unit.\n", "Matching is case-sensitive, so the syn root must match the source header's casing exactly outside the `{unit}` slot." ] }, { "cell_type": "code", "execution_count": null, "id": "3", "metadata": {}, "outputs": [], "source": [ "biologic_file = resolve_source(BIOLOGIC_URL)\n", "\n", "biologic_parser = DelimTxtParser(\n", " normalizer=TableNormalizer(\n", " test_time_second=(Syn(hdr=\"time/{unit}\"),),\n", " voltage_volt=(Syn(hdr=\"Ecell/{unit}\"),),\n", " current_ampere=(Syn(hdr=\"I/{unit}\"),),\n", " cycle_count=(Syn(hdr=\"cycle number\"),),\n", " )\n", ")\n", "biologic_parser" ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [ "`matches_ext()` checks the file extension against the parser's accepted types" ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": {}, "outputs": [], "source": [ "print(\"matches .txt: \", biologic_parser.matches_ext(\".txt\"))\n", "print(\"matches .xlsx:\", biologic_parser.matches_ext(\".xlsx\"))" ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "`read_column_headings()` sniffs only the header row \u2014 no data rows loaded\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [], "source": [ "biologic_parser.read_column_headings(biologic_file)" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "`normalizer_score()` counts how many columns the normalizer can resolve\n" ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": {}, "outputs": [], "source": [ "biologic_parser.normalizer_score(biologic_file)" ] }, { "cell_type": "markdown", "id": "10", "metadata": {}, "source": [ "`read()` returns a normalised polars LazyFrame with BDF-standard column names" ] }, { "cell_type": "code", "execution_count": null, "id": "11", "metadata": {}, "outputs": [], "source": [ "biologic_parser.read(biologic_file).collect()" ] }, { "cell_type": "markdown", "id": "12", "metadata": {}, "source": [ "## Graceful degradation \u2014 empty normalizer preserves source column names\n", "\n", "When a `TableNormalizer()` with no synonyms is supplied, `read()` still works but\n", "keeps the original column names from the file. This is useful for exploring an unfamiliar file." ] }, { "cell_type": "code", "execution_count": null, "id": "13", "metadata": {}, "outputs": [], "source": [ "raw_parser = DelimTxtParser(normalizer=TableNormalizer())\n", "raw_parser.read(biologic_file, validate=False).collect()" ] }, { "cell_type": "markdown", "id": "14", "metadata": {}, "source": [ "## `NdaParser` \u2014 Neware binary files\n", "\n", "`NdaParser` reads Neware's binary `.nda` / `.ndax` format via `fastnda`.\n", "Its extension handling targets the binary format, the opposite of `DelimTxtParser`: `.nda` returns `True`, `.csv` returns `False`." ] }, { "cell_type": "code", "execution_count": null, "id": "15", "metadata": {}, "outputs": [], "source": [ "neware_file = resolve_source(NEWARE_NDA_URL)\n", "\n", "neware_parser = NdaParser(\n", " normalizer=TableNormalizer(\n", " test_time_second=(Syn(hdr=\"total_time_{unit}\"),),\n", " voltage_volt=(Syn(hdr=\"voltage_{unit}\"),),\n", " current_ampere=(Syn(hdr=\"current_{unit}\"),),\n", " cycle_count=(Syn(hdr=\"cycle_count\"),),\n", " ),\n", ")\n", "neware_parser" ] }, { "cell_type": "code", "execution_count": null, "id": "16", "metadata": {}, "outputs": [], "source": [ "print(\"matches .nda: \", neware_parser.matches_ext(\".nda\"))\n", "print(\"matches .csv: \", neware_parser.matches_ext(\".csv\"))" ] }, { "cell_type": "code", "execution_count": null, "id": "17", "metadata": {}, "outputs": [], "source": [ "neware_parser.read_column_headings(neware_file)" ] }, { "cell_type": "code", "execution_count": null, "id": "18", "metadata": {}, "outputs": [], "source": [ "neware_parser.read(neware_file)" ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" } }, "nbformat": 4, "nbformat_minor": 5 }