User API

This is an example function:

Public package exports for nanodent.

class nanodent.Experiment(timestamp: datetime.datetime, test: nanodent.models.SignalTable, stem: str = '', paths: nanodent.models.ExperimentPaths | None = None, source_path: pathlib.Path | None = None, source_format: str | None = None, metadata: dict[str, str]=<factory>, metadata_entries: tuple[nanodent.models.MetadataEntry, ...]=(), approach: nanodent.models.SignalTable | None = None, drift: nanodent.models.SignalTable | None = None, extra_sections: collections.abc.Mapping[str, nanodent.models.SignalTable]=<factory>, temperature_c: float | None = None, humidity_percent: float | None = None, segment_definitions: tuple[nanodent.models.SegmentDefinition, ...]=(), parsed_tip_area_function: nanodent.models.TipAreaFunction | None = None, tip_area_function: nanodent.models.TipAreaFunction | None = None, enabled: bool = True, disabled_reason: str | None = None, onset: 'OnsetDetectionResult | None' = None, force_peaks: 'ForcePeakDetectionResult | None' = None, unloading: 'UnloadingDetectionResult | None' = None, oliver_pharr: 'OliverPharrExperimentResult | None' = None, hertzian: 'HertzianExperimentResult | None' = None)
classmethod from_measurements(*, stem: str, timestamp: datetime, time: Any, displacement: Any, force: Any, time_unit: str = 's', displacement_unit: str = 'nm', force_unit: str = 'uN', raw_columns: tuple[str, str, str] | None = None, source_path: str | Path | None = None, source_format: str | None = None, metadata: Mapping[str, str] | None = None, metadata_entries: tuple[MetadataEntry, ...] = (), approach: SignalTable | None = None, drift: SignalTable | None = None, extra_sections: Mapping[str, SignalTable] | None = None, temperature_c: float | None = None, humidity_percent: float | None = None, segment_definitions: tuple[SegmentDefinition, ...] = (), enabled: bool = True, disabled_reason: str | None = None) Experiment

Create one experiment from array-like measurement signals.

classmethod from_tabular_data(table: Mapping[str, Any] | Any, *, stem: str, timestamp: datetime, time_column: str, displacement_column: str, force_column: str, time_unit: str = 's', displacement_unit: str = 'nm', force_unit: str = 'uN', source_path: str | Path | None = None, source_format: str | None = None, metadata: Mapping[str, str] | None = None, metadata_entries: tuple[MetadataEntry, ...] = (), approach: SignalTable | None = None, drift: SignalTable | None = None, extra_sections: Mapping[str, SignalTable] | None = None, temperature_c: float | None = None, humidity_percent: float | None = None, segment_definitions: tuple[SegmentDefinition, ...] = (), enabled: bool = True, disabled_reason: str | None = None) Experiment

Create one experiment from a mapping- or DataFrame-like table.

section(name: str) SignalTable

Return a named signal section.

Args:

name: Section name, typically approach, drift, or test.

Returns:

Requested signal table.

Raises:

KeyError: If the section does not exist on the experiment.

summary() dict[str, Any]

Return a compact summary useful for quick inspection.

Returns:

Dictionary containing key metadata and section sizes.

property trace: SignalTable

Return the primary canonical measurement trace.

unloading_curve(*, x: str = 'disp_nm', y: str = 'force_uN') tuple[ndarray[tuple[Any, ...], dtype[float64]], ndarray[tuple[Any, ...], dtype[float64]]]

Return the unloading branch as aligned NumPy arrays.

Args:

x: Trace column used for the x-values. y: Trace column used for the y-values.

Returns:

Pair of NumPy arrays sliced from the detected unloading start through the end of the test trace.

Raises:
ValueError: If no successful unloading result is attached or the

stored unloading start index is invalid.

KeyError: If either requested column is absent from the test trace.

with_enabled(enabled: bool, *, reason: str | None = None) Experiment

Return a copy of the experiment with updated enabled state.

Args:
enabled: Whether the experiment should participate in default

grouping, plotting, and analysis flows.

reason: Optional short reason recorded when disabling an

experiment. Enabled experiments always clear the reason.

Returns:

New experiment instance with updated enabled metadata.

with_force_peaks(result: ForcePeakDetectionResult | None) Experiment

Return a copy of the experiment with updated force peaks.

with_hertzian(result: HertzianExperimentResult | None) Experiment

