Skip to content

Commit ff5e7df

Browse files
Phil-Duacursoragent
andcommitted
Restart napari-curvealign: migrate to pycurvelets API
- Migrate roi_manager, new_curv, widget from curvealign_py to pycurvelets - Add local Boundary type for pycurvelets compatibility - Remove curvealign_py/ctfire_py dependencies (moved to api_curation_py) - Add test_roi_formats, test_summary_statistics for plugin - Add examples and simple_usage.py demonstrating pycurvelets - CTFIRE and POST_ANALYSIS modes disabled Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a2b7dc1 commit ff5e7df

File tree

9 files changed

+911
-360
lines changed

9 files changed

+911
-360
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -8,123 +8,36 @@ on:
88
branches: [main]
99

1010
jobs:
11-
test-basic:
11+
test:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
1515
python-version: ["3.11", "3.12", "3.13"]
1616
steps:
1717
- uses: actions/checkout@v4
1818

19-
- uses: actions/setup-python@v5
20-
with:
21-
python-version: ${{ matrix.python-version }}
22-
19+
# Install system dependencies (like in your Docker container)
2320
- name: Install system dependencies
24-
run: |
25-
sudo apt-get update
26-
sudo apt-get install -y build-essential gcc g++ make wget curl unzip
27-
28-
- name: Install dependencies
29-
run: |
30-
python -m pip install --upgrade pip
31-
python -m pip install --break-system-packages -e .
32-
python -m pip install --break-system-packages pytest pytest-cov ruff
33-
34-
- name: Run basic tests (placeholder mode)
35-
env:
36-
QT_QPA_PLATFORM: "offscreen"
37-
run: |
38-
pytest -q -vv tests/curvealign_py/ tests/ctfire_py/ tests/test_unified_api.py
39-
40-
# Secure build with CurveLab; skip on forked PRs (no secrets on forks).
41-
test-curvelab:
42-
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
43-
runs-on: ubuntu-latest
44-
strategy:
45-
matrix:
46-
python-version: ["3.10", "3.12"]
47-
steps:
48-
- uses: actions/checkout@v4
21+
run: sudo apt-get update && sudo apt-get install -y libgl1 libglib2.0-0 python3-venv
4922

23+
# Set up Python
5024
- uses: actions/setup-python@v5
5125
with:
5226
python-version: ${{ matrix.python-version }}
53-
- name: Install system dependencies
54-
run: |
55-
sudo apt-get update
56-
sudo apt-get install -y build-essential gcc g++ make wget curl unzip
57-
58-
- name: Fetch CurveLab archive
59-
shell: bash
60-
env:
61-
FETCH_FDCT: ${{ secrets.FETCH_FDCT }}
62-
run: |
63-
set -euo pipefail
64-
if [ -z "${FETCH_FDCT}" ]; then
65-
echo "::error title=Missing secret::FETCH_FDCT is empty. It must download utils/CurveLab-2.1.3.tar.gz."
66-
exit 1
67-
fi
68-
mkdir -p utils
69-
cd utils
70-
echo "-- Pre eval --"
71-
ls -la
72-
echo "Downloading CurveLab archive via FETCH_FDCT..."
73-
eval "${FETCH_FDCT}"
74-
echo "-- Post eval --"
75-
ls -la
76-
if [ ! -f "CurveLab-2.1.3.tar.gz" ]; then
77-
echo "::error title=CurveLab archive missing after FETCH_FDCT::Expected utils/CurveLab-2.1.3.tar.gz to exist after running FETCH_FDCT."
78-
echo "Files in utils/:"
79-
ls -la
80-
exit 1
81-
fi
82-
if command -v file >/dev/null 2>&1; then
83-
echo "Downloaded archive mime-type:"
84-
MIME="$(file -b --mime-type "CurveLab-2.1.3.tar.gz" || true)"
85-
echo "${MIME}"
86-
if [[ "${MIME}" == text/html* ]]; then
87-
echo "::error title=CurveLab download returned HTML::Likely a 403/redirect. Showing first 40 lines:"
88-
head -n 40 "CurveLab-2.1.3.tar.gz" || true
89-
echo "Fix: update FETCH_FDCT to use 'curl --fail-with-body -fL ... -o CurveLab-2.1.3.tar.gz <URL>' so it fails on 403 instead of saving HTML."
90-
exit 1
91-
fi
92-
fi
93-
94-
- name: Prepare FFTW + CurveLab toolchain
95-
env:
96-
FDCT: ""
97-
FFTW: ""
98-
CPPFLAGS: ""
99-
LDFLAGS: ""
100-
run: |
101-
unset FDCT FFTW CPPFLAGS LDFLAGS
102-
bash .github/ci_setup_curvelab.sh
103-
104-
- name: Test CurveLab compilation
105-
run: |
106-
echo "Testing CurveLab compilation..."
107-
if [ -z "${FDCT:-}" ]; then
108-
echo "❌ FDCT environment variable not set by ci_setup_curvelab.sh"
109-
exit 1
110-
fi
111-
echo "FDCT=${FDCT}"
112-
ls -la "${FDCT}" || true
113-
ls -la "${FDCT}/fdct_wrapping_cpp/src" || true
114-
ls -la "${FDCT}/fdct3d/src" || true
115-
test -f "${FDCT}/fdct_wrapping_cpp/src/libfdct_wrapping.a" && echo "✅ libfdct_wrapping.a built" || (echo "❌ libfdct_wrapping.a missing"; exit 1)
116-
test -f "${FDCT}/fdct3d/src/libfdct3d.a" && echo "✅ libfdct3d.a built" || (echo "❌ libfdct3d.a missing"; exit 1)
11727

