Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash scripts/ci_setup.sh"
}
]
}
]
}
}
15 changes: 15 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.git
.github
venv
.venv
__pycache__
*.pyc
.pytest_cache
.ruff_cache
htmlcov
.coverage
data/reference
data/samples
*.tiff
*.tif
docs
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,api]"

- name: Lint (ruff)
run: ruff check src/ tests/

- name: Run tests
run: pytest tests/ -q

docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t dendro:ci .
- name: Smoke-test the API container
run: |
docker run -d -p 8000:8000 --name dendro dendro:ci
for i in $(seq 1 30); do
if curl -fs http://localhost:8000/health >/dev/null; then ok=1; break; fi
sleep 1
done
curl -fs http://localhost:8000/health
test -n "$ok"
docker rm -f dendro
33 changes: 33 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Dendro dating API container.
#
# Builds an image that serves the cross-dating engine over HTTP. By default it
# ships the small synthetic reference fixtures so the API is functional out of
# the box for a demo; mount or copy a real ITRDB corpus and point
# DENDRO_REFERENCE_DIR at it for production use:
#
# docker run -p 8000:8000 \
# -e DENDRO_REFERENCE_DIR=/data/reference \
# -v /path/to/itrdb:/data/reference dendro
FROM python:3.11-slim AS base

ENV PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
DENDRO_REFERENCE_DIR=/app/data/reference

WORKDIR /app

# Install dependencies first for better layer caching.
COPY pyproject.toml setup.py README.md ./
COPY src ./src
RUN pip install --upgrade pip && pip install ".[api]"

# Provide the synthetic fixtures as a default (demo) reference set.
COPY tests/fixtures/reference/synth /app/data/reference/synth

EXPOSE 8000

# Simple container healthcheck against the API.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health').status==200 else 1)"

CMD ["dendro-api"]
55 changes: 51 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,59 @@
# Dendrochronology Dating Tool

A Python CLI tool for dating historic timber by cross-dating ring width measurements against ITRDB reference chronologies.
A Python tool and HTTP service for dating historic timber by cross-dating ring width measurements against ITRDB reference chronologies. Usable from the command line or as a cloud-deployable API.

## Overview

This tool helps determine when trees were felled by:
1. Extracting ring width measurements from scanned wood cross-sections
2. Cross-dating against publicly available ITRDB chronologies
3. Identifying the calendar year of the outermost ring (felling year if bark edge present)
3. Interpreting the calendar year of the outermost ring as a felling date (exact with bark edge; a range or *terminus post quem* otherwise)

Designed for dating historic New England timber (1600s-1800s), but works for any region with ITRDB coverage.

## Running as a cloud service

The cross-dating engine is exposed as a stateless FastAPI service (the engine is
used as a library; the API does not shell out to the CLI):

```bash
pip install -e ".[api]"
export DENDRO_REFERENCE_DIR=/path/to/itrdb # directory of .rwl/.crn files
dendro-api # serves on :8000
```

Endpoints: `GET /health`, `GET /references`, `POST /date` (JSON ring widths),
`POST /date/csv` (CSV upload), `POST /parse` (Tucson file upload). Example:

```bash
curl -X POST http://localhost:8000/date -H 'content-type: application/json' \
-d '{"widths": [1.2, 0.9, ...], "has_bark_edge": true, "era_start": 1700, "era_end": 1850}'
```

Or with Docker (ships the synthetic demo references; mount a real corpus for production):

```bash
docker build -t dendro . && docker run -p 8000:8000 dendro
```

### Felling-year interpretation

Dating establishes the year of the **last measured ring**. What that implies for
the felling date depends on the sample:

- **Bark/waney edge present** → exact felling year.
- **Sapwood present, no bark edge** → estimated felling-year *range* (species sapwood model).
- **No sapwood retained** → *terminus post quem* ("felled after").

Pass `--bark-edge/--no-bark-edge`, `--has-sapwood`, and `--sapwood-count` to the
`dendro date` command (or the corresponding API fields).

### Series orientation

All series are dated in canonical oldest→newest (pith→bark) order. If your
measurements run bark-inward, pass `--orientation bark_to_pith` and they are
reversed automatically.

## Installation

```bash
Expand Down Expand Up @@ -197,7 +240,8 @@ dendrochronology/
│ └── cli/main.py # CLI entry point
├── data/
│ └── reference/ # Downloaded ITRDB chronologies
└── tests/ # Test suite (43 tests)
│ └── api/app.py # FastAPI cloud service
└── tests/ # Test suite (83 tests + fixtures)
```

## Species Codes
Expand Down Expand Up @@ -268,7 +312,10 @@ source venv/bin/activate
pytest tests/ -v
```

47 tests total, including 4 real-data validation tests using ITRDB chronologies.
83 tests total. Most run offline against committed synthetic fixtures
(`tests/fixtures`), including an end-to-end dating test that recovers a known
felling year. Four additional validation tests use real ITRDB chronologies and
skip automatically unless that data has been downloaded.

## Algorithm Validation

Expand Down
Loading
Loading