Return a copy of the experiment with updated Hertzian results.

with_oliver_pharr(result: OliverPharrExperimentResult | None) Experiment

Return a copy of the experiment with updated analysis results.

with_onset(result: OnsetDetectionResult | None) Experiment

Return a copy of the experiment with updated onset results.

with_tip_area_function(tip_area_function: TipAreaFunction | None) Experiment

Return a copy of the experiment with updated tip-area override.

with_unloading(result: UnloadingDetectionResult | None) Experiment

Return a copy of the experiment with updated unloading data.

class nanodent.ExperimentGroup(stems: tuple[str, ...], index: int = 0)

A deterministic group of temporally related experiments.

resolve(study: Study, *, include_disabled: bool = False) tuple[Experiment, ...]

Resolve the group’s stems against the current study.

summary(study: Study, *, include_disabled: bool = True) dict[str, Any]

Return a notebook-friendly summary of the group.

class nanodent.ExperimentPaths(stem: str, hld_path: Path, tdm_path: Path | None = None, tdx_path: Path | None = None)

Paths belonging to one experiment stem.

class nanodent.ForcePeakDetectionResult(success: bool, reason: str | None = None, peaks: tuple[ForcePeakPosition, ...] = (), peak_count: int = 0, prominence: float = 100.0, threshold: float | None = 1.0)

Result of raw-force peak detection on one experiment.

summary() dict[str, Any]

Return a notebook-friendly summary row.

class nanodent.ForcePeakPosition(index: int, time_s: float | None, disp_nm: float | None, force_uN: float, prominence_uN: float | None)

One detected force peak with aligned experiment coordinates.

summary() dict[str, Any]

Return a notebook-friendly summary row.

class nanodent.HertzianExperimentResult(stem: str = '', success: bool = False, reason: str | None = None, fit_start_index: int = 0, fit_end_index: int = 0, fit_point_count: int = 0, used_smoothing: bool = False, smoothing: ~collections.abc.Mapping[str, ~typing.Any] | None = None, initial_onset_disp_nm: float | None = None, force_correction_uN: float | None = None, amplitude_uN_per_nm_3_2: float | None = None, h_onset_nm: float | None = None, reduced_modulus_uN_per_nm2: float | None = None, radius_nm: float | None = None, pop_in_load_uN: float | None = None, tau_max_uN_per_nm2: float | None = None, r_squared: float | None = None, x_fit: ~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.float64]] = <factory>, y_fit: ~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.float64]] = <factory>)

Result of one Hertzian onloading fit.

summary() dict[str, Any]

Return a notebook-friendly summary row.

class nanodent.MetadataEntry(key: str, value: str)

A single raw metadata line from an input file.

class nanodent.OliverPharrExperimentResult(stem: str = '', success: bool = False, reason: str | None = None, fit_model: str = 'linear_fraction', evaluation_index: int = 0, evaluation_force_uN: float | None = None, evaluation_disp_nm: float | None = None, unloading_start_index: int = 0, unloading_end_index: int = 0, fit_point_count: int = 0, used_smoothing: bool = False, smoothing: ~collections.abc.Mapping[str, ~typing.Any] | None = None, disp_correction_nm: float | None = None, force_correction_uN: float | None = None, stiffness_uN_per_nm: float | None = None, r_squared: float | None = None, linear_slope_uN_per_nm: float | None = None, linear_intercept_uN: float | None = None, linear_depth_intercept_nm: float | None = None, power_law_k: float | None = None, power_law_m: float | None = None, power_law_hf_nm: float | None = None, hardness_success: bool = False, hardness_reason: str | None = None, epsilon: float | None = None, onset_disp_nm: float | None = None, hmax_nm: float | None = None, contact_depth_nm: float | None = None, contact_area_nm2: float | None = None, hardness_uN_per_nm2: float | None = None, reduced_modulus_uN_per_nm2: float | None = None, tip_area_function: ~nanodent.models.TipAreaFunction | None = None, x_fit: ~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.float64]] = <factory>, y_fit: ~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.float64]] = <factory>)

Result of one Oliver-Pharr unloading fit.

summary() dict[str, Any]

Return a notebook-friendly summary row.

