Metadata-Version: 2.4
Name: fritts-dendro
Version: 0.2.0
Summary: Open-source desktop tool for tree-ring cross-dating, measurement, and master chronology building.
Author: Mark
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyQt6<7,>=6.6
Requires-Dist: pyqtgraph<0.14,>=0.13
Requires-Dist: pandas<3,>=2.1
Requires-Dist: numpy<2,>=1.26
Requires-Dist: scipy<2,>=1.12
Requires-Dist: lxml<6,>=5.0
Requires-Dist: tifffile>=2024.1
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-qt>=4.3; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Dynamic: license-file

# Fritts — Dendrochronology Analysis Platform

![Fritts Screenshot](assets/fritts.png)

An open-source desktop application for tree-ring cross-dating, measurement, and master chronology building. Built with Python, PyQt6, and PyQtGraph. Named in honor of **Harold C. Fritts** (1930–2024), author of *Tree Rings and Climate* and a pioneer of dendroclimatology.

**Repository**: [GitHub](https://github.com/mabo-du/fritts) · [GitLab](https://gitlab.com/mabodu/fritts)  
**Package**: [`fritts-dendro` on PyPI](https://pypi.org/project/fritts-dendro/)

---

## Table of Contents

1. [Why Fritts?](#why-fritts)
2. [Features](#features)
3. [Quick Start](#quick-start)
4. [Installation](#installation)
5. [Supported Formats](#supported-formats)
6. [Dependencies](#dependencies)
7. [Usage](#usage)
8. [Cross-Dating Methods](#cross-dating-methods)
9. [COFECHA-Style Quality Control](#cofecha-style-quality-control)
10. [Chronology Building](#chronology-building)
11. [Image Measurement](#image-measurement)
12. [Session Workspace](#session-workspace)
13. [ITRDB Integration](#itrdb-integration)
14. [Keyboard Shortcuts](#keyboard-shortcuts)
15. [Sample Data](#sample-data)
16. [Project Structure](#project-structure)
17. [Development](#development)
18. [User Guide](#user-guide)
19. [Target Users](#target-users)
20. [Tech Stack](#tech-stack)
21. [Known Issues](#known-issues)
22. [License](#license)

---

## Why Fritts?

Existing dendrochronology software is often dated, proprietary, Windows-only, or splits critical workflows across multiple applications. **Fritts** unifies format parsing, interactive visual plotting, statistical cross-dating, and chronology building into a single, modern, cross-platform interface.

Key differentiators:
- **All-in-one** — Import, visualize, cross-date, detrend, build chronologies, and export in a single application.
- **Modern GUI** — PyQtGraph-powered canvas with GPU-accelerated rendering, smooth zoom/pan, and multi-series overlay.
- **Standards-compliant** — Full Tucson decadal read/write, TRiDaS read/write, Heidelberg read.
- **COFECHA-compatible QC** — Sliding-window correlation with AR prewhitening and Spearman significance (matching `dplR` defaults).
- **ITRDB integration** — Search and download data directly from the NOAA International Tree-Ring Data Bank.
- **Cross-platform** — Linux, macOS, and Windows with optional standalone binaries.

## Features

- **Multi-format import/export** — Tucson (.rwl, .tuc, .crn), Heidelberg (.fh), TRiDaS (.xml).
- **Interactive plotting** — PyQtGraph canvas with smooth zoom, pan, multi-series overlay, and skeleton plot mode.
- **Statistical cross-dating** — Baillie-Pilcher t-value, Hollstein t-value, Gleichläufigkeit (GLK) with Buras-Wilmking 2015 correction, and sliding-window analysis.
- **COFECHA-style Quality Control** — Leave-one-out master chronology, Yule-Walker AR(p) prewhitening (AIC order selection), segment correlation with Spearman's rho and p-value thresholding. Adjustable alpha via the QC dialog.
- **Detrending** — Mean, negative exponential, Hugershoff, cubic spline, and Regional Curve Standardisation (RCS).
- **Chronology builder** — Interactive master chronology with real-time EPS and R-bar metrics. Biweight robust mean averaging.
- **Classical ring detection** — Projection-profile boundary detection on scanned wood-section images.
- **Geometric pith estimator** — Adjustable concentric-circle overlay for estimating missing distance to pith.
- **Session workspace** — Save and load complete project state to `.fritts` JSON files (all series, references, metadata).
- **ITRDB data search** — Browse and download from the NOAA International Tree-Ring Data Bank.
- **R/dplR export** — Generate companion `.R` scripts for advanced analysis in R's `dplR` package.
- **Preferences** — DPI and theme settings persisted via `QSettings`.
- **Full undo/redo** — Command-pattern architecture with 200-step undo history.
- **Sample data** — Two synthetic 100-year series in `sample_data/example.rwl` for evaluation.

## Quick Start

```bash
# Install from PyPI
pip install fritts-dendro

# Launch
fritts
```

Or from source:

```bash
git clone https://github.com/mabo-du/fritts.git
cd fritts
pip install -e ".[dev]"
fritts
```

### First-run workflow

1. **Import data**: `File > Import` (`Ctrl+Shift+I`) — select a `.rwl`, `.fh`, or `.xml` file.
2. **Explore**: Click series in the Series List to view statistics. Toggle visibility with checkboxes.
3. **Cross-date**: Right-click a floating series → "Cross-Date Series", or `Tools > Cross-Date` (`Ctrl+D`).
4. **Detrend**: `Tools > Detrend` — remove biological growth trends before chronology building.
5. **Build chronology**: `Tools > Build Chronology` (`Ctrl+B`) — create a master curve from reference series.
6. **QC**: `Tools > QC Report` — run COFECHA-style quality control with adjustable alpha threshold.
7. **Save**: `File > Save Workspace` (`Ctrl+S`) — save your session as a `.fritts` project file.
8. **Export**: `File > Export` (`Ctrl+E`) — export as Tucson `.rwl`, TRiDaS `.xml`, or R/dplR script.

## Installation

### From PyPI (recommended)

```bash
pip install fritts-dendro
fritts
```

### From source (development)

```bash
git clone https://github.com/mabo-du/fritts.git
cd fritts

# Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate  # Linux/macOS
# .venv\Scripts\activate   # Windows

# Install with dev dependencies
pip install -e ".[dev]"

# Launch
fritts
```

### Standalone binaries

Pre-built executables are available from the [GitHub Releases](https://github.com/mabo-du/fritts/releases) page:

| Platform | File | Notes |
|----------|------|-------|
| **Linux** | `fritts-linux-x64` + `Fritts-x86_64.AppImage` | AppImage is portable |
| **macOS** | `fritts-macos-arm64` | Apple Silicon (M1+) |
| **Windows** | `fritts-windows-x64.exe` | Standalone executable |

### System dependencies

**Ubuntu/Debian**:
```bash
sudo apt install libxcb-cursor0 libegl1 libgl1
```

**Fedora**:
```bash
sudo dnf install qt6-qtbase-gui
```

## Supported Formats

| Format | Extensions | Read | Write | Description |
|--------|-----------|------|-------|-------------|
| Tucson Decadal | `.rwl`, `.tuc`, `.crn` | ✅ | ✅ | Most common tree-ring format. 0.01mm or 0.001mm precision, auto-detected from stop code (999 or -9999). |
| Heidelberg | `.fh` | ✅ | — | German format with HEADER/DATA sections. Auto-detects single-column and decadal-block layouts. |
| TRiDaS XML | `.xml` | ✅ | ✅ | International standard (TRiDaS 1.2.2). Handles astronomical BCE convention. |
| Fritts Workspace | `.fritts` | ✅ | ✅ | JSON project file preserving all series, references, metadata, and format version. |

### Precision handling

- **Tucson**: Stop code `999` → 0.01 mm precision (divide by 100). Stop code `-9999` → 0.001 mm precision (divide by 1000). Mixed stop codes warn and use the dominant value as fallback.
- **TRiDaS**: Unit attribute determines precision (`1/100th millimetres` → /100, `micrometre` → /1000, `millimetre` → /1).
- **Heidelberg**: Values assumed in 1/100 mm (divide by 100).

## Dependencies

| Package | Minimum | Purpose |
|---------|---------|---------|
| PyQt6 | 6.6 | GUI framework |
| pyqtgraph | 0.13 | GPU-accelerated interactive plotting |
| pandas | 2.1 | Data manipulation and alignment |
| numpy | 1.26 | Numerical computation |
| scipy | 1.12 | Statistical algorithms (Pearson, Spearman, splines) |
| lxml | 5.0 | TRiDaS XML parsing |
| tifffile | 2024.1 | Memory-mapped TIFF loading for large images |

All dependencies have explicit upper bounds in `pyproject.toml` to prevent breaking on future major releases.

## Usage

### Launching

```bash
fritts
```

Or directly:

```bash
python -m dendro.main
```

### The Interface

The main window is divided into three panels:

| Panel | Location | Content |
|-------|----------|---------|
| **Series List** | Left | All loaded series with checkboxes, year ranges, visibility toggles. Right-click for context menu (Cross-Date, Set as Reference, Remove). |
| **Plot Area** | Centre | PyQtGraph canvas showing ring-width curves. Multi-series overlay with zoom/pan. |
| **Stats Panel** | Right | Selected series statistics: mean, standard deviation, min/max, year range, ring count. After cross-dating: t-values, GLK, overlap. |

### Series List operations

| Action | How |
|--------|-----|
| Select a series | Left-click → shows stats in right panel |
| Toggle visibility | Checkbox |
| Cross-date | Right-click → "Cross-Date Series" |
| Set as reference | Right-click → "Set as Reference" |
| Remove | Right-click → "Remove Series" (undoable) |
| Shift in time | Arrow keys (← →) with active selection. Clamped to ±10,000 years. |
| Snap to offset | Click a proposed start year in the cross-date results panel |

### Plot controls

| Control | Action |
|---------|--------|
| Left-click + drag | Pan |
| Scroll wheel | Zoom in/out |
| Ctrl + scroll | Zoom X-axis only |
| Shift + scroll | Zoom Y-axis only |
| Right-click | Context menu |
| F key | Zoom to fit |

## Cross-Dating Methods

Fritts provides three complementary statistics for cross-dating:

### Baillie-Pilcher t-value (`t_bp`)

A 5-year running mean is applied to both series before computing Pearson's r and converting to a t-statistic: `t = r · √((n−6)/(1−r²))`. This pre-smoothing reduces the influence of high-frequency noise and emphasises decadal-scale patterns.

- **Degrees of freedom**: n−6 (4 lost to the running mean, 2 to bivariate correlation).
- **Reference**: Baillie & Pilcher (1973), *Tree-Ring Bulletin* 33:7–14.
- **Typical significance**: t > 3.5 for 50-year overlap.

### Hollstein t-value (`t_ho`)

Each series is transformed to Wuchswerte (growth-change values) before correlation: `Wᵢ = 100 · ln(xᵢ / xᵢ₋₁)`. This year-over-year ratio transform emphasises short-term growth changes. The canonical formula matches `dendroNetwork::wuchswerte()` (Hollstein 1980, pp 14–15).

- **Degrees of freedom**: n−3 (1 lost to the lag, 2 to bivariate correlation).
- **Reference**: Hollstein (1980), *Mitteleuropäische Eichenchronologie*.

### Gleichläufigkeit (GLK)

GLK measures the percentage of years where both series show the same direction of change (up/up or down/down). Implemented with the **Buras-Wilmking (2015) correction**:

- Year-pairs where **both** series show zero change count as a synchronous match.
- Year-pairs where **only one** series shows zero change are excluded.
- Z-score and p-value are computed using the exact binomial test (n < 30) or normal approximation (n ≥ 30).
- **Reference**: Buras & Wilmking (2015), *Dendrochronologia* 33:42–48.

### Interpreting results

| Statistic | Threshold | Meaning |
|-----------|-----------|---------|
| t_bp / t_ho | > 3.5 | Strong match, series likely correctly dated |
| t_bp / t_ho | 2.5–3.5 | Possible match, investigate further |
| t_bp / t_ho | < 2.5 | Weak match |
| GLK | > 60% | Good trend agreement |
| GLK | > 70% | Very strong trend agreement |
| p-value | < 0.05 | Statistically significant |

### Sliding window cross-dating

`Tools > Cross-Date` (`Ctrl+D`) runs `crossdate_sliding()` which slides the sample across the reference at every position where the overlap exceeds the minimum threshold (default 30 years). At each position, all three statistics (t_bp, t_ho, GLK) are computed on the overlapping segment. Results are displayed as a table ranked by t_bp.

## COFECHA-Style Quality Control

`Tools > QC Report` runs a COFECHA-style analysis following `dplR::corr.series.seg()` conventions:

1. **Leave-one-out master**: For each target series, a master chronology is built from the mean (or biweight robust mean) of all other series.
2. **AR prewhitening**: Each full-length series is prewhitened via Yule-Walker AR(p) with AIC order selection (max order 5), removing autocorrelation that would inflate apparent significance. Matching dplR's `normalize.xdate()` behaviour.
3. **Sliding windows**: Fixed-length segments (default 50 years, 25-year overlap) are correlated against the master.
4. **Spearman's rho with p-value**: Segments are flagged when `p > α` (default α = 0.05). This adapts to varying segment lengths and df loss from prewhitening — unlike COFECHA's hardcoded r < 0.32 threshold which only applies to n=50.
5. **Adjustable alpha**: The QC dialog provides an alpha spinbox (0.001–1.0) with a "Run QC" button to re-run at different thresholds.

## Chronology Building

`Tools > Build Chronology` (`Ctrl+B`) creates a master chronology from selected reference series:

1. Select reference series.
2. Choose averaging method: **Mean** or **Biweight robust mean** (resistant to outliers, recommended).
3. The chronology is built with EPS and R-bar calculated in real time:
   - **R-bar**: Mean inter-series correlation across all pairwise overlaps (Pearson r).
   - **EPS** (Expressed Population Signal): `EPS = (n · r̄) / (1 + (n−1) · r̄)`. Threshold for a well-represented chronology: ≥ 0.85.
4. The chronology appears in the Series List as `CHRONOLOGY`.
5. Right-click → "Cross-Date Series" to date floating series against the master chronology.

## Image Measurement

`Tools > Image Measurement` opens the wood-section image viewer:

1. **Load an image** — JPEG, PNG, or TIFF. Files >500 MB use memory-mapped tifffile I/O for efficient loading.
2. **Set DPI** — Calibrate the image resolution for accurate millimeter measurements.
3. **Detect rings** — Click "Auto-Detect Rings" to run the classical projection-profile boundary detection algorithm.
4. **Manual markers** — Left-click to place/adjust ring boundary markers.
5. **Extract series** — Click "Extract Series" with ≥2 markers to export ring-width measurements. Series IDs are validated (≤8 alphanumeric characters).
6. **Pith estimator** — Toggle concentric-circle overlay to estimate missing distance to pith.

The channel order for loaded images is **RGB** (matching pyqtgraph's `makeARGB` expectations). Grayscale images are automatically broadcast to 3-channel.

## Session Workspace

`File > Save Workspace` (`Ctrl+S`) serialises the entire session (all series, detrended indices, reference flags, metadata) to a `.fritts` JSON file:

- NaN values → JSON `null` for standard compliance.
- `format_version` field for forward/backward compatibility.
- `File > Open Workspace` (`Ctrl+O`) to reload.
- `File > Save As` (`Ctrl+Shift+S`) for versioned saves.

## ITRDB Integration

`Tools > Search ITRDB` connects to the [NOAA International Tree-Ring Data Bank](https://www.ncei.noaa.gov/products/paleoclimatology/tree-ring) API:

- **Search**: Keyword search across all published ITRDB studies (default limit 30 results to avoid API errors).
- **Download**: Select a study and click "Download Selected" to fetch available `.rwl` series.
- **Security**: SSRF guard (host whitelisted to `ncei.noaa.gov`), SSL context, 100 MB Content-Length cap.
- **User-Agent**: `Fritts/{version}` for API identification.

API endpoint: `https://www.ncei.noaa.gov/access/paleo-search/study/search.json`

## Keyboard Shortcuts

| Shortcut | Action |
|----------|--------|
| `Ctrl+O` | Open Workspace |
| `Ctrl+S` | Save Workspace |
| `Ctrl+Shift+S` | Save Workspace As |
| `Ctrl+Shift+I` | Import data |
| `Ctrl+E` | Export data |
| `Ctrl+,` | Preferences |
| `Ctrl+Z` | Undo |
| `Ctrl+Shift+Z` / `Ctrl+Y` | Redo |
| `Ctrl+D` | Cross-Date |
| `Ctrl+B` | Build Chronology |
| `F` | Zoom to Fit |
| `←` / `→` | Shift active series ±1 year |
| `Ctrl+Q` | Quit |

## Sample Data

The repository includes `sample_data/example.rwl` — two synthetic 100-year series (SAMPLEA, SAMPLEB) generated from an age-related exponential trend with AR(1) noise. These are suitable for:

- Testing import/export workflows
- Evaluating cross-dating statistics
- Practicing chronology building
- Familiarisation with the interface

```bash
fritts
# File > Import → select sample_data/example.rwl
```

## Project Structure

```
fritts/
├── assets/                  # Static assets (screenshots, icons)
├── docs/
│   ├── USER_GUIDE.md        # Comprehensive user documentation
│   ├── scope.md             # Project scope and roadmap
│   └── research-papers/     # Reference papers
├── sample_data/             # Synthetic ring-width files
│   └── example.rwl          # 2 series, 100 years each
├── src/
│   └── dendro/
│       ├── io/              # Format parsers
│       │   ├── tucson.py    # Tucson decadal (.rwl) read/write
│       │   ├── heidelberg.py# Heidelberg (.fh) reader
│       │   ├── tridas.py    # TRiDaS XML read/write
│       │   ├── itrdb.py     # NOAA ITRDB API client
│       │   └── export_r.py  # dplR-compatible R script generator
│       ├── models/
│       │   ├── series.py    # RingWidthSeries (frozen dataclass)
│       │   ├── session.py   # SessionManager with serialization
│       │   └── commands.py  # Command pattern (undo/redo)
│       ├── stats/
│       │   ├── crossdate.py  # t_bp, t_ho, GLK, sliding cross-date
│       │   ├── chronology.py # Chronology builder, rbar, EPS
│       │   ├── detrend.py    # Mean, neg exp, spline, RCS
│       │   ├── quality_control.py  # COFECHA-style QC
│       │   └── ai_segmentation.py  # Classical ring detection
│       ├── ui/
│       │   ├── main_window.py      # Application window
│       │   ├── series_list.py      # Series list panel
│       │   ├── series_view.py      # Plot panel
│       │   ├── image_view.py       # Image measurement
│       │   ├── chronology_builder.py # Chronology dialog
│       │   ├── preferences_dialog.py # Settings dialog
│       │   └── qc_dialog.py        # QC report dialog
│       └── main.py           # Application entry point
├── tests/                    # Test suite (pytest)
│   ├── test_regression.py    # 35+ regression tests
│   ├── test_crossdate.py     # Cross-dating edge cases
│   ├── test_session_serialization.py  # Save/load round-trip
│   ├── test_io_formats.py    # Heidelberg, TRiDaS parsers
│   ├── test_ui_smoke.py      # UI smoke tests (pytest-qt)
│   └── ...
├── pyproject.toml            # Build configuration
└── README.md
```

## Development

```bash
# Install with dev dependencies
pip install -e ".[dev]"

# Run tests (95+ tests)
pytest

# Run linting
ruff check src/ tests/

# Format code
ruff format src/ tests/
```

### Test organization

| File | Coverage |
|------|----------|
| `test_regression.py` | FR-specific regression tests (statistics, models, signals) |
| `test_session_serialization.py` | .fritts save/load round-trip, version validation |
| `test_commands.py` | Command pattern (DetrendCommand, undo/redo) |
| `test_crossdate.py` | Cross-dating edge cases (no overlap, min_overlap, identical series) |
| `test_tucson_parser.py` | Tucson read/write round-trip (BCE, non-decade starts) |
| `test_io_formats.py` | Heidelberg and TRiDaS parser tests |
| `test_detrend.py` | Detrending methods (mean, neg exp, spline) |
| `test_itrdb.py` | ITRDB API client (mocked HTTP) |
| `test_ui_smoke.py` | UI instantiation smoke tests (pytest-qt) |

### Contributing

1. Fork the repository on [GitHub](https://github.com/mabo-du/fritts) or [GitLab](https://gitlab.com/mabodu/fritts).
2. Create a feature branch from `master`.
3. Make your changes with tests.
4. Run the full test suite: `pytest`.
5. Submit a merge request / pull request.

## User Guide

See [docs/USER_GUIDE.md](docs/USER_GUIDE.md) for comprehensive documentation covering:
- Installation troubleshooting
- Importing and exporting data
- Cross-dating workflows
- Detrending methods
- Chronology building with EPS/R-bar interpretation
- COFECHA-style Quality Control (prewhitening, alpha threshold)
- ITRDB data search
- Image measurement and ring detection
- Keyboard shortcuts reference
- Troubleshooting common issues

## Target Users

- **Dendrochronologists** dating archaeological timbers and historical structures.
- **Climate researchers** building proxy records from tree rings.
- **Wood specialists** in archaeology, heritage, and conservation.
- **Students** learning tree-ring analysis methods.

## Tech Stack

| Component | Technology |
|-----------|------------|
| GUI | PyQt6 (Qt 6.6+) |
| Visualization | PyQtGraph (0.13.x, GPU-accelerated) |
| Data | Pandas, NumPy |
| Statistics | SciPy (Pearson r, Spearman rho, splines) |
| XML | lxml (TRiDaS parsing) |
| Image I/O | tifffile (large TIFF memory-mapped loading) |
| Testing | pytest, pytest-qt, pytest-mock |
| Linting | ruff |
| Build | setuptools, PyInstaller, AppImage |

## Known Issues

### NOAA ITRDB API limit

The ITRDB search API returns HTTP 500 errors when the `limit` parameter is 46 or higher. Fritts defaults to `limit=30` to avoid this. If you need more results, run multiple searches with different keywords.

### pyqtgraph version

Fritts requires `pyqtgraph>=0.13,<0.14`. Version 0.14 removed a method that pyqtgraph's own internal plotting code still calls, causing `AttributeError` spam on plot interactions. This is pinned in `pyproject.toml`. A warning is shown at startup if >=0.14 is detected.

### TRiDaS NaN handling

The TRiDaS writer converts `NaN` ring-width values to `0`. This is a known limitation of the current export implementation.

### Large image loading

For TIFF files >500 MB, the QImage path has a 2× memory overhead (RGBA decode + numpy copy). Fritts automatically switches to tifffile memory-mapped I/O for files above this threshold, avoiding the double allocation. For extremely large files (>2 GB), ensure sufficient system memory for pyqtgraph's texture upload.

### Undo stack

The undo stack is limited to 200 commands. When exceeded, the oldest commands are discarded. This prevents unbounded memory growth during long sessions.

## License

MIT

---

*Fritts — Named in honor of Harold C. Fritts (1930–2024), pioneer of dendroclimatology.*