118-
- name: Install Python dependencies
28+
# Create virtual environment
29+
- name: Create virtual environment
11930
run: |
120-
python -m pip install --upgrade pip
121-
python -m pip install --break-system-packages -e ".[curvelab]"
122-
python -m pip install --break-system-packages pytest pytest-cov ruff
31+
python -m venv venv
32+
source venv/bin/activate
33+
python -m pip install --upgrade pip setuptools wheel
34+
python -m pip install -e .
35+
python -m pip install pytest ruff
12336
124-
- name: Run tests with CurveLab
37+
# Run tests in the venv
38+
- name: Run tests
12539
env:
126-
TMEQ_RUN_CURVELETS: "1"
12740
QT_QPA_PLATFORM: "offscreen"
12841
run: |
129-
python tests/test_curvelops_basic.py
130-
pytest -q -vv tests/curvealign_py/ tests/ctfire_py/ tests/test_unified_api.py tests/test_curvelops_basic.py
42+
source venv/bin/activate
43+
pytest -rs

simple_usage.py

Lines changed: 75 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,86 @@
1+
#!/usr/bin/env python3
12
"""
2-
CurveAlign API Usage Examples
3-
Demonstrates basic and advanced usage of the modern CurveAlign Python API.
4-
"""
3+
Simple usage examples for pycurvelets (manually converted API from MATLAB CurveAlign).
54
5+
Requires: curvelops (for curvelet transform), pycurvelets (this package).
6+
Run from repo root: python simple_usage.py
7+
"""
68
import numpy as np
7-
import curvealign_py as curvealign
8-
import ctfire_py as ctfire
99

10-
def basic_analysis_example():
11-
"""Basic fiber analysis with CurveAlign API."""
12-
print("=== Basic CurveAlign Analysis ===")
13-
14-
# Create test image (replace with: io.imread('your_image.tif'))
15-
image = np.random.rand(256, 256)
16-
print(f"Image shape: {image.shape}")
17-
18-
# Run core analysis
19-
print("Running curvelet-based analysis...")
20-
result = curvealign.analyze_image(image)
21-
22-
# Display results
23-
print(f"\nResults:")
24-
print(f" Fiber segments detected: {len(result.curvelets)}")
25-
print(f" Mean angle: {result.stats['mean_angle']:.1f}°")
26-
print(f" Alignment score: {result.stats['alignment']:.3f}")
27-
print(f" Fiber density: {result.stats['density']:.6f}")
28-
29-
return result
10+
try:
11+
from pycurvelets.models import (
12+
CurveletControlParameters,
13+
FeatureControlParameters,
14+
ImageInputParameters,
15+
BoundaryParameters,
16+
FiberAnalysisParameters,
17+
OutputControlParameters,
18+
AdvancedAnalysisOptions,
19+
)
20+
from pycurvelets.get_ct import get_ct
21+
from pycurvelets.new_curv import new_curv
22+
from pycurvelets.process_image import process_image
23+
HAS_PYCURVELETS = True
24+
except ImportError as e:
25+
HAS_PYCURVELETS = False
26+
print(f"pycurvelets not available: {e}")
27+
3028

31-
def ctfire_analysis_example():
32-
"""Individual fiber extraction with CT-FIRE integration."""
33-
print("\n=== CT-FIRE Analysis ===")
34-
35-
image = np.random.rand(256, 256)
36-
37-
# Option 1: Use unified CurveAlign interface
38-
print("Using unified interface (CT-FIRE mode)...")
39-
result_unified = curvealign.analyze_image(image, mode="ctfire")
40-
print(f" Unified interface: {len(result_unified.curvelets)} features")
41-
42-
# Option 2: Use CT-FIRE directly
43-
print("Using CT-FIRE API directly...")
44-
result_ctfire = ctfire.analyze_image(image)
45-
print(f" Direct CT-FIRE: {len(result_ctfire.fibers)} fibers")
46-
print(f" Network analysis: {len(result_ctfire.network.intersections)} intersections")
47-
48-
return result_unified, result_ctfire
29+
def example_get_ct():
30+
"""Extract curvelets from an image using get_ct."""
31+
if not HAS_PYCURVELETS:
32+
return
33+
# Create a simple test image (e.g. 128x128)
34+
img = np.random.rand(128, 128).astype(np.float64) * 255
35+
curve_cp = CurveletControlParameters(keep=0.05, scale=1.0, radius=10.0)
36+
feature_cp = FeatureControlParameters(
37+
minimum_nearest_fibers=2,
38+
minimum_box_size=32,
39+
fiber_midpoint_estimate=1,
40+
)
41+
fiber_structure, density_df, alignment_df, _ = get_ct(img, curve_cp, feature_cp)
42+
print(f"get_ct: {len(fiber_structure)} curvelets extracted")
43+
if len(fiber_structure) > 0:
44+
print(f" angles: min={fiber_structure['angle'].min():.1f}, max={fiber_structure['angle'].max():.1f}")
4945

50-
def visualization_example():
51-
"""Create visualizations with different backends."""
52-
print("\n=== Visualization Examples ===")
53-
54-
image = np.random.rand(128, 128)
55-
result = curvealign.analyze_image(image)
56-
57-
# Create overlay visualization
58-
try:
59-
overlay = curvealign.overlay(image, result.curvelets)
60-
print(f"Overlay created: {overlay.shape}")
61-
62-
# Create angle map
63-
angle_map_raw, angle_map_processed = curvealign.angle_map(image, result.curvelets)
64-
print(f"Angle maps created: {angle_map_processed.shape}")
65-
66-
except ImportError as e:
67-
print(f"Visualization backend not available: {e}")
6846

69-
def batch_processing_example():
70-
"""Process multiple images efficiently."""
71-
print("\n=== Batch Processing ===")
72-
73-
# Create sample images
74-
images = [np.random.rand(128, 128) for _ in range(3)]
75-
print(f"Processing {len(images)} images...")
76-
77-
# Batch analysis with curvelet method
78-
results_curvelets = curvealign.batch_analyze(images, mode="curvelets")
79-
print(f"Curvelet analysis: {len(results_curvelets)} results")
80-
81-
# Batch analysis with CT-FIRE method
82-
results_ctfire = curvealign.batch_analyze(images, mode="ctfire")
83-
print(f"CT-FIRE analysis: {len(results_ctfire)} results")
84-
85-
# Summary statistics
86-
total_features = sum(len(r.curvelets) for r in results_curvelets)
87-
print(f"Total features detected: {total_features}")
47+
def example_new_curv():
48+
"""Extract curvelets using new_curv (lower-level)."""
49+
if not HAS_PYCURVELETS:
50+
return
51+
img = np.random.rand(64, 64).astype(np.float64) * 255
52+
curve_cp = CurveletControlParameters(keep=0.1, scale=1.0, radius=5.0)
53+
in_curves, coeffs, inc = new_curv(img, curve_cp)
54+
print(f"new_curv: {len(in_curves)} curvelets, inc={inc:.4f}")
8855

89-
def advanced_options_example():
90-
"""Demonstrate custom analysis parameters."""
91-
print("\n=== Advanced Configuration ===")
92-
93-
image = np.random.rand(256, 256)
94-
95-
# Configure custom analysis options
96-
options = curvealign.CurveAlignOptions(
97-
keep=0.002, # Stricter coefficient threshold
98-
dist_thresh=150.0, # Boundary analysis distance
99-
minimum_nearest_fibers=6, # Feature computation requirements
100-
minimum_box_size=24 # Local analysis window size
101-
)
102-
103-
result = curvealign.analyze_image(image, options=options)
104-
print(f"Custom analysis: {len(result.curvelets)} features with strict parameters")
105-
106-
# CT-FIRE with custom options
107-
ctfire_options = ctfire.CTFireOptions(
108-
run_mode="ctfire",
109-
thresh_flen=20.0, # Minimum fiber length
110-
sigma_im=1.5 # Image smoothing
111-
)
112-
113-
ctfire_result = ctfire.analyze_image(image, ctfire_options)
114-
print(f"Custom CT-FIRE: {len(ctfire_result.fibers)} fibers")
11556

116-
def main():
117-
"""Run all examples to demonstrate API capabilities."""
118-
print("CurveAlign Python API - Usage Examples")
119-
print("=" * 50)
120-
121-
# Run examples
122-
basic_result = basic_analysis_example()
123-
ctfire_results = ctfire_analysis_example()
124-
visualization_example()
125-
batch_processing_example()
126-
advanced_options_example()
127-
128-
print("\n" + "=" * 50)
129-
print("All examples completed successfully!")
130-
print("\nNext steps:")
131-
print("1. Load your own images with: from skimage import io; image = io.imread('path.tif')")
132-
print("2. Use visualization: matplotlib (default) or optional backends (napari, imagej)")
133-
print("3. Customize analysis with CurveAlignOptions and CTFireOptions")
134-
print("4. Process datasets with batch_analyze() for efficiency")
57+
def example_process_image():
58+
"""Run full process_image pipeline (requires curvelops)."""
59+
if not HAS_PYCURVELETS:
60+
return
61+
import tempfile
62+
import os
63+
img = np.random.rand(128, 128).astype(np.float64) * 255
64+
with tempfile.TemporaryDirectory() as tmp:
65+
image_params = ImageInputParameters(img=img, img_name="test")
66+
fiber_params = FiberAnalysisParameters(fiber_mode=0, keep=0.05)
67+
output_params = OutputControlParameters(
68+
output_directory=tmp,
69+
make_associations=False,
70+
make_map=False,
71+
make_overlay=False,
72+
make_feature_file=True,
73+
)
74+
result = process_image(image_params, fiber_params, output_params)
75+
if result and "fib_feat_df" in result:
76+
print(f"process_image: wrote features, {len(result['fib_feat_df'])} rows")
77+
else:
78+
print("process_image: no result (curvelops may be required)")
79+
13580

13681
if __name__ == "__main__":
137-
main()
82+
print("pycurvelets simple usage examples\n" + "=" * 40)
83+
example_get_ct()
84+
example_new_curv()
85+
example_process_image()
86+
print("\nDone.")

0 commit comments

Comments
 (0)