class nanodent.OnsetDetectionResult(success: bool, mode: str = 'relative', reason: str | None = None, onset_index: int | None = None, onset_time_s: float | None = None, onset_disp_nm: float | None = None, baseline_points: int = 0, baseline_start_index: int | None = None, baseline_end_index: int | None = None, baseline_mean_uN: float | None = None, baseline_offset_uN: float | None = None, baseline_std_uN: float | None = None, threshold_uN: float | None = None, absolute_threshold_uN: float | None = None, k: float = 4.0, consecutive: int = 1, used_smoothing: bool = False, smoothing: Mapping[str, Any] | None = None)

Result of sustained-threshold onset detection on one force signal.

summary() dict[str, Any]

Return a notebook-friendly summary row.

class nanodent.QualityCheckResult(enabled: bool, reason: str | None = None, onset_fraction: float | None = None, onset_disp_nm: float | None = None, rise_width_fraction: float | None = None)

Result of a heuristic experiment-quality check.

class nanodent.SegmentDefinition(number: int, segment_type: str, duration_s: float, begin_time_s: float, end_time_s: float, begin_demand: float | None, end_demand: float | None, points: int)

Metadata describing one acquisition segment.

class nanodent.SignalTable(columns: dict[str, ndarray[tuple[Any, ...], dtype[float64]]], point_count: int, raw_columns: tuple[str, ...])

Tabular numeric section data with normalized column names.

property column_names: tuple[str, ...]

Normalized column names in source order.

to_dict() dict[str, ndarray[tuple[Any, ...], dtype[float64]]]

Return a shallow copy of the column mapping.

Returns:

Dictionary mapping normalized column names to float64 arrays.

class nanodent.Study(experiments: tuple[Experiment, ...], tip_area_function: TipAreaFunction | None = None)

A collection of experiments sorted by acquisition timestamp.

analyze_hertzian(*, stems: Iterable[str] | str | None = None, smoothing: Mapping[str, Any] | None = None, fit_num_points: int = 200, peak_prominence: float = 100.0, peak_threshold: float | None = 1.0, include_disabled: bool = False, overwrite: bool = False) Study

Analyze selected experiments with a Hertzian onloading fit.

analyze_oliver_pharr(*, stems: Iterable[str] | str | None = None, fit_model: str = 'power_law_full', unloading_fraction: float | None = None, smoothing: Mapping[str, Any] | None = None, fit_num_points: int = 200, epsilon: float = 0.75, include_disabled: bool = False, overwrite: bool = False) Study

Analyze selected experiments with one Oliver-Pharr fit model.

classify_quality(*, min_robust_force_span_uN: float = 200.0, low_quantile: float = 0.4, high_quantile: float = 0.999, max_disp_nm: float = 1000.0, peak_bin_count: int = 48, peak_prominence_fraction: float = 0.05, min_secondary_peak_fraction: float = 0.1, require_two_peaks: bool = False, disp_z_threshold: float = 100.0, force_z_threshold: float = 70.0, bin_count: int = 24, baseline_bin_count: int = 4, onset_force_fraction: float = 0.05, target_force_fraction: float = 0.5, sustained_bins: int = 2, max_rise_width_fraction: float = 0.2) Study

Return a study with experiments flagged by quality heuristics.

detect_force_peaks(*, stems: Iterable[str] | str | None = None, prominence: float = 100.0, threshold: float | None = 1.0, include_disabled: bool = False, overwrite: bool = False) Study

Detect raw-force peaks on selected experiments.

detect_onset(*, stems: Iterable[str] | str | None = None, mode: str = 'relative', baseline_points: int = 100, baseline_start_index: int | None = None, baseline_end_index: int | None = None, k: float = 4.0, absolute_threshold_uN: float | None = None, consecutive: int = 5, smoothing: Mapping[str, Any] | None = None, include_disabled: bool = False, overwrite: bool = False) Study

Detect onset on selected experiments using the test force signal.

detect_unloading(*, stems: Iterable[str] | str | None = None, include_disabled: bool = False, overwrite: bool = False) Study

Detect unloading-start indices on selected experiments.

disable_experiments(stems: Iterable[str] | str, *, reason: str = 'manual') Study

Return a study with selected experiment stems disabled.

enable_experiments(stems: Iterable[str] | str) Study

Return a study with selected experiment stems enabled.

get_experiments(*, stems: Iterable[str] | str | None = None, include_disabled: bool = False) tuple[Experiment, ...]

Return study experiments, optionally filtered by stems.

