AcyclePy: Advanced cyclostratigraphy and time series analysis toolkit for Python. Provides programmatic API (Series/detrend/spectral/wavelet/filtering/COCO) and full GUI tools for geological data analysis.
Project description
AcyclePy — Advanced Cyclostratigraphy and Time Series Analysis Toolkit
Version 0.5.1 | PyPI | Repository | MIT License
AcyclePy is the Python library companion to the Acycle desktop application (Li, Hinnov & Kump, 2019). 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.
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
pip install acycle
Requirements: Python >= 3.8, numpy, scipy, pandas, matplotlib, astropy, PySide6 (for GUI tools).
Table of Contents
- Core Concepts
- Quick Start Examples
- The Series Object
- Data I/O
- Preprocessing
- Spectral Analysis
- Wavelet Analysis
- Filtering
- Age Modeling & Tuning
- Cyclostratigraphy (COCO / eCOCO)
- Sedimentation Noise (DYNOT / rho1)
- Astronomical Calculations
- Image Processing & Digitizing
- Plotting
- CLI Tools (GUI Compatibility)
- All Result Objects
- Built-in Example Datasets
- 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 figureresult.to_dataframe()— export to pandas DataFrameresult.save("prefix")— save to filesresult.settings— dict of parameters used
Quick Start Examples
Example 1: Load data, clean, interpolate, detrend, compute spectrum
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
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
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
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)
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
# 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
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
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 |
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".
# 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")
# 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=ax+b, y_new=cy+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)
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.
psd_swa = s.spectral_swa(fmin=0, fmax=0.1)
Series.evolutionary_spectral(**kw)
Sliding-window (evolutive) spectrogram.
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")
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).
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)
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)
# 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)
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)
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)
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)
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)
am = ac.sedrate_to_age_model(depth=d, sedrate=sr, sedrate_unit="cm/kyr")
ac.stratigraphic_correlation(ref, target, tie_points)
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.
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.
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)
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)
rho1 = s.rho1_noise(mode="single", window=0.35)
print(f"rho1: {rho1.settings}")
Astronomical Calculations
ac.insolation(start, stop, **kw)
ins = ac.insolation(0, 1000, step=1, day=80, latitude=65, solution="La2004")
ac.astronomical_solution(start, stop, **kw)
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)
result = ac.milankovitch_calculator(model="Waltham2015", age=100)
ac.signal_noise(**kw)
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)
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
s.plot(kind="line", color="steelblue", grid=True, xlabel="Depth (m)")
Multi-series stacked
ac.plot_standardized([s1, s2, s3], method="zscore", offset=2)
ac.plot_offset([s1, s2], method="zscore", offset=2)
Multi-panel
ac.plot_multi([s1, s2], layout=(2, 1), shared_x=True, title="Subplots")
Save figure
ac.save_figure(fig, "output.pdf", dpi=300, format="pdf")
Series helpers
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:
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
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
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
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.)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file acycle-0.5.1-py3-none-any.whl.
File metadata
- Download URL: acycle-0.5.1-py3-none-any.whl
- Upload date:
- Size: 825.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a7edafced033220d9005084b7f0b7ee086362db81d8d2422ef2c7a9b949ddc0
|
|
| MD5 |
09d20107708490eb21eb3bdaec5be543
|
|
| BLAKE2b-256 |
f513e8202c5f3a43bcd3b5ab27c4d23415d7fd25c13f1166c9d5725507ea3b45
|