Metadata-Version: 2.4
Name: acycle
Version: 0.5.4
Summary: AcyclePy: Advanced cyclostratigraphy toolkit
Author-email: Acycle Team <acycle@example.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/jnccClub/AcyclePy
Project-URL: Repository, https://github.com/jnccClub/AcyclePy.git
Project-URL: Issue Tracker, https://github.com/jnccClub/AcyclePy.git/issues
Project-URL: Documentation, https://github.com/jnccClub/AcyclePy/wiki
Keywords: cyclostratigraphy,time-series,spectral,wavelet,geology
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.20.0
Requires-Dist: pandas>=1.3.0
Requires-Dist: PySide6>=6.0.0
Requires-Dist: matplotlib>=3.3.0
Requires-Dist: scipy>=1.6.0
Requires-Dist: scikit-image>=0.18.0
Requires-Dist: scikit-learn>=0.24.0
Requires-Dist: Pillow>=8.0.0
Requires-Dist: qt-material>=2.0.0
Requires-Dist: astropy>=5.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: black>=22.0.0; extra == "dev"
Requires-Dist: flake8>=4.0.0; extra == "dev"
Provides-Extra: full
Requires-Dist: sounddevice>=0.4.0; extra == "full"
Requires-Dist: h5py>=3.0.0; extra == "full"
Requires-Dist: netCDF4>=1.5.0; extra == "full"

# AcyclePy — Advanced Cyclostratigraphy and Time Series Analysis Toolkit