group_by_datetime_ranges(ranges: Iterable[tuple[datetime, datetime]], *, include_disabled: bool = False) list[ExperimentGroup]

Create groups from explicit inclusive datetime windows.

group_by_time_gap(max_gap: timedelta = datetime.timedelta(seconds=1800), *, include_disabled: bool = False) list[ExperimentGroup]

Group experiments by the gap between consecutive timestamps.

group_scalar_series(metric: str, *, groups: Sequence[ExperimentGroup] | None = None, max_gap: timedelta = datetime.timedelta(seconds=1800), include_disabled: bool = False, drop_missing: bool = True) list[dict[str, Any]]

Return one aggregated scalar row per resolved experiment group.

load_session(path: str | Path, *, overwrite: bool = False) Study

Apply a previously saved analysis session onto this study.

regroup(groups: Iterable[Sequence[Experiment | str]], *, include_disabled: bool = False) list[ExperimentGroup]

Create explicit groups from Python-side experiment selections.

resolve_group(group: ExperimentGroup, *, include_disabled: bool = False) tuple[Experiment, ...]

Resolve a stem-based group against this study.

save_session(path: str | Path) Path

Persist experiment flags and attached analysis results.

scalar_series(metric: str, *, stems: Iterable[str] | str | None = None, include_disabled: bool = False, drop_missing: bool = True) list[dict[str, Any]]

Return timestamped scalar rows for one supported metric.

set_enabled(stems: Iterable[str] | str, *, enabled: bool, reason: str | None = None) Study

Return a study with selected stems manually enabled or disabled.

with_tip_area_function(tip_area_function: TipAreaFunction | None) Study

Return a copy of the study with a new study-wide tip area.

class nanodent.TipAreaFunction(c0: float = 0.0, c1: float = 0.0, c2: float = 0.0, c3: float = 0.0, c4: float = 0.0, c5: float = 0.0)

Polynomial-style tip area function for contact-area estimation.

evaluate(contact_depth_nm: float) float

Return the contact area in nm^2 for one contact depth.

class nanodent.UnloadingDetectionResult(success: bool, method: str = 'max_force', reason: str | None = None, start_index: int | None = None, start_time_s: float | None = None, start_disp_nm: float | None = None, start_force_uN: float | None = None, end_disp_nm: float | None = None)

Result of unloading-start detection on one experiment curve.

summary() dict[str, Any]

Return a notebook-friendly summary row.

nanodent.analyze_hertzian(disp_nm: ArrayLike, force_uN: ArrayLike, *, fit_end_index: int, smoothing: Mapping[str, Any] | None = None, fit_num_points: int = 200, initial_onset_disp_nm: float | None = None, baseline_offset_uN: float | None = None, reduced_modulus_uN_per_nm2: float | None = None, pop_in_load_uN: float | None = None, stem: str = '') HertzianExperimentResult

Fit a Hertzian onloading model up to a force-peak endpoint.

Args:

disp_nm: Displacement values in acquisition order. force_uN: Force values in acquisition order. fit_end_index: Inclusive local index of the first force peak. smoothing: Optional keyword arguments forwarded to nanodent.savgol. fit_num_points: Number of dense fitted points for plotting. initial_onset_disp_nm: Optional initial guess for the fitted onset. baseline_offset_uN: Optional force baseline offset subtracted before

fitting.

reduced_modulus_uN_per_nm2: Optional Oliver-Pharr reduced modulus used

to derive the Hertzian radius and maximum shear stress.

pop_in_load_uN: Optional second force-peak load used to derive maximum

shear stress.

stem: Optional experiment label propagated by higher-level wrappers.

Returns:

Result object containing fitted Hertzian diagnostics. Invalid or incomplete fit windows are returned as unsuccessful results instead of raising, except for malformed input arguments.

nanodent.analyze_oliver_pharr(disp_nm: ArrayLike, force_uN: ArrayLike, *, unloading_start_trace_index: int = 0, fit_model: Literal['linear_fraction', 'power_law_full'] = 'power_law_full', unloading_fraction: float | None = None, smoothing: Mapping[str, Any] | None = None, fit_num_points: int = 200, onset_disp_nm: float | None = None, baseline_offset_uN: float | None = None, epsilon: float = 0.75, tip_area_function: TipAreaFunction | None = None, stem: str = '') OliverPharrExperimentResult

Fit one supported Oliver-Pharr model to an unloading branch.

Args:
disp_nm: Displacement values from the unloading branch in

