Skip to content
Merged
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ fibermorph --demo_real_curv --output_directory ~/fibermorph_demo_curv
fibermorph --demo_real_section --output_directory ~/fibermorph_demo_section
```

### Optional extras

Some features rely on optional dependencies. Install them only if needed:

```bash
pip install "fibermorph[raw]" # enable RAW image conversion via rawpy
pip install "fibermorph[viz]" # install matplotlib-based visualization helpers
```

Extras can be combined, e.g. `pip install "fibermorph[raw,viz]"`.

## Using fibermorph on your data

Once installed, use fibermorph on your own grayscale TIFFs:
Expand Down
11 changes: 1 addition & 10 deletions conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,14 @@ requirements:
- poetry-core <1.9
run:
- python >=3.9,<3.14
# Conditional numpy and pyarrow for Python 3.13 support
# For Python <3.13: numpy 1.x, pyarrow 15.x
# For Python >=3.13: numpy 2.x, pyarrow 17.x
- numpy >=1.26.4,<3.0
- numpy >=1.26.4
- joblib >=1.3.2,<2.0.0
- pandas >=2.2.0,<3.0.0
- pillow >=10.2.0,<11.0.0
- requests >=2.31.0,<3.0.0
- matplotlib-base >=3.8.2,<4.0.0
- rawpy >=0.19.0,<0.20.0
- scipy >=1.8,<2.0
- shapely >=2.0.2,<3.0.0
- tqdm >=4.66.1,<5.0.0
- scikit-image >=0.22.0,<0.23.0
- scikit-learn >=1.4.0,<2.0.0
- sympy >=1.12,<2.0
- pyarrow >=15.0.0

test:
imports:
Expand Down
43 changes: 43 additions & 0 deletions docs/dependency-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## Dependency Slimming Plan

We want to reduce install and deployment overhead (for both the CLI and the Streamlit GUI) by auditing fibermorph's dependencies. This document outlines the plan, rationale, and checklist so the work stays organized.

### Goals
- Identify runtime dependencies that are no longer required for core workflows.
- Move optional functionality (GUI, raw conversion, demo generators) behind extras.
- Keep the base install as lightweight as possible (numpy, scipy, scikit-image, pandas, joblib, tqdm).
- Record every change/rationale here for future reference.

### Candidate Dependencies

| Package | Current Usage | Proposed Action | Notes |
|---------------|---------------|-----------------|-------|
| `sympy` | Dummy data ellipse area | Replace with `math.pi * a * b`; remove dependency | Not used in runtime workflows |
| `matplotlib` | Historical plotting/demos | Confirm active usage; move to optional extra if only for visualization | GUI doesn't rely on it |
| `pyarrow` | Legacy | Verify actual usage; drop if unused | Listed in deps but not obviously referenced |
| `rawpy` | `--raw2gray` workflow | Move to optional `raw` extra; guard import | GUI users typically upload TIFFs |
| `scikit-learn`| Dummy data MinMaxScaler | Swap for numpy-based scaling; remove dependency | Not needed elsewhere |
| `shapely` | Dummy data ellipse properties | Replace with basic geometry math | Avoid heavy dep |
| `pytest` | Should be dev-only | Ensure not bundled into runtime distribution | Already in dev group but reconfirm |
| GUI extras | `streamlit`, `requests` | Already optional via `[gui]` extra | Keep optional |

### Audit Checklist
1. ✅ **Inventory imports** – `python tools/inventory_imports.py`
2. ✅ **Refactor replacements**:
- `demo/dummy_data.py` now uses pure numpy/math.
- `demo/demo.py` ellipse helpers rewritten without sympy.
- Removed unused `fibermorph/arc_sim.py`.
3. ✅ **Update `pyproject.toml`**:
- Core deps trimmed (removed matplotlib, rawpy, scikit-learn, shapely, sympy, pyarrow, argparse, pytest).
- Added optional extras `raw = ["rawpy"]`, `viz = ["matplotlib"]`.
4. ✅ **Guard optional imports** – `raw_to_gray` now raises a helpful message when `rawpy` is missing.
5. ✅ **Docs** – README updated with optional extras (`raw`, `viz`).
6. ☐ **Testing** – run pytest with minimal install; confirm optional extras.

### Next Steps
- Work on a dedicated branch (e.g., `feature/dependency-trim`) branched from `main`.
- Tackle the checklist, updating this document with decisions/results.
- Once complete, bump version and summarize changes.

### Tools
- `python tools/inventory_imports.py` – reports top-level imports across the `fibermorph` package.
118 changes: 0 additions & 118 deletions fibermorph/arc_sim.py

This file was deleted.

5 changes: 3 additions & 2 deletions fibermorph/core/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import numpy as np
import skimage
import skimage.filters
import skimage.io
import skimage.util
from matplotlib import pyplot as plt

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -52,8 +52,9 @@ def filter_curv(
output_path = make_subdirectory(output_path, append_name="filtered")
# inverting and saving the filtered image
img_inv = skimage.util.invert(filter_img)
img_uint8 = skimage.util.img_as_ubyte(np.clip(img_inv, 0, 1))
save_path = pathlib.Path(output_path) / f"{im_name}.tiff"
plt.imsave(save_path, img_inv, cmap="gray")
skimage.io.imsave(save_path, img_uint8)
logger.debug(f"Saved filtered image to {save_path}")

return filter_img, im_name
28 changes: 14 additions & 14 deletions fibermorph/demo/demo.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import sympy

import math
import os
import random
import pathlib
import shutil
import sys
from datetime import datetime

import fibermorph
import numpy as np
import pandas as pd
import requests
from PIL import Image
from joblib import Parallel, delayed
from skimage import draw
from sympy import geometry
from tqdm import tqdm

from . import dummy_data
import fibermorph
from joblib import Parallel, delayed


def create_results_cache(path):
Expand Down Expand Up @@ -230,10 +228,13 @@ def sim_ellipse(
)
img[rr, cc] = 0

p1 = geometry.Point((im_height_px / px_per_um) / 2, (im_width_px / px_per_um) / 2)
e1 = geometry.Ellipse(p1, hradius=max_rad_um, vradius=min_rad_um)
area = sympy.N(e1.area)
eccentricity = e1.eccentricity
a = max_rad_um
b = min_rad_um
area = math.pi * a * b
if a <= 0:
eccentricity = 0.0
else:
eccentricity = math.sqrt(max(0.0, 1.0 - (b * b) / (a * a)))

jetzt = datetime.now()
timestamp = jetzt.strftime("%b%d_%H%M_%S_%f")
Expand Down Expand Up @@ -278,11 +279,10 @@ def validation_section(output_location, repeats, jobs=2):
# create list of random variables from range
def gen_ellipse_data():
min_diam_um = random.uniform(30, 120)
ecc = random.uniform(0.0, 1.0)
# min_diam_um = random.uniform(30, max_diam_um)
max_diam_um = geometry.Ellipse(
geometry.Point(0, 0), vradius=min_diam_um, eccentricity=ecc
).hradius
ecc = random.uniform(0.0, 0.99)
if ecc >= 1.0:
ecc = 0.99
max_diam_um = min_diam_um / math.sqrt(1.0 - ecc**2)
angle_deg = random.randint(0, 360)
list = [max_diam_um, min_diam_um, angle_deg]
return list
Expand Down
52 changes: 25 additions & 27 deletions fibermorph/demo/dummy_data.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
"""
Script to generate dummy data for testing curvature
Script to generate dummy data for testing curvature.

Based on script to produce non-colliding rectangles adapted from:
https://stackoverflow.com/questions/4373741/how-can-i-randomly-place-several-non-colliding-rects
"""

import sympy
from __future__ import annotations

from PIL import Image, ImageDraw
import math
import os
import pathlib
import random
from datetime import datetime
from random import randint
import pathlib

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
from datetime import datetime
from sklearn import preprocessing
from sympy import geometry
from PIL import Image, ImageDraw

random.seed()

Expand Down Expand Up @@ -182,13 +181,20 @@ def center_func(coord_df):

return dat3

def center_python_func(coord_df):
scaler = preprocessing.MinMaxScaler(feature_range=(0, 200))

dat4 = coord_df
dat4["x"] = scaler.fit_transform(coord_df[["x"]])
dat4["y"] = scaler.fit_transform(coord_df[["y"]])

def minmax_scale(series: pd.Series, feature_range: tuple[float, float] = (0.0, 200.0)) -> pd.Series:
lower, upper = feature_range
min_val = series.min()
max_val = series.max()
if math.isclose(max_val, min_val):
midpoint = (lower + upper) / 2.0
return pd.Series(np.full(series.shape, midpoint), index=series.index, dtype=float)
scale = (upper - lower) / (max_val - min_val)
return lower + (series - min_val) * scale

def center_python_func(coord_df: pd.DataFrame) -> pd.DataFrame:
dat4 = coord_df.copy()
dat4["x"] = minmax_scale(coord_df["x"])
dat4["y"] = minmax_scale(coord_df["y"])
return dat4

# dats["c_coords"] = dats["coords"].apply(lambda row: center_func(row))
Expand All @@ -200,14 +206,8 @@ def center_python_func(coord_df):
coord_list = np.array(dats["c_coords"].iloc[0])
coord_tuple = tuple(map(tuple, coord_list))

x, y = zip(*coord_tuple)
plt.scatter(x, y)
plt.show()

draw.line(xy=coord_tuple, fill="black")

im.show()


def draw_line(draw, rect, width):
pad = 40
Expand Down Expand Up @@ -238,11 +238,9 @@ def draw_ellipse(draw, rect, width):
# values for min and max and area of ellipses to pass to dataframe
width_df = maxx - minx
height_df = maxy - miny
r1 = width_df / 2
r2 = height_df / 2
p1 = geometry.Point(0, 0)
e1 = geometry.Ellipse(p1, r1, r2)
area = sympy.N(e1.area)
r1 = width_df / 2.0
r2 = height_df / 2.0
area = math.pi * r1 * r2

return width_df, height_df, area

Expand Down
Loading
Loading