**Version 0.5.1** | [PyPI](https://pypi.org/project/acycle/) | [Repository](https://github.com/jnccClub/AcyclePy) | MIT License

AcyclePy is the Python library companion to the **Acycle** desktop application
([Li, Hinnov & Kump, 2019](https://doi.org/10.1016/j.cageo.2019.02.011)). It
provides a **programmatic API** (no GUI required) for scientific time-series
analysis: cyclostratigraphy, spectral analysis, wavelet analysis, filtering,
age modeling, astronomical calculations, image digitization, and more.

The library follows a Pyleoclim-style *fluent design*: load a time or depth
series, chain preprocessing and analysis calls, then plot or export results
— all in a few lines of code.

```python
import acycle as ac

s = ac.Series.from_file("data.txt", x_unit="m", y_name="GR")
s2 = s.clean(sort=True).interpolate(step=0.33).detrend(window=0.3, method="lowess")
psd = s2.spectral(method="mtm", nw=2, noise="robust_ar1", fmax=1.0)
psd.plot(xaxis="period")
```

> **Note:** This package also bundles the full Acycle desktop GUI tools. Use
> `acycle-imageprocessor`, `acycle-plot`, etc. from the command line.

---

## Installation

```bash
pip install acycle
```

Requirements: Python >= 3.8, numpy, scipy, pandas, matplotlib, astropy,
PySide6 (for GUI tools).

---

## Table of Contents

1. [Core Concepts](#core-concepts)
2. [Quick Start Examples](#quick-start-examples)
3. [The Series Object](#the-series-object)
4. [Data I/O](#data-io)
5. [Preprocessing](#preprocessing)
6. [Spectral Analysis](#spectral-analysis)
7. [Wavelet Analysis](#wavelet-analysis)
8. [Filtering](#filtering)
9. [Age Modeling & Tuning](#age-modeling--tuning)
10. [Cyclostratigraphy (COCO / eCOCO)](#cyclostratigraphy-coco--ecoco)
11. [Sedimentation Noise (DYNOT / rho1)](#sedimentation-noise-dynot--rho1)
12. [Astronomical Calculations](#astronomical-calculations)
13. [Image Processing & Digitizing](#image-processing--digitizing)
14. [Plotting](#plotting)
15. [CLI Tools (GUI Compatibility)](#cli-tools-gui-compatibility)
16. [All Result Objects](#all-result-objects)
17. [Built-in Example Datasets](#built-in-example-datasets)
18. [Full API Reference](#full-api-reference)

---

## Core Concepts

| Concept | Class/Function | Description |
|---------|---------------|-------------|
| Time series | `ac.Series` | Two-column (x, y) data with units and metadata |
| Multi-series | `ac.MultiSeries` | Collection of Series with shared x-axis |
| Image | `ac.Image` | Image for digitization and analysis |
| Power spectrum | `ac.PSD` | Result of `spectral()` — frequency, power, noise |
| Wavelet | `ac.WaveletResult` | CWT power, period, COI, significance |
| Filtered signal | `ac.FilterResult` | Result of `filter()` — amplitude, phase |
| Age model | `ac.AgeModel` | Depth-age pairs, sedimentation rate |
| COCO result | `ac.CocoResult` | Correlation coefficient vs sedimentation rate |
| Sediment noise | `ac.SedNoiseResult` | DYNOT / rho1 noise quantification |

Every result object supports:

- `result.plot()` — generate a matplotlib figure
- `result.to_dataframe()` — export to pandas DataFrame
- `result.save("prefix")` — save to files
- `result.settings` — dict of parameters used

---

## Quick Start Examples

### Example 1: Load data, clean, interpolate, detrend, compute spectrum

```python
import acycle as ac
import matplotlib.pyplot as plt

# Load bundled example
s = ac.load_example("la2004_etp")

# Chain preprocessing
s2 = (s.clean(sort=True, dropna=True, duplicate="mean")
      .interpolate(step=2.0, method="linear")
      .detrend(window=0.3, method="lowess"))

# Compute power spectrum with red-noise background
psd = s2.spectral(method="mtm", nw=2, noise="robust_ar1", fmax=1.0)
psd.plot(xaxis="period")
plt.show()
```

**Expected output:** A figure with the power spectrum (black), median-smoothed
curve (red), and red-noise confidence levels (blue dashed). Peaks above the
99% confidence line are statistically significant.

### Example 2: Generate synthetic data and run wavelet analysis

```python
import acycle as ac
import numpy as np

# Create a signal with two periodic components
t = np.arange(0, 1000, 1.0)
y = (np.sin(2 * np.pi * t / 100)     # 100-kyr cycle
     + 0.5 * np.sin(2 * np.pi * t / 41)   # 41-kyr cycle
     + 0.2 * np.sin(2 * np.pi * t / 23)   # 23-kyr cycle
     + np.random.randn(len(t)) * 0.1)     # white noise

s = ac.Series(x=t, y=y, x_name="Time", x_unit="kyr", y_name="Signal")

# Continuous wavelet transform
wav = s.wavelet(mother="MORLET", period_min=4, period_max=256)
# wav is a WaveletResult with power, coi, significance attributes
print(f"Periods: {wav.period[:5]}...")
print(f"Power shape: {wav.power.shape}")
```

**Expected output:**
```
Periods: [4.  4.29 4.6  4.93 5.28]...
Power shape: (79, 1000)
```

### Example 3: COCO — cyclostratigraphic correlation coefficient

```python
import acycle as ac

# Load a depth-domain gamma ray log
s = ac.Series.from_file(
    "data/Examples/Example-WayaoCarnianGR0.txt",
    x_unit="m", y_name="GR"
)

# Clean and interpolate
s = s.clean(sort=True).interpolate(step=0.33)

# COCO analysis: test sedimentation rates 4.29 to 29.89 cm/kyr at 0.2 step
coco = s.coco(
    median_age=230,        # Ma
    sed_rate=(4.29, 29.89, 0.2),  # (min, max, step) cm/kyr
    n_sim=200,             # Monte Carlo simulations
)

best_sr = coco.sed_rate[np.argmax(coco.rho)]
print(f"Best sedimentation rate: {best_sr:.1f} cm/kyr")
print(f"Correlation: {coco.rho.max():.3f}, H0-SL: {coco.h0_sl.min():.4f}")
```

### Example 4: Bandpass filtering

```python
import acycle as ac
import numpy as np

s = ac.Series(x=np.linspace(0, 500, 1000),
              y=np.sin(2*np.pi*np.linspace(0,500,1000)/100),
              x_name="Depth", x_unit="m", y_name="Signal")

# Gaussian bandpass filter around 0.01 cycles/m
result = s.filter(kind="bandpass", method="gaussian", flow=0.005, fhigh=0.015)
filt = result.filtered  # a new Series

print(f"Filtered: {filt}")
print(f"Amplitude available: {result.amplitude is not None}")
```

### Example 5: Image digitization (no GUI)

```python
import acycle as ac
import numpy as np

# Create a simple test image (20x20 RGB)
img_data = np.random.randint(0, 255, (20, 20, 3), dtype=np.uint8)
img = ac.Image(data=img_data, name="Test")

# Convert to grayscale
gray = img.to_grayscale()
print(f"Grayscale shape: {gray.shape}")

# Extract intensity profile
profile = img.profile()
print(f"Profile length: {len(profile['intensity'])} pixels")
```

---

## The Series Object

### Creating a Series

```python
# From raw arrays
s = ac.Series(x=[1, 2, 3, 4, 5], y=[10, 20, 15, 25, 30],
              x_name="Depth", x_unit="m", y_name="GR", y_unit="API")

# From a file
s = ac.Series.from_file(
    "data.txt",
    columns=(0, 1),         # x, y column indices (0-based)
    delimiter=None,         # auto-detect: comma, tab, or whitespace
    x_unit="m", y_name="GR",
    sort=True, dropna=True, duplicate="mean",
)

# Load a built-in example
s = ac.load_example("la2004_etp")
s = ac.load_lr04(start=0, stop=5320)   # LR04 benthic stack
s = ac.load_cenogrid(variable="d18o")  # CENOGRID
```

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `s.x`, `s.y` | ndarray | Raw data arrays |
| `s.n` | int | Number of points |
| `s.dt` | float | Median sampling interval |
| `s.x_min`, `s.x_max` | float | X-axis extent |
| `s.x_name`, `s.y_name` | str | Axis labels |
| `s.x_unit`, `s.y_unit` | str | Unit labels |
| `s.history` | list[dict] | Chain of applied operations |
| `s.metadata` | dict | Arbitrary metadata |

### Chainable Methods

```python
s2 = s.clean(sort=True, dropna=True)          # → Series
s3 = s2.interpolate(step=0.33)                 # → Series
s4 = s3.detrend(window=0.3, method="lowess")   # → Series
s5 = s4.standardize()                          # → Series
s6 = s4.clip_by_threshold(0.5)                 # → Series
s7 = s4.moving_average(10)                     # → Series
psd = s3.spectral()                            # → PSD
wav = s3.wavelet()                             # → WaveletResult
filt = s3.filter(kind="bandpass", flow=0.01, fhigh=0.1)  # → FilterResult
coco = s3.coco(230, (4.29, 29.89, 0.2))       # → CocoResult
```

### Quick Plot

```python
s.plot(kind="line", color="steelblue", line_width=1.5,
       xlabel="Depth (m)", ylabel="Value", grid=True)
```

---

## Data I/O

| Function | Description | Returns |
|----------|-------------|---------|
| `ac.read_series(path, **kw)` | Read two-column data file | `Series` |
| `ac.write_series(series, path)` | Write to delimited file | `str` (path) |
| `ac.extract_columns(path, x_col, y_col)` | Extract columns from multi-column file | `Series` |
| `ac.new_folder(path)` | Create directory | `str` |
| `ac.new_text(path)` | Create empty text file | `str` |
| `ac.save_figure(fig, path)` | Save matplotlib figure | `str` |

```python
s = ac.read_series("data.csv", columns=(1, 3), delimiter=",",
                    x_unit="m", y_name="TOC", sort=True)
ac.write_series(s, "output.txt", sep="\t")
ac.save_figure(fig, "plot.pdf", dpi=300)
```

---

## Preprocessing

### `Series.detrend(window=0.35, method="lowess")`

Remove long-term trends. Supported methods: `"linear"`, `"polynomial"`,
`"lowess"`, `"loess"`, `"rlowess"`, `"rloess"`, `"moving_mean"`, `"savgol"`.

```python
# Remove 35% lowess trend
s_dt = s.detrend(window=0.35, method="lowess")
# Or specify window in axis units
s_dt = s.detrend(window=80, window_unit="axis", method="lowess")
# Get trend as well
s_dt, s_trend = s.detrend(window=0.3, return_trend=True)
```

### `Series.clip_by_threshold(threshold, side="above", mode="delete")`

```python
# Delete all values above 10
s2 = s.clip_by_threshold(10, side="above", mode="delete")
# Cap values above 10 to exactly 10
s2 = s.clip_by_threshold(10, side="above", mode="cap")
# Set values below -5 to zero
s2 = s.clip_by_threshold(-5, side="below", mode="zero")
```

### Other preprocessing methods

| Method | Description |
|--------|-------------|
| `s.interpolate(step, method)` | Uniform resampling |
| `s.interpolate_pro(step, method)` | Advanced interpolation with gap filling |
| `s.interpolate_to(reference)` | Resample onto another Series' x-grid |
| `s.select(start, stop)` | Extract a sub-range |
| `s.standardize()` | Z-score: `(y - mean) / std` |
| `s.log10(handle_nonpositive)` | Base-10 logarithm |
| `s.derivative(order)` | Numerical derivative |
| `s.remove_sections(sections)` | Delete sections `[(start, stop), ...]` |
| `s.add_gaps(gaps)` | Insert NaN gaps `[(position, duration), ...]` |
| `s.remove_peaks(ymin, ymax, mode)` | Cap or set-to-NaN peaks |
| `s.changepoint(method)` | Detect changepoints in y |
| `s.transform_xy(a, b, c, d)` | Affine: x_new=a*x+b, y_new=c*y+d |
| `s.find_extreme(kind)` | Find maximum or minimum value |
| `s.multiply(other)` | Multiply by another Series element-wise |
| `s.moving_average(n)` | Simple moving average |
| `s.gaussian_smooth(n, sigma)` | Gaussian kernel smooth |
| `s.moving_median(n)` | Running median filter |

Utility functions:

| Function | Description |
|----------|-------------|
| `ac.merge_series(list)` | Merge multiple series into DataFrame |
| `ac.pca(data, n_components)` | Principal component analysis |

---

## Spectral Analysis

### `Series.spectral(method="mtm", **kw)`

```python
psd = s.spectral(
    method="mtm",              # "mtm" | "lomb_scargle" | "periodogram"
    nw=2,                      # time-bandwidth product (MTM)
    noise="robust_ar1",        # "classic_ar1" | "robust_ar1" | "power_law" | None
    fmax=1.0,                  # max frequency; "nyquist" for Nyquist
    median_smooth=0.2,         # smoothing window fraction
    confidence=(0.90, 0.95, 0.99, 0.999),
)
# Plot with period axis (log scale)
psd.plot(xaxis="period")
# Export to DataFrame
df = psd.to_dataframe()
# Save to CSV
psd.save("spectrum_output")
# Access settings
print(psd.settings)
# Access noise model
print(f"Rho: {psd.rho}, Noise power: {psd.noise_power[:5]}")
```

### `Series.spectral_swa(**kw)`

Sliding-window spectral analysis with FDR confidence levels.

```python
psd_swa = s.spectral_swa(fmin=0, fmax=0.1)
```

### `Series.evolutionary_spectral(**kw)`

Sliding-window (evolutive) spectrogram.

```python
evo = s.evolutionary_spectral(
    method="fft",              # "fft" | "lah_fft" | "mtm" | "lomb_scargle"
    window=0.35,               # window fraction
    step=0.05,                 # sliding step
    nw=2,                      # MTM parameter
)
# evo.x, evo.frequency, evo.power are 2D arrays
```

### `Series.prewhiten(method="robust_ar1")`

```python
s_pw = s.prewhiten(method="robust_ar1")  # robust AR(1) removal
s_pw = s.prewhiten(method="classic_ar1") # classic AR(1)
s_pw = s.prewhiten(method="user", rho=0.5)  # user-specified rho
```

---

## Wavelet Analysis

### `Series.wavelet(**kw)`

Continuous wavelet transform (CWT) based on Torrence & Compo (1998).

```python
wav = s.wavelet(
    mother="MORLET",           # "MORLET" | "PAUL" | "DOG"
    period_min=2 * s.dt,       # default: 2 * sampling interval
    period_max=0.5 * s.n * s.dt,  # default: half record length
    dj=0.1,                    # scale resolution
    pad=True,                  # zero-pad to next power of 2
)

# Access results
print(wav.power.shape)         # (n_periods, n_time)
print(wav.period[:5])          # period axis
print(wav.coi)                 # cone of influence
print(wav.significance)        # significance vs chi2 test
```

### `Series.wavelet_coherence(other, **kw)`

```python
s2 = ac.Series(x=s.x, y=s.y + np.random.randn(len(s.y)) * 0.1)
coherence = s.wavelet_coherence(s2)
print(coherence["coherence"].shape)  # (n_periods, n_time)
```

---

## Filtering

### `Series.filter(**kw)`

```python
# Bandpass filter
result = s.filter(
    kind="bandpass",           # "bandpass" | "lowpass" | "highpass"
    method="gaussian",         # "gaussian" | "taner" | "butter" | "cheby1" | "ellip"
    flow=0.01, fhigh=0.05,    # frequency bounds
    remove_mean=True,
)

filt = result.filtered        # filtered Series
amp = result.amplitude        # amplitude envelope (taner/gaussian)
phase = result.phase          # instantaneous phase
freq_inst = result.instantaneous_frequency  # instantaneous frequency
```

### `Series.dynamic_filter(**kw)`

```python
result = s.dynamic_filter(
    window=0.35, fmin=0.005, fmax=0.05,
    lower_bound=[(0, 0.01), (500, 0.02)],  # evolving lower boundary
    upper_bound=[(0, 0.06), (500, 0.04)],  # evolving upper boundary
)
```

### `Series.amplitude_modulation(flow, fhigh, **kw)`

```python
am = s.amplitude_modulation(flow=0.008, fhigh=0.012)
envelope = am["envelope"]     # Hilbert envelope of filtered band
```

---

## Age Modeling & Tuning

### `Series.build_age_model(cycle_period, **kw)`

```python
age_model = s.build_age_model(405, anchor="max", start_age=0)
# age_model.depth, age_model.age, age_model.sed_rate
```

### `Series.tune(age_model, **kw)`

```python
s_tuned = s.tune(age_model, direction="depth_to_time")
# s_tuned is now in the time domain
```

### `ac.sedrate_to_age_model(depth, sedrate, **kw)`

```python
am = ac.sedrate_to_age_model(depth=d, sedrate=sr, sedrate_unit="cm/kyr")
```

### `ac.stratigraphic_correlation(ref, target, tie_points)`

```python
result = ac.stratigraphic_correlation(
    (x_ref, y_ref), (x_targ, y_targ),
    tie_points=[(0, 0), (50, 45), (100, 95)],
)
```

---

## Cyclostratigraphy (COCO / eCOCO)

### `Series.coco(median_age, sed_rate, **kw)`

Correlation COefficient analysis.

```python
coco = s.coco(
    median_age=230,            # Ma
    sed_rate=(4.29, 29.89, 0.2),  # cm/kyr
    n_sim=2000,                # Monte Carlo simulations (2000+ for publication)
    astronomical_periods=None, # auto: Berger(1989) periods
)

best_idx = np.argmax(coco.rho)
print(f"Best SR: {coco.sed_rate[best_idx]:.1f} cm/kyr, rho={coco.rho[best_idx]:.3f}")
```

### `Series.ecoco(median_age, sed_rate, **kw)`

Evolutionary COCO — sliding-window analysis.

```python
ecoco = s.ecoco(
    median_age=230,
    sed_rate=(4.29, 29.89, 0.2),
    window=0.35,               # sliding window fraction
    n_sim=500,
)
# ecoco.positions, ecoco.rho_2d, ecoco.h0_sl_2d
```

---

## Sedimentation Noise (DYNOT / rho1)

### `Series.dynot(**kw)`

```python
dynot = s.dynot(
    window_range=(300, 500),   # kyr
    step=5,                    # kyr
    n_sim=1000,                # bootstrap iterations
)
# dynot.age, dynot.median, dynot.quantiles
```

### `Series.rho1_noise(**kw)`

```python
rho1 = s.rho1_noise(mode="single", window=0.35)
print(f"rho1: {rho1.settings}")
```

---

## Astronomical Calculations

### `ac.insolation(start, stop, **kw)`

```python
ins = ac.insolation(0, 1000, step=1, day=80, latitude=65, solution="La2004")
```

### `ac.astronomical_solution(start, stop, **kw)`

```python
etp = ac.astronomical_solution(0, 1000, step=1, output="ETP", normalize=True)
ecc = ac.astronomical_solution(0, 1000, step=1, output="eccentricity")
```

### `ac.milankovitch_calculator(**kw)`

```python
result = ac.milankovitch_calculator(model="Waltham2015", age=100)
```

### `ac.signal_noise(**kw)`

```python
sine = ac.signal_noise(start=0, stop=1000, model="sine", period=100, amplitude=5, seed=42)
red = ac.signal_noise(start=0, stop=500, model="red_noise", mean=0, std=1, rho=0.7, seed=42)
white = ac.signal_noise(start=0, stop=200, model="white_noise", mean=0, std=1)
poly = ac.signal_noise(start=0, stop=10, step=0.1, model="polynomial", polynomial_coeffs=[1, 2, 3])
```

---

## Image Processing & Digitizing

### `ac.Image(data, path, **kw)`

```python
img = ac.Image(path="photo.jpg", name="MyPhoto")

# Display
img.show()

# Grayscale
gray = img.to_grayscale(output="gray.png")

# CIE Lab conversion
lab = img.to_lab()

# Intensity profile along a line
profile = img.profile(control_points=[(0, 10), (100, 50)])

# Digitize data points from an image
points = img.digitize(
    axis_points=[(10, 20), (200, 20), (10, 20), (10, 180)],
    axis_values=[(0, 100), (0, 50)],  # ((x1, x2), (y1, y2))
    color=(255, 0, 0), tolerance=50,
)
```

---

## Plotting

### Single series

```python
s.plot(kind="line", color="steelblue", grid=True, xlabel="Depth (m)")
```

### Multi-series stacked

```python
ac.plot_standardized([s1, s2, s3], method="zscore", offset=2)
ac.plot_offset([s1, s2], method="zscore", offset=2)
```

### Multi-panel

```python
ac.plot_multi([s1, s2], layout=(2, 1), shared_x=True, title="Subplots")
```

### Save figure

```python
ac.save_figure(fig, "output.pdf", dpi=300, format="pdf")
```

### Series helpers

```python
sr = s.sampling_rate(plot=True)        # resolution histogram
vd = s.value_distribution(bins=50)     # distribution + Q-Q plot
s.to_sound("data.wav", repeat=5, sample_rate=8192)  # sonification
```

---

## CLI Tools (GUI Compatibility)

These commands launch the original Acycle desktop GUI tools:

| Command | Tool |
|---------|------|
| `acycle-imageprocessor` | Image Processor — digitization and analysis |
| `acycle-plot` | PlotPro — interactive plotting |
| `acycle-interpolation [file]` | Interpolation Pro |
| `acycle-data-extractor [file]` | Data Extractor |
| `acycle-section-remover [file]` | Section Remover |
| `acycle-gap-adder [file]` | Gap Adder |
| `acycle-data-clipper [file]` | Data Clipper |
| `acycle-image-analyzer` | Image Analyzer |

Or from Python:

```python
ac.launch_imageprocessor(image="photo.jpg")
ac.launch_plotpro(files=["data.txt"])
ac.launch_interpolation(data_file="data.txt")
```

---

## All Result Objects

| Object | Key Attributes | Methods |
|--------|---------------|---------|
| `PSD` | `.frequency`, `.power`, `.period`, `.noise_power`, `.rho` | `.plot()`, `.to_dataframe()`, `.save()` |
| `EvolutiveSpectrum` | `.x`, `.frequency`, `.power` | `.to_dataframe()` |
| `WaveletResult` | `.x`, `.period`, `.power`, `.coi`, `.significance` | — |
| `FilterResult` | `.filtered`, `.amplitude`, `.phase`, `.instantaneous_frequency` | — |
| `AgeModel` | `.depth`, `.age`, `.sed_rate`, `.tie_points` | — |
| `CocoResult` | `.sed_rate`, `.rho`, `.p_value`, `.h0_sl` | — |
| `SedNoiseResult` | `.age`, `.median`, `.quantiles` | — |

---

## Built-in Example Datasets

```python
s = ac.load_example("la2004_etp")           # La2004 ETP solution
s = ac.load_example("petm_logfe")           # PETM log-Fe data
s = ac.load_example("wayao_gr")             # Carnian gamma ray log
s = ac.load_example("newark_depth_rank")    # Newark Basin depth ranks
s = ac.load_example("rednoise_0.7_2000")    # Synthetic red noise
s = ac.load_example("guandao_gr")           # Guandao Anisian GR
s = ac.load_example("launa_loa_co2")        # Mauna Loa CO2
s = ac.load_example("csa_extinction")       # Extinction event data
```

---

## Full API Reference

### Top-level imports

```python
from acycle import (
    Series, MultiSeries,                 # core data
    PSD, EvolutiveSpectrum,             # spectral results
    WaveletResult, FilterResult,         # wavelet & filter results
    AgeModel, CocoResult, SedNoiseResult,  # age & cyclostrat results
    Image,                                # image analysis
    # IO
    read_series, write_series, load_example, load_lr04, load_cenogrid,
    new_folder, new_text, save_figure, extract_columns,
    # Basic
    insolation, astronomical_solution, milankovitch_calculator, signal_noise,
    # Preprocessing
    detrend, interpolate_pro, clip_by_threshold, remove_sections,
    add_gaps, remove_peaks, changepoint, merge_series, pca,
    transform_xy, find_extreme, multiply_series,
    # Plotting
    plot_multi, plot_standardized, plot_offset,
    # CLI
    launch_imageprocessor, launch_plotpro, launch_interpolation,
    launch_data_extractor, launch_section_remover, launch_gap_adder,
    launch_data_clipper, launch_image_analyzer,
)
```

### Submodule imports

```python
from acycle.spectral import _mtm_spectrum, _lomb_scargle_spectrum
from acycle.wavelet import cwt, wavelet_coherence
from acycle.filter import apply_filter, dynamic_filter, amplitude_modulation
from acycle.age import build_age_model, tune, sedrate_to_age_model, stratigraphic_correlation
from acycle.astrochron import coco, ecoco, dynot, rho1_noise
from acycle.preprocess import detrend, clip_by_threshold, changepoint, pca
from acycle.plot_api import plot_multi, plot_standardized, save_figure
from acycle.image_api import Image
```

---

## Citation

Li, M., Hinnov, L., & Kump, L. (2019). Acycle: Time-series analysis software
for paleoclimate research and education. *Computers & Geosciences*, 127, 12-22.
https://doi.org/10.1016/j.cageo.2019.02.011

---

## Changelog

- **v0.5.1**: Bug fixes (lowess fallback, fmax string handling, wavelet chi2, spectral imports)
- **v0.5.0**: Complete programmatic API with 70+ functions (all AcyclePy_API.docx requirements)
- **v0.3.0—0.4.0**: CLI wrappers and basic package structure
- **v0.1.x**: Original PyPI package with GUI tools only (acycle-imageprocessor, acycle-plot, acycle-interpolation, etc.)