acquisition order.

force_uN: Force values from the unloading branch in acquisition

order.

unloading_start_trace_index: Absolute trace index of the first

unloading sample. This keeps result indices aligned with the original experiment trace.

fit_model: Fitting strategy. linear_fraction fits a straight line

to the early unloading branch. power_law_full fits f = k * (h - hf)^m to the full unloading branch.

unloading_fraction: Fraction of the post-start unloading branch used

for linear_fraction. When omitted, defaults to 0.2.

smoothing: Optional keyword arguments forwarded to nanodent.savgol.

When provided, the same filter is applied to displacement and force before fitting.

fit_num_points: Number of points used to evaluate the dense fitted

curve for plotting.

onset_disp_nm: Optional onset displacement used to compute

onset-corrected hardness diagnostics.

baseline_offset_uN: Optional force baseline offset subtracted before

evaluation and fitting.

epsilon: Geometry factor used for contact-depth estimation. tip_area_function: Optional tip area function used for contact-area

estimation. When omitted, defaults to 24.5 * hc^2.

stem: Optional experiment label propagated by higher-level wrappers.

Returns:

Result object containing fitted unloading diagnostics. Invalid or incomplete unloading segments are returned as unsuccessful results instead of raising, except for malformed input arguments.

nanodent.calculate_hertzian_radius(hertzian_amplitude_uN_per_nm_3_2: float, reduced_modulus_uN_per_nm2: float) float

Return Hertzian tip radius implied by amplitude and reduced modulus.

nanodent.calculate_tau_max(reduced_modulus_uN_per_nm2: float, hertzian_amplitude_uN_per_nm_3_2: float, pop_in_load_uN: float) float

Return the maximum shear stress from OP, Hertzian, and pop-in values.

nanodent.classify_flat_force(force_uN: ArrayLike, *, min_robust_force_span_uN: float = 200.0, low_quantile: float = 0.05, high_quantile: float = 0.95) QualityCheckResult

Classify runs whose force signal remains nearly flat throughout.

Args:

force_uN: Force values from the test section. min_robust_force_span_uN: Minimum acceptable robust force span between

the selected quantiles.

low_quantile: Lower quantile used for the robust force span. high_quantile: Upper quantile used for the robust force span.

Returns:

Quality classification result. Flat signals are marked with reason flat_force.

nanodent.classify_gradual_onset(disp_nm: ArrayLike, force_uN: ArrayLike, *, bin_count: int = 24, baseline_bin_count: int = 4, onset_force_fraction: float = 0.05, target_force_fraction: float = 0.5, sustained_bins: int = 2, max_rise_width_fraction: float = 0.2) QualityCheckResult

Classify curves whose onset rises too gradually in displacement.

The heuristic sorts the force-displacement samples by displacement, averages them onto coarse displacement bins, and measures how much displacement the coarse curve needs to rise from an early threshold to a mid-force threshold. Curves whose rise width is too broad are classified as gradual onset.

Args:

disp_nm: Displacement values from the test section. force_uN: Force values from the test section. bin_count: Number of coarse displacement bins used to suppress

point-to-point loops and noise.

baseline_bin_count: Number of leftmost bins used to estimate the

initial baseline force level.

onset_force_fraction: Lower force fraction used to define onset. target_force_fraction: Upper force fraction used to define where the

onset rise is considered complete.

sustained_bins: Number of consecutive bins that must exceed the lower

onset threshold before the onset is accepted.

max_rise_width_fraction: Maximum allowed relative displacement width

between the lower and upper force thresholds.

Returns:

Quality classification result with optional onset diagnostics.

nanodent.classify_high_displacement(disp_nm: ArrayLike, *, max_disp_nm: float = 1000.0) QualityCheckResult

Classify runs whose test displacement exceeds a hard limit.

Args:

disp_nm: Displacement values from the test section. max_disp_nm: Maximum allowed displacement in nanometers.

Returns:

Quality classification result. Curves that exceed the displacement limit are marked with reason high_disp.

nanodent.classify_outlier_jumps(disp_nm: ArrayLike, force_uN: ArrayLike, *, disp_z_threshold: float = 100.0, force_z_threshold: float = 70.0) QualityCheckResult

Classify experiments with isolated local spikes in displacement or force.

Args:

disp_nm: Displacement values from the test section. force_uN: Force values from the test section. disp_z_threshold: Robust z-score threshold for isolated displacement

