From bf9c23f50e8b4a5139273711bd4b8bd53beb418e Mon Sep 17 00:00:00 2001 From: micah johnson Date: Tue, 27 Jan 2026 22:25:39 -0700 Subject: [PATCH 1/4] =?UTF-8?q?Bump=20version:=200.10.2=20=E2=86=92=200.10?= =?UTF-8?q?.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- setup.cfg | 2 +- study_lyte/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 21d0e97..2da24e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "study-lyte" -version = "0.10.2" +version = "0.10.3" description = "Analysis software for the Lyte probe, a digital penetrometer for studying snow" keywords = ["snow penetrometer", "smart probe", "digital penetrometer", 'lyte probe', "avalanches", "snow densiy"] readme = "README.rst" diff --git a/setup.cfg b/setup.cfg index 9379442..faa443c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.10.2 +current_version = 0.10.3 commit = True tag = True diff --git a/study_lyte/__init__.py b/study_lyte/__init__.py index 4772226..1cbf8ce 100644 --- a/study_lyte/__init__.py +++ b/study_lyte/__init__.py @@ -2,4 +2,4 @@ __author__ = """Micah Johnson """ __email__ = 'info@adventuredata.com' -__version__ = '0.10.2' +__version__ = '0.10.3' From 1335bd2da8ef7673eeb66fcb7015e506ba2da430 Mon Sep 17 00:00:00 2001 From: micah johnson Date: Wed, 4 Feb 2026 06:39:18 -0700 Subject: [PATCH 2/4] Speeding up the stop detection here by 2 orders of magnitude! --- study_lyte/detect.py | 22 ++++++++-------------- study_lyte/io.py | 5 +++-- tests/conftest.py | 6 +++--- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/study_lyte/detect.py b/study_lyte/detect.py index 48a1f98..2145a0e 100644 --- a/study_lyte/detect.py +++ b/study_lyte/detect.py @@ -115,20 +115,14 @@ def get_signal_event(signal_series, threshold=0.001, search_direction='forward', # if we have results, find the first match with n points that meet the criteria if n_points > 1 and len(ind) > 0: - npnts = n_points - 1 - id_diff = np.ones_like(ind) * 0 - id_diff[1:] = (ind[1:] - ind[0:-1]) - id_diff[0] = 1 - id_diff = np.abs(id_diff) - spacing_ind = [] - - # Determine if the last n points are all 1 idx apart - for i, ix in enumerate(ind): - if i >= npnts: - test_arr = id_diff[i - npnts:i + 1] - if all(test_arr == 1): - spacing_ind.append(ix) - ind = spacing_ind + # Vectorized consecutive point detection + diffs = np.diff(ind) + # Find runs of consecutive indices (diff == 1) + consecutive = np.concatenate([[False], diffs == 1]) + # Count consecutive runs + for i in range(n_points - 2): + consecutive[1:] = consecutive[1:] & consecutive[:-1] + ind = ind[consecutive] # If no results are found, return the first index the series if len(ind) == 0: diff --git a/study_lyte/io.py b/study_lyte/io.py index 4b51a27..d76705d 100644 --- a/study_lyte/io.py +++ b/study_lyte/io.py @@ -1,4 +1,5 @@ -from typing import Tuple +from pathlib import Path +from typing import Tuple, Union import pandas as pd import numpy as np @@ -36,7 +37,7 @@ def read_data(f:str, metadata:dict, header_position:int) -> Tuple[pd.DataFrame, df['time'] = np.linspace(0, n/sr, n) return df, metadata -def read_csv(f: str) -> Tuple[pd.DataFrame, dict]: +def read_csv(f: Union[str, Path]) -> Tuple[pd.DataFrame, dict]: """ Reads any Lyte probe CSV and returns a dataframe and metadata dictionary from the header diff --git a/tests/conftest.py b/tests/conftest.py index 9577580..e40fc64 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,16 +2,16 @@ from os.path import dirname, join from study_lyte.io import read_csv from study_lyte.profile import LyteProfileV6 - +from pathlib import Path @pytest.fixture(scope='session') def data_dir(): - return join(dirname(__file__), 'data') + return Path(__file__).parent.joinpath('data') @pytest.fixture(scope='function') def raw_df(data_dir, fname): - df, meta = read_csv(join(data_dir, fname)) + df, meta = read_csv(data_dir.joinpath(fname)) return df @pytest.fixture(scope='function') From d0fbdc1a4bb0bbbeae1bfdea9850aee1e7e888f1 Mon Sep 17 00:00:00 2001 From: micah johnson Date: Wed, 4 Feb 2026 06:44:39 -0700 Subject: [PATCH 3/4] small speed up on updward motion detection --- study_lyte/profile.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/study_lyte/profile.py b/study_lyte/profile.py index 2777b75..31356c2 100644 --- a/study_lyte/profile.py +++ b/study_lyte/profile.py @@ -300,16 +300,13 @@ def has_upward_motion(self): Bool indicating if upward motion was detected """ if self._has_upward_motion is None: - self._has_upward_motion = False - # crop the depth data and down sample for speedy check + # crop the depth data and downsample for speedy check n = get_points_from_fraction(len(self.depth), 0.005) - coarse = self.depth.iloc[self.start.index:self.stop.index:n] - # loop and find any values greater than the current value - for i,v in coarse.items(): - upward = np.any(coarse.loc[i:] > v + 5) - if upward: - self._has_upward_motion = True - break + data = self.depth.iloc[self.start.index:self.stop.index:n].values + + # Vectorized: check if any point rises > 5 above the running minimum + cummin = np.minimum.accumulate(data) + self._has_upward_motion = bool(np.any(data - cummin > 5)) return self._has_upward_motion From 3c23d5da1cbaeb5d3601d81421c784de5a3ce1cf Mon Sep 17 00:00:00 2001 From: micah johnson Date: Wed, 4 Feb 2026 06:59:31 -0700 Subject: [PATCH 4/4] 10% speed up in io --- study_lyte/io.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/study_lyte/io.py b/study_lyte/io.py index d76705d..e1ba82c 100644 --- a/study_lyte/io.py +++ b/study_lyte/io.py @@ -25,18 +25,34 @@ def find_metadata(f:str) -> [int, dict]: break return header_position, metadata -def read_data(f:str, metadata:dict, header_position:int) -> Tuple[pd.DataFrame, dict]: - """Read just the csv to enable parsing metadata and header position separately""" - df = pd.read_csv(f, header=header_position) - # Drop any columns written with the plain index - df.drop(df.filter(regex="Unname"), axis=1, inplace=True) - if 'time' not in df and 'SAMPLE RATE' in metadata: +def read_data(f: Union[str, Path], metadata: dict, header_position: int) -> Tuple[pd.DataFrame, dict]: + """ + Reads just the data from the Lyte probe CSV file + Args: + f: Path to csv, or file buffer + metadata: Dictionary of metadata from the header + header_position: Line number where the header ends + Returns: + tuple: + **df**: pandas Dataframe + **metadata**: dictionary containing header info + """ + # Use engine='c' explicitly and specify dtypes if known + df = pd.read_csv(f, header=header_position, engine='c') + + # Faster column dropping - avoid regex + unnamed_cols = [c for c in df.columns if c.startswith('Unnamed')] + if unnamed_cols: + df.drop(columns=unnamed_cols, inplace=True) + + if 'time' not in df.columns and 'SAMPLE RATE' in metadata: sr = int(metadata['SAMPLE RATE']) n = len(df) - df['time'] = np.linspace(0, n/sr, n) + df['time'] = np.linspace(0, n / sr, n) return df, metadata + def read_csv(f: Union[str, Path]) -> Tuple[pd.DataFrame, dict]: """ Reads any Lyte probe CSV and returns a dataframe