spikes relative to neighboring samples.

force_z_threshold: Robust z-score threshold for isolated force spikes

relative to neighboring samples.

Returns:

Quality classification result with reason outlier_disp or outlier_force when an isolated local spike is detected.

nanodent.classify_peak_balance(disp_nm: ArrayLike, force_uN: ArrayLike, *, peak_bin_count: int = 48, peak_prominence_fraction: float = 0.05, min_secondary_peak_fraction: float = 0.1, require_two_peaks: bool = False) QualityCheckResult

Classify curves whose second resolved peak is too small.

The heuristic sorts the force-displacement samples by displacement, averages them onto coarse displacement bins, smooths the coarse force curve, and compares the two strongest resolved peaks.

If fewer than two prominent peaks are resolved after smoothing, the heuristic abstains by default. Set require_two_peaks=True to disable runs unless two resolved peaks are present.

Args:

disp_nm: Displacement values from the test section. force_uN: Force values from the test section. peak_bin_count: Number of coarse displacement bins used before peak

detection.

peak_prominence_fraction: Minimum prominence for resolved peaks,

expressed as a fraction of the coarse-force dynamic range.

min_secondary_peak_fraction: Minimum allowed ratio between the

second-highest and highest resolved peaks.

require_two_peaks: When true, disable curves that do not resolve at

least two prominent peaks after smoothing.

Returns:

Quality classification result. Curves with an undersized second peak, or without two resolved peaks when require_two_peaks is true, are marked with reason weak_second_peak.

nanodent.classify_quality(disp_nm: ArrayLike, force_uN: ArrayLike, *, min_robust_force_span_uN: float = 200.0, low_quantile: float = 0.4, high_quantile: float = 0.999, max_disp_nm: float = 1000.0, peak_bin_count: int = 48, peak_prominence_fraction: float = 0.05, min_secondary_peak_fraction: float = 0.1, require_two_peaks: bool = False, disp_z_threshold: float = 100.0, force_z_threshold: float = 70.0, bin_count: int = 24, baseline_bin_count: int = 4, onset_force_fraction: float = 0.05, target_force_fraction: float = 0.5, sustained_bins: int = 2, max_rise_width_fraction: float = 0.2) QualityCheckResult

Run the enabled quality heuristics in order and return the first match.

Args:

disp_nm: Displacement values from the test section. force_uN: Force values from the test section. min_robust_force_span_uN: Minimum acceptable robust force span for the

flat-force check.

low_quantile: Lower quantile used for the robust force span. high_quantile: Upper quantile used for the robust force span. max_disp_nm: Maximum allowed displacement before disabling the

experiment.

peak_bin_count: Number of coarse displacement bins used for the

peak-balance heuristic.

peak_prominence_fraction: Minimum prominence used to resolve peaks,

relative to the coarse-force dynamic range.

min_secondary_peak_fraction: Minimum allowed ratio between the

second-highest and highest resolved peaks.

require_two_peaks: When true, disable curves that do not resolve at

least two peaks after smoothing.

disp_z_threshold: Robust z-score threshold for isolated displacement

spikes.

force_z_threshold: Robust z-score threshold for isolated force spikes. bin_count: Number of coarse displacement bins for gradual-onset

detection.

baseline_bin_count: Number of early bins used for baseline force. onset_force_fraction: Lower force fraction used to define onset. target_force_fraction: Upper force fraction used to define where the

onset rise is considered complete.

sustained_bins: Number of consecutive bins that must exceed the onset

threshold.

max_rise_width_fraction: Maximum allowed displacement width between the

lower and upper onset thresholds before disabling the experiment.

Returns:

First disabling classification that matches, otherwise an enabled result.

nanodent.detect_force_peaks(force_uN: ArrayLike, *, time_s: ArrayLike | None = None, disp_nm: ArrayLike | None = None, prominence: float = 100.0, threshold: float | None = 1.0) ForcePeakDetectionResult

Detect up to two strongest force peaks in a raw force signal.

Args:

force_uN: Force values in acquisition order. time_s: Optional time values aligned with force_uN. disp_nm: Optional displacement values aligned with force_uN. prominence: Minimum peak prominence passed to find_peaks. threshold: Minimum peak threshold passed to find_peaks.

Returns:

Result object containing up to two strongest peaks. Signals with no detected peaks return an unsuccessful result instead of raising, except for malformed input arguments.

nanodent.detect_onset(force_uN: ArrayLike, *, time_s: ArrayLike | None = None, disp_nm: ArrayLike | None = None, mode: Literal['relative', 'absolute'] = 'relative', baseline_points: int = 100, baseline_start_index: int | None = None, baseline_end_index: int | None = None, k: float = 4.0, absolute_threshold_uN: float | None = None, consecutive: int = 5, smoothing: Mapping[str, Any] | None = None) OnsetDetectionResult

Detect the first sustained threshold crossing in one force signal.

Args:

force_uN: Force values in acquisition order. time_s: Optional time values aligned with force_uN. disp_nm: Optional displacement values aligned with force_uN. mode: Thresholding mode. relative uses baseline mean plus

k * baseline_std; absolute compares directly against absolute_threshold_uN.

baseline_points: Number of leading samples used to estimate the

baseline statistics. The effective count is clipped to the signal length.

baseline_start_index: Optional inclusive start index of the baseline

window.

baseline_end_index: Optional exclusive end index of the baseline

window.

k: Number of baseline standard deviations above the mean required for

the detection threshold.

absolute_threshold_uN: Absolute threshold used in absolute mode. consecutive: Number of consecutive samples above the threshold needed

to accept an onset.

smoothing: Optional keyword arguments forwarded to nanodent.savgol.

Returns:

Result object containing the onset index and baseline diagnostics. Signals with no sustained crossing return an unsuccessful result instead of raising, except for malformed input arguments.

nanodent.detect_unloading(force_uN: ArrayLike, *, time_s: ArrayLike | None = None, disp_nm: ArrayLike | None = None, method: Literal['max_force'] = 'max_force') UnloadingDetectionResult

Detect the start of the unloading branch in one test curve.

Args:

force_uN: Force values in acquisition order. time_s: Optional time values aligned with force_uN. disp_nm: Optional displacement values aligned with force_uN. method: Detection strategy. max_force uses the global maximum force

sample as unloading start.

Returns:

Result object containing the unloading-start coordinates. The current default method always succeeds for non-empty, well-formed signals.

nanodent.load_experiment(path: str | Path) Experiment

Load a single experiment from disk.

Args:
path: Path to an experiment .hld file or to a sibling file that

shares the same experiment stem.

Returns:

Parsed experiment including metadata, numeric sections, and sibling file paths.

nanodent.load_folder(path: str | Path, *, recursive: bool = False) Study

Load all parseable experiments from a folder.

Args:

path: Directory containing one or more experiment file triplets. recursive: When True, also scan subdirectories recursively.

Returns:

Study containing all experiments discovered in the folder, sorted by acquisition timestamp.

Raises:

NotADirectoryError: If path does not point to a directory.

nanodent.plot_experiments(*args: Any, **kwargs: Any) Axes

Lazily import the experiment force-displacement plotting helper.

Args:
*args: Positional args forwarded to

nanodent.plotting.plot_experiments.

**kwargs: Keyword args forwarded to

nanodent.plotting.plot_experiments.

Returns:

Axes containing the plotted experiment force-displacement curves.

nanodent.plot_group_timeline(*args: Any, **kwargs: Any) tuple[Figure, Axes]

Lazily import the group timeline plotting helper when needed.

Args:
*args: Positional args forwarded to

nanodent.plotting.plot_group_timeline.

**kwargs: Keyword args forwarded to

nanodent.plotting.plot_group_timeline.

Returns:

Figure and axes containing the group timeline visualization.

nanodent.save_experiment_plots(*args: Any, **kwargs: Any) list[Path]

Lazily import and save one plot file per experiment.

Args:
*args: Positional args forwarded to

nanodent.plotting.save_experiment_plots.

**kwargs: Keyword args forwarded to

nanodent.plotting.save_experiment_plots.

Returns:

Paths to the saved plot files.

nanodent.savgol(values: ArrayLike, *, window_length: int = 31, polyorder: int = 3, mode: str = 'interp') ndarray[tuple[Any, ...], dtype[float64]]

Apply a Savitzky-Golay filter with window coercion for short signals.

Args:

values: One-dimensional signal to smooth. window_length: Preferred smoothing window length. polyorder: Polynomial order used by the filter. mode: Edge-handling mode forwarded to SciPy.

Returns:

Smoothed float64 NumPy array with the same shape as the input.