Repository navigation
Conversation
Attempting to fix the zip file upload errors.
- README: remove HuggingFace Spaces YAML frontmatter (was rendering as raw text on GitHub) - packages.txt: replace libgl1-mesa-glx (removed in Debian bookworm/trixie) with libgl1 - requirements.txt: use -e ".[gui]" to include matplotlib and seaborn for the Streamlit Cloud visualizations tab - streamlit_app.py: replace main() call (removed in new app.py) with exec() pattern so Streamlit's re-run model works correctly - test.yaml: drop poetry in favour of plain pip (avoids poetry.lock sync issues after adding opencv-python-headless); add libgl1/libglib2.0-0 apt step; run tests on feature/** branches too - conventional-prs.yaml: mark title check continue-on-error in fork (advisory only; lasisilab enforces it on the upstream PR) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…flict libglib2.0-0 from Bullseye repos requires libffi7 which is not available on the Trixie-based Streamlit Cloud image. opencv-python-headless needs no system GL libraries, so packages.txt can be empty. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…as package names apt-get was trying to install each word of the comment as a package.
Streamlit Cloud now runs Python 3.14.4. The previous caret constraints (^1.26.4 for numpy, ^0.22 for scikit-image, ^10.2 for pillow) forced pip to use source distributions that fail to compile on Python 3.14. Widening to >=X allows pip to resolve pre-built wheels for newer minor versions. Also remove quotes around .[gui] in requirements.txt for uv compatibility.
app.py was calling segment_section() with sam2_checkpoint=/sam2_cfg= (hair_analysis names) but section_sam2.py defines checkpoint=/model_cfg=.
- TestComputeEFD: access efd["efd_coeffs"] instead of treating return value as ndarray directly - TestComputeRadialProfile: update key names to radial_* prefix, replace missing radius_range_mu with computed radial_max - radial_min, add n_radial_peaks to expected key set - TestExtractFeaturesFromArray: fix call signature (resolution_mu=, source_name=), adapt assertions for dict return type, fix efd key count (51 not 40), fix hu prefix (no underscore) - TestClassifyShape: add n_radial_peaks and asymmetry_index to all feature dicts (now required by classify_shape) - test_section_pipeline: fix hu column prefix from hu_ to hu - TestCurlIndex: make _curved_skel dense enough to avoid disconnected segments causing nan; fix assertion direction (chord/arc metric gives lower values for curved lines, not higher) Agent-Logs-Url: https://github.com/ezhil384/fibermorph/sessions/86945c40-fef0-4344-97fa-c44ac3c5e80d Co-authored-by: ezhil384 <80913303+ezhil384@users.noreply.github.com>
…ctored-version fix(tests): align test suite with refactored v2 API
Feature/v2 enhanced analysis
Merges Abhiraj Ezhil's v2: SAM2 section segmentation (optional), extended shape/curvature metrics, batch pipeline, rewritten Streamlit GUI with SLURM job builder, Docker, CI rework. PR: lasisilab#16 Conflict resolutions preserve the fibermorph-dev improvements: - workflows.py / curvature_pipeline.py: student's v2 structure with dev's image-validation guards and error handling re-applied. - io/readers.py and utils/filesystem.py keep the dev versions (student's changes there were line-ending churn only). - gui/app.py takes the student's rewrite wholesale; dev's zip/URL validation targets functions that no longer exist in the new GUI (noted as a feature regression to revisit). - Normalized all CRLF line endings introduced by the fork back to LF and added .gitattributes to prevent recurrence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
list_images on the dev branch verifies images with PIL, so zero-byte touch() fixtures are correctly rejected. Tests now write real minimal TIFFs and additionally assert that corrupt/empty/AppleDouble .tif files are excluded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- diameter_mean_mu unit conversion: multiply by µm/px instead of divide (was off by (1000/resolution)^2, ~57x at the 132 px/mm default); add a two-resolution regression test that locks the conversion direction and fails against the inverted form. - streamlit_app.py: exec app.py with __file__/__name__ pinned to the app's real path so its __file__-relative lookups (SAM2 checkpoint resolver) resolve inside the package instead of the repo root. - gui/__init__.py: main() called app.main(), deleted by the v2 refactor (the app is now a top-level Streamlit script); delegate to launcher, which runs it via streamlit run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Restore the deliberate Python <3.13 upper bound (lab decision in 1.0.1; the fork widened to >=3.10 while its own CI matrix, README, and .python-version still say 3.10-3.12, and 3.13/3.14 are untested). - Regenerate poetry.lock: the fork edited pyproject (opencv-python-headless, seaborn, requests, scipy, widened numpy/pillow) without relocking, so poetry install failed on content-hash mismatch. - Drop continue-on-error from conventional-prs.yaml (would have made PR title enforcement advisory upstream). - Strip the UTF-8 BOM the fork introduced in README.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The section resolution parameter is pixels-per-µm (area_px/resolution_mu², min_diam*resolution_mu), and section_pipeline.py + the CLI already say so. But the GUI fields and two docstrings said 'µm/px' — the inverse. Following that label and entering the physical µm/pixel value made the size filter reject the real cross-sections and return empty masks. - Relabel the two GUI inputs and the shape_analysis/batch docstrings to 'px/µm', and add a help tooltip: enter the reciprocal of a µm/pixel scale (e.g. 0.18 µm/pixel -> 5.556). - section_sam2._watershed_segment now logs a specific warning when the size filter rejects every candidate, naming the px window, the resolution_mu, and the likely calibration cause. Algorithm/output unchanged. - Quick Test surfaces a visible st.warning listing any images where no cross-section was detected, pointing at the resolution setting — so the failure is no longer silent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per the lab's decision, the app no longer guesses within- vs between-individual grouping from filenames (removed parse_metadata from the Quick Test flow). Each result row is now just the per-image measurements plus the source filename; group downstream from the CSV. Distribution figures summarise across images, so they now render only when 2+ images of a type are uploaded (a single image previously produced degenerate boxplots/heatmaps and empty grouping figures). With one image the app shows the per-image table + CSV and a short note. The grouping-dependent figures (Region Comparison, Within-Individual Variability) are skipped in Quick Test since the app no longer parses groups. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tool now does image analysis (segment -> measure) and leaves grouped statistics (within/between individual) to the user downstream. - Replace the fragile regex parse_metadata with parse_canonical_name: a lenient split of the enforced convention Individual_Sample_Side (e.g. Y_5_B.tif). Missing trailing fields come back blank; extra tokens are ignored; it never raises and never groups — the labels are attached as columns only. - Batch pipeline: drop _aggregate_per_sample and hair_analysis_per_sample.csv. run_batch now returns a single per-image DataFrame with the source filename and individual/sample/side label columns. Docstrings/return types updated. - Update tests to the new behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ownsampling - Quick Test documents the Individual_Sample_Side naming convention and attaches the parsed labels as columns (no grouping in the app). - Replace the grouped figure catalogue (in Quick Test and the Results tab) with a single simple ungrouped histogram of area + eccentricity (sections) / mean curvature + curl index (curvature), shown only with 2+ images. Results tab now shows per-image tables + that histogram; per-sample loading/figures removed. - Add utils/imaging.downsample_to_resolution: given the px/µm resolution, large scans are downsampled to a ~2 px/µm working resolution with resolution scaled to match, so µm measurements are preserved (<0.5%) while a 20MP scan drops to ~2.6MP and processing is several times faster. Wired into the section GUI path (never upsamples; never shrinks below 400px longest side). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Since the tool is per-image and already records the source filename, parsing it into individual/sample/side columns added no information and presumed a naming scheme. Removed parse_canonical_name and the label columns everywhere (batch, GUI, Results tab). The filename is preserved on every row; group downstream however the study needs. The GUI still notes that filenames are recorded so users can name for their own downstream grouping — but the app parses nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing both image types' uploads and settings at once was confusing and let users mix types by accident. Each type now has its own tab with only its own uploader, settings, run button, results table, ungrouped histogram, and CSV download. Section results keep independent session state from curvature. The standalone Segmentation preview tab is folded into the Cross-Section tab (its masks appear under an expander after the results), removing the cross-tab 'run Quick Test first' dependency. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From a multi-agent review of the tab-split GUI: - Curvature table/histogram referenced columns the pipeline never produces (curv_mean/curv_median); the real names are curv_mean_mean/curv_median_mean, so the curvature results were silently blank. Fixed the column mapping and both histogram calls. - Resolution number_inputs had no lower bound; entering 0 raised a cryptic 'float division by zero' on every image. Added min_value guards. - A re-run that detected nothing left the previous run's table/CSV/masks on screen next to the error. Now clears section/curvature results on empty runs. - _metric_histograms crashed matplotlib on non-finite values; now drops inf/-inf alongside NaN. - Segmentation mask store was a filename-keyed dict, so duplicate upload names silently clobbered a mask; changed to a list. Added an upfront warning when uploaded filenames collide (ambiguous source_file). - Dropped model_cfg='' passed to segment_section (it defeated the SAM2 default config and silently forced watershed). - Updated the stale module docstring/intro/comments left by the tab split. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GUI and CLI now take a units selector alongside the resolution value and
convert to the pixels-per-unit the pipeline expects, so the µm/px-vs-px/µm
mix-up that produced empty masks can't recur.
- utils/units.py: resolution_to_px_per_unit helper (+ tests)
- cli.py: --resolution_mu_units {px_per_um,um_per_px},
--resolution_mm_units {px_per_mm,mm_per_px}; --resolution_mm now float
- gui/app.py: unit selectbox + live 'working resolution' caption on all four
resolution inputs (Cross-Section, Curvature, Batch section + curvature);
SBATCH emits :g-formatted resolutions so a fractional px/mm survives
- workflows.py: fix batch() docstring µm/pixel -> pixels/µm
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…info The app had five tabs; three of them (Batch, Submit & Monitor, Results) only functioned when Streamlit ran on a UMich SLURM login node and leaked personal identifiers. Reduced to three honest tabs: Cross-Section | Curvature | Run at scale - Removes the live sbatch/squeue submit+monitor and the separate Results tab (in-app runs already show results inline; the CLI writes its own CSVs). - 'Run at scale' is now documentation + a downloadable SBATCH scaffold: it states plainly it does not submit anything, shows the generated script inline with a download button, and explains where the CLI writes results. - Scrubs personal/UMich defaults: account 'tlasisi0' -> blank (YOUR_ACCOUNT placeholder), partitions spgpu/standard -> user-entered (YOUR_PARTITION), ~/fibermorph_output home path -> relative 'fibermorph_output', /nfs/turbo placeholders -> generic /path/to/... - Title no longer says 'Hair Analysis Pipeline' (terminology); output CSV filename left unchanged to avoid breaking existing result files. - Drops now-unused imports (re, subprocess, time) and session keys. Validated with streamlit AppTest: reciprocal units (0.18 µm/px -> 5.55556 px/µm; 0.01 mm/px -> 100 px/mm) flow correctly into the generated script, which contains only placeholders. 182 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add help tooltips to the Taubin window and CLAHE controls (Curvature tab and Remote settings) so users understand what they do. - Rename the cluster tab 'Run at scale' -> 'Run Remote' per the location-based framing (a 'Run Local' companion is a follow-up). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pipeline already measures each fibre fragment (length + curvature) and
writes them to analysis/ImageSum_*.csv, but the GUI discarded that (temp dir
deleted, only the aggregate row kept) and instead headlined Abhiraj's v2
metrics (curl index, wave count, diameter, total length).
Now the Curvature tab shows:
- Per-fragment table (primary): one row per fragment with length + mean/median
curvature, read back from the pipeline's ImageSum output.
- Per-image summary: mean/median curvature and mean/median/total length,
computed transparently from the fragments.
- Distribution histograms across fragments (length + curvature).
- Curl index / wave count / diameter moved behind an off-by-default
'extended (experimental)' toggle, labelled as v2/student additions.
_process_curvature_gui now returns {fragments, image_row}; extended_curvature
defaults off. Validated: a 3-fragment synthetic image yields 3 per-fragment
rows with physically correct curvature ordering. 182 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clarifies the local-vs-hosted model and fixes the 2 GB-image blocker: the hosted app can't see local files and caps uploads, but the same GUI runs on the user's machine with no such limit. - launcher.py: sets FIBERMORPH_LOCAL=1 and raises maxUploadSize to 5 GB when launched via fibermorph-gui. - app.py: new 'Run Local' tab explaining pip install + fibermorph-gui and that Streamlit runs fine locally; shows a live banner for whether you're local. - Cross-Section & Curvature tabs: in local mode, a 'Folder on disk' input reads images straight from a directory (no upload, any size). Hosted app stays upload-only. Shared input-picker/gather helpers; run loops iterate uniform (name, path) work lists. - Tab bar is now Cross-Section | Curvature | Run Local | Run Remote. Validated with streamlit AppTest: hosted mode hides the folder option (4 tabs, no exception); local mode reads a 3-fragment image from a folder and produces the per-fragment + summary tables. 182 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
diameter_mean_mu / diameter_cv came from the v2 student fork and are being pulled from fibermorph's outputs until the method is validated. - curvature_pipeline.py: drop the diameter computation and the medial-axis distance map that existed only to feed it (the medial-axis skeleton for the curl-index path is unchanged). Extended curvature now yields curl_index, curl_index_std, wave_count, wave_count_per_mm, length_total. - gui/app.py: remove diameter from the extended-metrics toggle, the pulled keys, and the summary labels; drop it from the batch-tab toggle label. - cli.py / workflows.py / batch.py: update --extended-curvature help/docstrings. - test_curvature_pipeline.py: remove the two diameter tests and their fixture. The section min/max diameter SIZE FILTER (--minsize/--maxsize) is unrelated and untouched. 180 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
.gitignore and .github/workflows/publish.yml were committed with CRLF line endings before .gitattributes (eol=lf) was added. Renormalize them so the eventual dev -> main merge shows no line-ending noise. No content changes (diff is empty with --ignore-cr-at-eol). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What changed
- fibermorph/gui/launcher.py: main() now parses its own --local flag
(argparse, documented by "fibermorph-gui --help"). Every other
command-line argument is passed through to "streamlit run", placed
after fibermorph's defaults so the user's value wins (Streamlit
uses the last value given for an option). Before this change extra
arguments were silently dropped.
- Hosted mode (no flag): FIBERMORPH_LOCAL is not set. The launcher
passes "--server.maxUploadSize 500", so the cap does not depend on
the directory the command is run from (a value in
.streamlit/config.toml is overridden). It passes no --server.address
and no --server.port, so Streamlit's own settings for those apply.
- Local mode ("--local", or FIBERMORPH_LOCAL=1 already set in the
environment): sets FIBERMORPH_LOCAL=1 and passes
"--server.maxUploadSize 5000 --server.address localhost".
- Environment variable STREAMLIT_SERVER_MAX_UPLOAD_SIZE: when it is set
(non-empty), the launcher does not pass --server.maxUploadSize in
either mode, so the variable's value is the cap. Streamlit ranks a
command-line option above a STREAMLIT_* variable, and above
config.toml, so passing the option would discard a host's setting.
New helpers: _option_value(), _upload_cap_mb(), _startup_notice().
- --server.address localhost is always passed in local mode, even when
STREAMLIT_SERVER_ADDRESS is set. This is deliberate: local mode reads
folders on this machine, so only an explicit "--server.address" on the
fibermorph-gui command line may open it to other machines. "--help"
and the module docstring state this precedence.
- The launcher prints one line to stderr naming the mode and the upload
cap in effect (including one set by the variable above or by a
command-line option). In local mode the line says "localhost only"
unless --server.address names something other than localhost,
127.0.0.1 or ::1; then it prints a WARNING that anyone who can reach
the machine can read its folders. The first-run Streamlit
email-prompt workaround is unchanged.
- fibermorph/gui/app.py: the Run Local view, the module docstring and
the comment above _LOCAL now say "fibermorph-gui --local" and explain
what plain "fibermorph-gui" does. How _LOCAL is read from
FIBERMORPH_LOCAL is unchanged.
- README.md, CONTRIBUTING.md and CHANGELOG.md updated to match.
Why
Before this change every "fibermorph-gui" launch ran
os.environ.setdefault("FIBERMORPH_LOCAL", "1") and raised the upload
cap to 5000 MB. Anyone who started the app with that command on a
server (the repository's Dockerfile does) silently gave every visitor
a "Folder on disk" input that lists and reads image folders on the
server, plus a 5 GB upload limit. Local mode is only safe on your own
machine, so it now has to be asked for. The launcher also dropped all
extra command-line arguments and forced the upload cap on the command
line, which made STREAMLIT_SERVER_MAX_UPLOAD_SIZE unusable.
Behavior change
- "fibermorph-gui" now starts hosted mode: uploads only, 500 MB cap,
no "Folder on disk" input, FIBERMORPH_LOCAL left unset. Servers and
containers that start the app with "fibermorph-gui" get hosted mode
without any configuration change.
- Hosted mode and the repository's Dockerfile: the Dockerfile sets
STREAMLIT_SERVER_PORT=7860 and STREAMLIT_SERVER_ADDRESS=0.0.0.0. The
launcher passes neither option in hosted mode, so the container keeps
listening on all interfaces on port 7860. The upload cap is now 500 MB
(it was 5000 MB); set STREAMLIT_SERVER_MAX_UPLOAD_SIZE to use another
value, or append "--server.maxUploadSize N" to the command.
- "fibermorph-gui --local" gives the previous local-mode features and
now also listens on localhost only. To reach it from another
machine, add "--server.address 0.0.0.0" to the command (only on a
network you trust: local mode lets visitors read folders on this
machine). The STREAMLIT_SERVER_ADDRESS variable does not do this.
- Setting FIBERMORPH_LOCAL=1 before running "fibermorph-gui" still
gives local mode, now with the localhost-only binding too. In a
container this means FIBERMORPH_LOCAL=1 stops the published port from
being reachable, even with STREAMLIT_SERVER_ADDRESS=0.0.0.0 set,
unless "--server.address 0.0.0.0" is added to the command.
- Extra Streamlit options (for example "--server.port 8600") are now
passed through to "streamlit run". Before, they were ignored.
- Anyone who relied on plain "fibermorph-gui" for local mode must now
add --local.
How to verify
python -m pytest -q fibermorph/test/test_gui_launcher.py \
fibermorph/test/test_gui_app.py
test_default_launch_is_hosted, test_local_flag_enables_local_mode,
test_preset_env_var_enables_local_mode,
test_extra_args_pass_through_after_defaults and
test_hosted_has_no_folder_on_disk_option cover the mode switch.
test_env_upload_cap_is_not_overridden,
test_env_address_does_not_widen_local_mode,
test_hosted_mode_leaves_address_and_port_to_streamlit and
test_local_notice_warns_when_address_is_widened cover the precedence
rules; the test_streamlit_* tests run the real Streamlit config
loader in a subprocess and check the cap and address it ends up with.
By hand: run "fibermorph-gui" and "fibermorph-gui --local" and check
that only the second shows "Input source: Folder on disk" and
listens on 127.0.0.1 only.
BREAKING-CHANGE: "fibermorph-gui" no longer enables local mode (folder
input, 5 GB upload cap). Use "fibermorph-gui --local" or set
FIBERMORPH_LOCAL=1. Servers and containers that start with
"fibermorph-gui" now get hosted mode. In local mode the app listens on
localhost only unless "--server.address" is given on the command line.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- fibermorph/gui/app.py, Cross-Section view > Settings: the "SAM2
checkpoint path" text box is now rendered only in local mode
(FIBERMORPH_LOCAL=1, i.e. "fibermorph-gui --local"). In hosted mode
the checkpoint passed to segment_section(..., checkpoint=...) is
always _DEFAULT_CHECKPOINT: the SAM2_CHECKPOINT environment variable
if set, otherwise the packaged default path.
- In hosted mode a caption replaces the box. It reads either "SAM2
checkpoint: configured on this server." or "SAM2 checkpoint: none
configured on this server, so SAM2 segmentation falls back to
watershed." (chosen with os.path.isfile). The caption does not
contain the path.
- fibermorph/gui/app.py, Run Remote view > Cross-section settings: the
"SAM2 checkpoint path" box was pre-filled with _DEFAULT_CHECKPOINT,
so every hosted visitor saw the server's path there. In hosted mode
it now starts empty, with the placeholder
"/path/to/sam2.1_hiera_tiny.pt" and a help text saying the path is for
the machine you will run on. In local mode it is still pre-filled
with the default. The value only ends up in the generated SBATCH
script text (the server never loads it). If SAM2 is enabled and the
box is blank, the script now contains
--sam2-checkpoint 'YOUR_SAM2_CHECKPOINT' for the user to fill in,
like the existing YOUR_ACCOUNT and YOUR_PARTITION placeholders.
- Tests in fibermorph/test/test_gui_app.py render all four views
(section, curvature, local, remote) in hosted mode and check that the
SAM2_CHECKPOINT directory name appears in no label, caption, help
text, placeholder, text box value or code block. README.md and
CHANGELOG.md describe the change.
Why
Whatever a visitor typed into the Cross-Section box was passed to
segment_section() and the server then tried to load that file as a SAM2
model. A remote user could therefore choose which file on the server
gets loaded, and section_sam2._get_sam2_generator keeps the first model
it loads for the life of the process, so one visitor's choice also
affected everyone after them. Separately, the server's own checkpoint
path was shown to visitors in the Run Remote box, where a server-local
path is also meaningless to someone building a script for a cluster.
Behavior change
- Hosted mode, Cross-Section view: the text box is gone; the app uses
SAM2_CHECKPOINT, or the default found by _resolve_checkpoint() in
app.py (first choice fibermorph/checkpoints/sam2.1_hiera_tiny.pt) if
the variable is not set. A host that wants SAM2 segmentation must set
SAM2_CHECKPOINT (or place the file at the default path) on the
server. With no checkpoint file, SAM2 falls back to watershed, as
before.
- SAM2_CHECKPOINT is read by the server process, and the first SAM2
model it loads is cached until the process exits. Restart the app
after changing SAM2_CHECKPOINT or replacing the checkpoint file.
- Hosted mode, Run Remote view: the checkpoint box is empty instead of
showing the server path. Users who want SAM2 in their script type the
path on their cluster, or leave it blank and edit
YOUR_SAM2_CHECKPOINT in the downloaded script.
- Local mode ("fibermorph-gui --local"): unchanged; both boxes are
shown, pre-filled with the same default, and their values are used.
- Cross-section results are unchanged for anyone who left the default
path in place.
How to verify
python -m pytest -q fibermorph/test/test_gui_app.py
test_hosted_views_never_show_the_server_checkpoint_path (four views,
checkpoint present and missing),
test_hosted_remote_checkpoint_box_starts_empty_with_a_placeholder,
test_hosted_script_uses_placeholder_for_blank_checkpoint,
test_hosted_has_no_sam2_checkpoint_text_input,
test_hosted_caption_says_when_no_checkpoint_is_configured,
test_hosted_ignores_a_client_supplied_checkpoint (hosted always passes
the server's checkpoint to segment_section even if a client sets the
widget's session key) and
test_local_uses_the_checkpoint_typed_in_the_box.
BREAKING-CHANGE: in hosted mode visitors can no longer enter a SAM2
checkpoint path in the Cross-Section view; the server's
SAM2_CHECKPOINT (or packaged default) is always used, and its path is
not shown in any view.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- New module fibermorph/gui/inputs.py (no Streamlit import, so it is
unit-testable). save_uploads(uploads, tmpdir) writes the i-th upload
(1-based, in upload order) to <tmpdir>/upload_NNNN<ext> and returns
[(display_name, path), ...]. <ext> is the original extension,
lower-cased, only if it is one of tif, tiff, png, jpg, jpeg;
otherwise there is no extension. The client's filename is not used
to build any path.
- display_name() reduces the client's name to its last component
(splitting on both "/" and "\"), drops control characters, and falls
back to the generated file name if nothing usable is left (empty
name, ".", ".."). restore_names() puts the display name back into
an error message that mentions the generated name or temp path; it
replaces whole names only, so "upload_0001" (saved without an
extension) is not rewritten inside "upload_00010.tif".
- fibermorph/gui/app.py: _gather_inputs() uses save_uploads() for
uploads; _UPLOAD_TYPES is now inputs.UPLOAD_TYPES (same list).
_process_section_gui() takes a new display_name argument and passes
it to section_props_extended(), so the "ID" and "mask_filename"
columns hold the uploaded name instead of the temp file's name. The
per-file error warnings in both views map generated names back to
the uploaded name (e.g. "Cannot identify image file 'my scan.tif'").
- Curvature needed no mapping beyond that: curvature_seq() names its
outputs after the temp file's stem (ImageSum_upload_0001_WindowSize-
50px.csv), but each image gets its own fresh output folder, the GUI
reads the one ImageSum_*.csv in it, and the tables use the display
name for source_file.
- The "Duplicate filenames" warning is kept: its text ("each file is
still measured separately") is now true.
- Tests: fibermorph/test/test_gui_inputs.py (absolute paths, "..",
Windows paths, empty names, duplicates, extensions) and AppTest
cases in fibermorph/test/test_gui_app.py. CHANGELOG.md updated.
Why
1. The app saved each upload with os.path.join(tmpdir, up.name) where
up.name is chosen by the client. os.path.join discards tmpdir when
the name is absolute, and ".." segments (or "\" separators on a
Windows server) leave it, so a crafted filename could create or
overwrite a file outside the temporary directory.
2. Two uploads with the same name were written to the same temp path,
the second overwriting the first before any analysis ran, so both
result rows measured the second file while the page said each file
was measured separately.
Behavior change
- Uploads with ordinary, distinct names: none. Results and displayed
names are the same as before.
- Uploads whose names repeat: each file is now measured on its own, so
the second row no longer duplicates the first. Rows are still
labeled with the shared name, and the duplicate warning is shown.
- Uploads whose names include directories (e.g. "a/b.tif" or
"C:\scans\b.tif"): shown as "b.tif"; nothing is written outside the
temporary directory.
- Folder mode (--local) is unchanged.
How to verify
python -m pytest -q fibermorph/test/test_gui_inputs.py \
fibermorph/test/test_gui_app.py
test_hostile_names_never_escape_tmpdir and
test_duplicate_names_become_distinct_files cover the helper, and
test_restore_names_only_replaces_whole_names covers the message
mapping. The two
*_duplicate_uploads_are_measured_separately_and_named_as_uploaded
tests in test_gui_app.py (cross-section and curvature) upload
same-named files of different size/shape through the running app and
check that the rows differ and carry the uploaded name.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- fibermorph/gui/launcher.py, _startup_notice(): the bind address is
now read with _option_value(...) and "localhost" is used only when
the option was not given at all (value is None). Before, an empty
value ("--server.address=" or "--server.address ''") was replaced by
"localhost" because an empty string is falsy in Python.
- An empty address is now reported like any other address that is not
localhost, 127.0.0.1 or ::1: the line reads "listening on all network
interfaces" and the WARNING that anyone who can reach the machine can
read its folders is printed.
- The launcher module docstring, README.md and CHANGELOG.md say that an
empty --server.address counts as widening the address.
- fibermorph/test/test_gui_launcher.py: "--server.address=" and
"--server.address ''" are added to
test_local_notice_warns_when_address_is_widened; new tests
test_local_notice_says_every_interface_for_an_empty_address and
test_streamlit_gets_an_empty_address_when_asked_for_one.
Why
Streamlit does not treat an empty server.address as unset: its server
uses "server.address or 0.0.0.0", so with an empty value it listens on
every network interface. The launcher still printed "localhost only"
for that case and did not print the WARNING, so the notice said the
opposite of what was happening. Local mode lets any visitor read folders
on the machine, so the notice has to be right. This only happens when
the person starting the app types the empty value; visitors cannot
cause it.
Behavior change
- "fibermorph-gui --local --server.address=" (or with a separate empty
argument) now prints "listening on all network interfaces" and the
WARNING line, instead of "localhost only".
- Nothing else changes: the arguments passed to "streamlit run", the
address Streamlit binds to, and the notices for localhost, 127.0.0.1,
::1 and non-empty wider addresses are the same as before.
How to verify
python -m pytest -q fibermorph/test/test_gui_launcher.py
By hand:
python -c "from fibermorph.gui import launcher as l; \
print(l._startup_notice(True, ['--server.address=']))"
prints a line that contains "all network interfaces" and "WARNING".
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed - Add fibermorph/core/frangi_v016.py with frangi_v016(), a copy of the 2-D Frangi vesselness filter exactly as scikit-image 0.16.2 computed it (Gaussian-smoothed Hessian with zero padding at the border, fixed gamma=15, eigenvalues ordered by absolute value). The file keeps the scikit-image BSD-3-Clause copyright and license text. The helpers skimage.util.img_as_float and invert are still taken from the installed scikit-image; for 8-bit and float64 input they return the same arrays in 0.16.2 and 0.26, and the module docstring says so. - fibermorph/core/filters.py: filter_curv() now calls frangi_v016(gray_img) instead of skimage.filters.frangi(gray_img). filter_curv_clahe() is unchanged in behavior and still uses the installed scikit-image; only its docstring now says it is not part of the v0.3.1 method. - Add fibermorph/test/test_frangi_v016.py and the reference data in fibermorph/test/test_data/frangi_v016/ (input image, outputs of the real scikit-image 0.16.2, the script that made them, and a README). TestInstalledHelpers pins the behavior of img_as_float and invert that frangi_v016 relies on. - CHANGELOG.md: entry under Unreleased / Fixed. Why The published fibermorph method (v0.3.1, Nov 2020) ran on scikit-image 0.16.2. Later scikit-image releases changed frangi(): gamma now defaults to a value computed from the image instead of 15, and the Hessian code changed. Every later step of the curvature pipeline (threshold, particle removal, skeletonize, prune, Taubin circle fits) produces identical pixels on both versions, so the filter alone made the same fibermorph code report different curvature depending on the installed scikit-image. For the straight-hair demo image (027_demo_nocurv, 50 px window, 132 px/mm) the mean curvature is 0.0526 per mm with scikit-image 0.16.2 and 0.173 per mm with 0.26 (3.3 times higher). For the curly demo image (004_demo_curv, same settings) mean curvature changes by 1.1%, median curvature by 3.4% and length by 2.9%; the hair count is the same. Behavior change With the default options (no CLAHE, no extended curvature), curvature and length values from curvature_seq(), the CLI `fibermorph --curvature` command and the GUI Curvature view now equal the original v0.3.1 values, whichever scikit-image is installed. Values produced earlier by 0.3.x/1.0.x releases or the unreleased 2.0.0 code (fibermorph-dev) on scikit-image newer than 0.16.2, will differ (the straight-hair example above goes from 0.173 to 0.0526 per mm), so re-run any analysis that must be comparable with the paper or with other runs. Two options are not the v0.3.1 method and do not reproduce it: - CLAHE (--use-clahe, curvature_seq(use_clahe=True), the CLAHE switch in the GUI): filter_curv_clahe() still uses the installed scikit-image frangi(). - Extended curvature (--extended-curvature, curvature_seq(extended_curvature=True)): the skeleton is made with medial_axis instead of the skeletonize step v0.3.1 used, so the base curvature and length columns also differ from v0.3.1 (synthetic test image, 8 runs: mean curvature -0.4% to +1.5%, mean length +1.2% to +1.5%). medial_axis breaks ties at random, so these runs also differ from each other. Extended curvature is the default of run_batch(), workflows.batch() and of the "Extended curvature metrics" switch in the GUI's SLURM script builder, so batch runs started with those defaults do not reproduce v0.3.1 values either. There is nothing to configure: no new dependency, flag or environment variable. In a quick check on a 1680 x 1936 image the frozen filter took about 1.0 s against 3.6 s for scikit-image 0.26. How to verify python -m pytest -q fibermorph/test/test_frangi_v016.py The tests compare frangi_v016() with arrays computed by scikit-image 0.16.2 (rtol 1e-9, atol scaled to the reference maximum) and check that filter_curv() returns the same array. They fail if skimage.filters.frangi is used instead (largest difference about 0.85 against a reference maximum of 1.9e-4). TestInstalledHelpers fails if a future scikit-image changes img_as_float or invert for 8-bit or float64 input. BREAKING CHANGE: curvature values change on scikit-image newer than 0.16.2; with the default options they now match the original v0.3.1 / published results instead of what 0.3.x/1.0.x gave on the installed scikit-image. CLAHE and extended curvature runs do not match v0.3.1. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- fibermorph/gui/app.py: new _upload_cap() reads Streamlit's own
"server.maxUploadSize" setting (st.get_option) and formats it for
the page; the module-level _UPLOAD_CAP holds the result. It replaces
the fixed "500 MB" / "5 GB" in four places:
* the sidebar status line ("Hosted · <cap> upload cap" or
"Local · <cap> cap · folder input"),
* the Run Local view's "Why" paragraph ("it caps uploads (<cap>
here)"),
* the hosted info box on the Run Local view ("upload-only, <cap>"),
* the local success box on the Run Local view ("uploads are capped
at <cap>"; it used to say "raised to 5 GB").
If the setting cannot be read as a number, the old defaults (500 MB
hosted, 5 GB local) are shown.
- The Run Local text no longer quotes a number for plain
"fibermorph-gui" on the reader's own machine ("uploads only, no
folder input"), and no longer uses "a 2 GB curvature image" as its
example of a file that will not upload (it now says "a
multi-gigabyte curvature image, say"), because either can be wrong
for the cap a host has set.
- fibermorph/gui/inputs.py: new format_upload_cap(megabytes): whole
thousands of MB are shown in GB (5000 -> "5 GB", 2000 -> "2 GB"),
anything else in MB (500 -> "500 MB", 2500 -> "2500 MB").
- The app.py module docstring, README.md and CHANGELOG.md say that the
500 MB and 5 GB figures are the launcher's defaults and that the
app shows whatever cap is in effect.
- Tests: format_upload_cap in fibermorph/test/test_gui_inputs.py;
test_hosted_text_follows_the_configured_upload_cap and
test_local_text_follows_the_configured_upload_cap in
fibermorph/test/test_gui_app.py (cap set to 500, 750, 2000, 300 and
5000 MB; the page must show that cap and none of the others).
Why
The launcher lets a host choose the cap: when the environment variable
STREAMLIT_SERVER_MAX_UPLOAD_SIZE is set it passes no
--server.maxUploadSize, and "--server.maxUploadSize N" on the
fibermorph-gui command line also wins. The app text ignored those and
always said 500 MB (hosted) or 5 GB (local), so a container started
with STREAMLIT_SERVER_MAX_UPLOAD_SIZE=2000 told visitors "500 MB"
right next to a file uploader that said 2 GB.
Behavior change
- With no cap configured, the page text is the same as before ("500 MB"
hosted, "5 GB" local), except for the wording changes listed above
in the Run Local view.
- With STREAMLIT_SERVER_MAX_UPLOAD_SIZE, "--server.maxUploadSize N" or
a config.toml value set, the sidebar status and the Run Local view
show that cap. Nothing about the cap itself changes: Streamlit
enforces the same limit as before.
- No new setting or environment variable. Hosts do not have to edit
the app to change the number shown.
How to verify
python -m pytest -q fibermorph/test/test_gui_app.py \
fibermorph/test/test_gui_inputs.py
By hand: STREAMLIT_SERVER_MAX_UPLOAD_SIZE=2000 fibermorph-gui, open
Run Local; the sidebar footer and the info box say 2 GB, matching
the limit under the file uploader on the Cross-Section view.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- fibermorph/core/curvature.py, window_iter():
- window_size=None again runs a whole-hair analysis. One Taubin
circle is fitted to each hair, hairs with area <= 0.5 * resolution
pixels are skipped, and the result has the columns ID, curv_mean,
curv_median, length_mean, length_median, hair_count. The per-hair
table is written to analysis/ImageSum_<name>.csv with columns
curv and length. This is the code of window_iter() in v0.3.1.
- test=True again returns the per-hair table instead of the image
summary, in both the window-size branch (columns curv_mean,
curv_median, length) and the whole-hair branch (curv, length).
- One difference from v0.3.1: if no hair is longer than the minimum,
the whole-hair branch returns hair_count 0 and NaN means, as the
window-size branch already does, instead of failing with
"ValueError: No objects to concatenate" from pandas.
- Docstring now describes the returned tables.
- fibermorph/analysis/curvature_pipeline.py: the docstring of
curvature_seq() now describes window_size=None, test=True and the
columns of the returned tables (it still said test only "skips some
I/O" and that a summary is always returned). No code change.
- fibermorph/test/test_core_curvature.py: the two synthetic-skeleton
tests that read the summary columns now pass test=False (they passed
test=True only because the summary was returned). New tests cover
per-hair output with test=True, the whole-hair mode (columns, minimum
length, CSV file, empty image, a window list containing None).
- CHANGELOG.md: entry under Unreleased / Fixed.
Why
The 0.3.7 refactor removed two behaviors of v0.3.1's window_iter().
(1) With window_size=None it returned an empty DataFrame and logged a
warning. None is what the CLI passes when --window_size is omitted, so
the default `fibermorph --curvature` run produced no curvature values,
and so did any caller that relied on the documented "entire fiber"
default. (2) It ignored test=True and always returned the image
summary. fibermorph.demo.demo.validation_curv() calls
curvature_seq(..., test=True) and then reads the per-hair columns
`length` and `curv_median`, so the simulated-data validation from the
paper (dummy_curv) failed with a KeyError.
Behavior change
- `fibermorph --curvature` without --window_size now outputs one
summary row per image with curv_mean, curv_median, length_mean,
length_median and hair_count (previously: empty).
- curvature_seq(window_size=None, ...) returns that summary; the
extended_curvature option works on it (curl index etc. are added;
wave_count is 0 because it needs the windowed curv_mean_mean).
- curvature_seq(..., test=True) returns one row per hair, not the
summary. Callers that passed test=True and expected the summary
must pass test=False; the CLI, GUI, batch pipeline and workflows
already pass test=False and are unaffected.
- Windowed analysis with test=False is unchanged.
How to verify
python -m pytest -q fibermorph/test/test_core_curvature.py \
fibermorph/test/test_curvature_pipeline.py
The new classes are TestPerHairOutputWhenTestTrue and TestWholeHairMode.
Reverting either behavior makes them fail. The existing pipeline tests
that pass window_size=None now exercise the whole-hair code instead of
receiving an empty table.
BREAKING CHANGE: window_iter()/analyze_all_curv()/curvature_seq() return
the per-hair table when test=True and a whole-hair summary when
window_size=None; code that relied on 0.3.7-1.0.1 behavior (summary with
test=True, empty table with window_size=None) must be updated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- fibermorph/gui/app.py, Cross-Section view > Settings, hosted mode:
the caption that replaces the "SAM2 checkpoint path" box is
reworded.
* Checkpoint file present (os.path.isfile on the server's
checkpoint): "SAM2 checkpoint: file found on this server. SAM2
also needs a GPU on the server; without one the app uses
watershed." (was "SAM2 checkpoint: configured on this server.")
* No file: "SAM2 checkpoint: no file found on this server, so SAM2
segmentation falls back to watershed." (was "...none configured
on this server, ...")
Neither caption contains the path. A comment next to the caption
records why only the file is checked.
- fibermorph/test/test_gui_app.py: the two caption tests are renamed
(test_hosted_caption_says_checkpoint_is_found_without_showing_path,
test_hosted_caption_says_when_no_checkpoint_file_is_found) and check
the new wording, that the GPU requirement is mentioned when a file
exists, and that the path is still not shown.
- CHANGELOG.md: the Security entry for the SAM2 checkpoint describes
the new caption.
Why
The caption only tested whether the checkpoint file exists. In
segmentation code (fibermorph/processing/section_sam2.py), SAM2 is used
only if the sam2 package imports and a CUDA GPU is present; otherwise
the app uses watershed before it looks at the checkpoint. On a CPU-only
server (for example the CPU stage of the repository's Dockerfile) with a
checkpoint file in place, the caption said "configured on this server",
which suggests SAM2 will run when it will not. "Use SAM2 segmentation
(GPU required)" is shown right above it, but the caption should not
contradict that.
Behavior change
- Text only. The checkpoint passed to segment_section(), the choice
between SAM2 and watershed, and the measurements are unchanged.
- The hosted caption now says "file found" / "no file found" instead of
"configured" / "none configured", and mentions the GPU requirement
when a file is found.
How to verify
python -m pytest -q fibermorph/test/test_gui_app.py
test_hosted_caption_says_checkpoint_is_found_without_showing_path and
test_hosted_caption_says_when_no_checkpoint_file_is_found.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed - CHANGELOG.md, Unreleased > Breaking Changes: new entry "Hosted visitors can no longer set the SAM2 checkpoint". It says that in hosted mode the Cross-Section view has no "SAM2 checkpoint path" box, that the server always uses SAM2_CHECKPOINT or the packaged default path, that the Run Remote checkpoint box now starts empty, that a host who wants SAM2 must set SAM2_CHECKPOINT (or put the file at the default path) on a machine with a GPU and restart the app after changing it, and that local mode is unchanged. It points to the existing Security entry for the full description. - CHANGELOG.md, Unreleased > Added: the fibermorph.gui.inputs entry now lists restore_names() (puts the uploaded file name back into error messages) and format_upload_cap() (formats the upload cap as "500 MB" or "5 GB"). It listed only save_uploads(), display_name() and safe_extension(). Why The SAM2 checkpoint change was committed as a breaking change, but the changelog described it only under Security. Someone reading Breaking Changes to decide what to do when updating a server would not learn that visitors lost the checkpoint box and that the host now has to set SAM2_CHECKPOINT. The inputs entry omitted two public helpers that the app uses. Behavior change None. Documentation only; no code, test or runtime behavior changes. How to verify git show --stat HEAD (only CHANGELOG.md changes) Read "Breaking Changes" and "Added" under "Unreleased" in CHANGELOG.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- Add fibermorph/test/test_curvature_golden.py. It runs curvature_seq()
with the default options (no CLAHE, no extended curvature) and
compares per-hair rows (test=True) and the image summary
(test=False) with CSV files made by the original v0.3.1 code, using
rtol 1e-9, for three window settings: 50 px, 0.5 mm and None (whole
hair), at 132 px/mm. Per-hair rows are compared in order, not sorted.
- TestSyntheticImageGolden: offline, on a synthetic 1000 x 1000 PNG
(about 400 KB) with arcs of several radii, a near-straight line, a
straight line and a short arc.
- TestDemoImagesGolden: on the lab demo images 004_demo_curv.tiff
(curly) and 027_demo_nocurv.tiff (straight), windows 50 px and
None. The tests download the TIFFs from
github.com/tinalasisi/fibermorph_DemoData (test_input/curv) at test
time, once per test run (a file already downloaded is reused), and
skip if the download fails or the file is not a TIFF. If the
environment variable FIBERMORPH_DEMO_CURV_DIR points to a folder
holding the two files, they are used and nothing is downloaded. The
TIFFs are not committed.
- TestDownloadHelper: the download helper reuses a downloaded file,
uses FIBERMORPH_DEMO_CURV_DIR, and skips when the server cannot be
reached.
- TestValidationCurvCallPath: curvature_seq(test=True) returns
per-hair rows with the columns length and curv_median that
fibermorph.demo.demo.validation_curv() reads.
- Add fibermorph/test/test_data/curv_golden/: the synthetic PNG and the
script that draws it, the reference CSV files, make_curv_reference.py
(runs the unmodified v0.3.1 code to make them) and a README with the
exact environment and commands.
- CHANGELOG.md: entry under Unreleased / Added.
Why
Curvature results changed silently between releases because
scikit-image's frangi() changed, and two behaviors of the original
window_iter() were lost in a refactor. Nothing in the test suite
compared the numbers with the published method, so neither was caught.
These tests pin the default curvature output to the original. The CLAHE
option and the extended-curvature option (medial-axis skeleton) are not
part of v0.3.1 and are not covered.
Behavior change
None for users. The test suite gains 19 tests that take about 35
seconds in total. The demo-image tests need the two TIFFs (2.6 MB and
3.3 MB), either from the download (each fetched once per run) or from
FIBERMORPH_DEMO_CURV_DIR; without network and without that variable
those 8 tests are skipped and the other 11 run. No change to the
package code.
How to verify
python -m pytest -q fibermorph/test/test_curvature_golden.py
Offline only: add -k "Synthetic or ValidationCurv or DownloadHelper".
The references were made with Python 3.8.20, scikit-image 0.16.2,
numpy 1.18.5, scipy 1.4.1, pandas 1.0.5 and Pillow 7.2.0 (v0.3.1 pins
scikit-image 0.16.2 and scipy 1.4.1); the current code matches them to
about 1e-14 relative on Python 3.12 with scikit-image 0.26. Switching
filter_curv() back to skimage.filters.frangi, or removing the
whole-hair or test=True behavior of window_iter(), makes these tests
fail.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Brings in three commits that make the curvature analysis give the same results as the published fibermorph method (v0.3.1, November 2020) on any installed scikit-image, with the default options: - fix(curvature)!: freeze the scikit-image 0.16.2 Frangi filter (new fibermorph/core/frangi_v016.py, used by filter_curv). The installed skimage.filters.frangi changed after 0.16.2. With it, the straight-hair demo image measured 0.173 mm^-1 mean curvature instead of 0.053 (3.3x). All later pipeline steps were already unchanged. - fix(curvature)!: restore whole-hair mode (window_size=None, the CLI default) and the per-hair table returned with test=True. Both were lost in the refactor first released in 0.3.7. --demo_dummy_curv works again. - test(curvature): golden tests against output of the original v0.3.1 code run with scikit-image 0.16.2. One test uses an offline synthetic image. The other uses the two demo images, downloaded at test time. Behavior change: curvature numbers from installs with scikit-image newer than 0.16.2 change to the original method's values. Each commit and CHANGELOG.md has the details. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in seven commits that fix how the GUI behaves when it is hosted for other people: - feat(gui)!: fibermorph-gui starts in hosted mode by default: uploads only, 500 MB cap, no folder input. Local mode (a "Folder on disk" input that reads this machine's folders, a 5 GB cap, listening on localhost only) now needs `fibermorph-gui --local`, or FIBERMORPH_LOCAL=1 set explicitly. Before, every fibermorph-gui start, including the repository's Dockerfile, silently turned local mode on, so every visitor could read folders on the server. Other arguments are passed through to `streamlit run`. - fix(gui)!: hosted visitors can no longer type the SAM2 checkpoint path that the server loads. The host sets it with the SAM2_CHECKPOINT environment variable. fibermorph does not ship a checkpoint file. - fix(gui): uploads are saved under generated names (upload_0001.tif, ...) inside the temporary folder, never under the client-supplied filename. The original filename is still shown in every result. Two uploads with the same name are now measured separately; before, the second one overwrote the first. - Follow-ups: the page shows the upload cap Streamlit actually enforces, the SAM2 caption says that SAM2 also needs a GPU, a warning appears when local mode is opened to other machines, and CHANGELOG entries were added. Behavior change for anyone hosting the app: a container or server that starts with `fibermorph-gui` now runs in hosted mode, which is the safe default. Use `--local` only on your own computer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What changed - README.md and CHANGELOG.md no longer mention a "packaged default" SAM2 checkpoint path. fibermorph does not ship a checkpoint, so a host must download one and point SAM2_CHECKPOINT at it. - CHANGELOG.md: whole-hair mode and the per-hair test output were removed by the refactor first released in 0.3.7, not in 1.0.0. Curvature values change compared with 0.3.x and 1.0.x releases run on scikit-image newer than 0.16.2, not only compared with 1.0.x. - README.md: dropped a sentence of change history from the usage section. It is already in CHANGELOG.md. - gui/app.py, Run Local view: the explanation no longer calls a local app "this hosted app" and no longer claims local mode has no upload limit. It only gives the cap number when the app is hosted. - test_curvature_golden.py: test_local_folder_is_used_without_download now uses a separate download folder, so it really tests the FIBERMORPH_DEMO_CURV_DIR branch. Before, it passed through the already-downloaded branch. - make_curv_reference.py: the docstring now says which library versions are pinned by v0.3.1's environment.yml, matching README.md in the same folder. Why These statements were inaccurate or confusing for anyone deploying the app or reading the changelog. Behavior change None for analyses. The Run Local view's wording changes. How to verify pytest (375 passed); README.md and CHANGELOG.md [Unreleased]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What changed
- fibermorph/cli.py: --window_size (nargs="+") now has an argparse
type, _window_size_value, which turns each value into a float, or
into None for "none" / "None". A new function,
normalize_window_sizes(values, window_unit), then checks the values
against --window_unit and returns what the curvature workflow gets:
None (whole hair), a list of ints for px, or a list of floats for mm.
Problems are reported with parser.error(), so the exit code is 2.
The check runs whichever module is chosen (--section and --raw2gray
too), although only --curvature uses the option.
- fibermorph/cli.py: parse_args() and main() take an optional list of
arguments (default: sys.argv[1:]), so the command line can be run
from tests without a subprocess. The console script and
"python -m fibermorph" are unchanged.
- --window_size help text, README.md (curvature options) and
CHANGELOG.md ([Unreleased] > Fixed) describe the new behavior.
- fibermorph/test/test_cli.py: new tests (see How to verify).
Why
The values were never converted, so curvature code received strings.
window_iter() in fibermorph/core/curvature.py computes
int(window_size * resolution) for --window_unit mm, which raised
"TypeError: can't multiply sequence by non-int of type 'float'". With
px it only worked because int("50") succeeds; "50.0" raised
"ValueError: invalid literal for int()". The original v0.3.1 command
line has the same bug. The help text already promised "Float or integer
or None", with None (whole hair) as the default.
Behavior change
- --window_unit mm with any --window_size now runs (it crashed).
- --window_size 50.0 with px now runs as 50 (it crashed).
- --window_size none is accepted and fits the whole hair, like leaving
the option out. It cannot be combined with numbers.
- Several values (a sweep) still work: --window_size 25 50 100.
- These now stop with a usage message and exit code 2 instead of a
traceback or an odd result: a value that is not a number, not
finite, or not greater than 0; a fraction of a pixel such as 50.5
with px (it used to crash). Zero and negative px values used to run:
they logged "less than 10 pixels, using full length" and measured
each hair over its whole length. They now exit 2.
- Output names are unchanged: px windows are ints
(ImageSum_<name>_WindowSize-50px.csv) and mm windows are floats
(..._WindowSize-0.5mm.csv; 1 mm is written as 1.0mm).
- The Run Remote view of the GUI writes "--window_size 50" (whole
pixels), which parses unchanged.
- Not covered here: other numeric options. Out-of-range values such
as --jobs 0 or --resolution_mm 0 still end in a Python traceback with
exit code 1; the next commit in this series rejects them with a
usage message.
How to verify
python -m pytest -q fibermorph/test/test_cli.py
runs parsing tests (px ints, px "50.0", px "50.5" rejected, mm floats,
several values, none, zero and negative rejected, default None) and
runs the real command line on a synthetic TIFF with px, mm, no
--window_size, and a sweep; the mm and px results are compared with
curvature_seq called directly. By hand:
fibermorph --curvature -i <tiff folder> -o <out folder> \
--window_size 0.5 --window_unit mm --resolution_mm 132
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- fibermorph/cli.py: the error for "none" combined with other values
now reads "'none' (fit the whole hair) must be given on its own, not
repeated or combined with window sizes". Before, "--window_size none
none" was reported as "cannot be combined with other window sizes"
although no other size had been given. The --window_size help text
now says that a window shorter than 10 pixels is not used.
- README.md (curvature options) and CHANGELOG.md ([Unreleased]): state
the 10 pixel rule. The changelog entry for --window_size no longer
says that zero and negative windows used to crash; a new Breaking
Changes entry says that a zero or negative px window used to run and
now exits with code 2, whichever module is chosen.
- fibermorph/test/test_cli.py: the window-size sweep test now also
compares its numbers with curvature_seq (it only checked names
before); a new test shows that a 0.001 mm window and a 5 px window
give the same numbers, because both fall under 10 pixels; the "none"
tests cover "none none" and "none 50".
Why
The curvature code (subset_gen in fibermorph/core/curvature.py) has
always replaced a window under 10 pixels by the full length of each
hair, logging "using full length". With --window_unit mm a value such
as 0.001 is accepted (132 px/mm makes it 0 px) and the output file is
named ..._WindowSize-0.001mm, so a user could believe a 0.001 mm window
was measured. The help text said a mm window "can be any number greater
than 0" and did not mention this. The changelog entry written with the
--window_size change also did not say that 0 and negative px windows ran
before that change and now stop.
Behavior change
- Compared with the previous commit, only one error message changes;
the rest is documentation and tests. No curvature number changes.
- The documented rule is the existing one: a window under 10 pixels
(for mm: int(window_size * --resolution_mm) < 10) measures each hair
as a single window over its whole length. Its numbers are not the same
as --window_size none, which fits one circle per hair and skips hairs
shorter than 0.5 mm.
- The previous commit made a zero or negative px window exit 2 instead
of measuring whole hairs. A script that still writes one (for
example one downloaded from the GUI's Run Remote view, whose "Taubin
window (px)" box has no lower limit) needs a positive window.
How to verify
python -m pytest -q fibermorph/test/test_cli.py
By hand, both commands log "less than 10 pixels, using full length"
and give the same numbers (only the ID differs):
fibermorph --curvature -i <tiff folder> -o <out> --window_size 5
fibermorph --curvature -i <tiff folder> -o <out2> \
--window_size 0.001 --window_unit mm
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- core/filters.py: deleted filter_curv_clahe (CLAHE contrast
enhancement, the installed scikit-image Frangi filter, and an Otsu
threshold computed on the middle 70% of the frame, i.e. a crop of the
top and bottom 15%). It is also gone from the fibermorph package
exports and __all__.
- Dropped the use_clahe parameter from analysis.curvature_pipeline.
curvature_seq, workflows.curvature, workflows.batch and
pipeline.batch.run_batch.
- cli.py: --use-clahe no longer enables anything. It stays registered,
hidden from --help, only to reject old scripts with a clear error
(see Behavior change).
- gui/app.py: removed the "CLAHE preprocessing" toggle from the
Curvature view and from the Run Remote view; the SBATCH script
generator can no longer write --use-clahe.
- Tests: deleted the filter_curv_clahe tests and the CLAHE cases of the
curvature pipeline tests. Added test_cli_removed_options.py (exit code
2, message, hidden from --help), tests that the API no longer has
use_clahe or filter_curv_clahe, and GUI tests that no CLAHE toggle is
shown and the generated script has no --use-clahe. The golden-test
docstring and README no longer mention the option.
Why
CLAHE preprocessing, and the 15% band crop that came with it, are not
part of the published fibermorph curvature method (v0.3.1, 2020). The
option used the installed scikit-image Frangi filter instead of the
frozen scikit-image 0.16.2 version the published numbers come from, so
its output changed with the installed scikit-image version and could
not be reproduced against the published method. The ridge-filter step
now has one path: filter_curv (the frozen 0.16.2 Frangi filter).
This commit is the first of three: the next one removes the extended
curvature option, which also changes the skeletonization step, and the
third updates the README, CHANGELOG, ROADMAP and CONTRIBUTING. Until
the third, the README still shows a curvature example with --use-clahe,
which now exits with status 2.
Behavior change
- fibermorph --curvature ... --use-clahe now stops before doing any work
with exit status 2 and "argument --use-clahe: this option was removed
because CLAHE preprocessing is not part of the published fibermorph
curvature method". Scripts that still pass it (for example SBATCH
scripts generated earlier by the web app) must have the flag deleted.
- Python callers passing use_clahe=... now get a TypeError;
fibermorph.filter_curv_clahe no longer exists.
- Results with the default options are unchanged: use_clahe defaulted
to False in every entry point.
How to verify
python -m pytest fibermorph/test/test_cli_removed_options.py \
fibermorph/test/test_curvature_pipeline.py \
fibermorph/test/test_curvature_golden.py
fibermorph --curvature -i in -o out --use-clahe; echo $? # prints 2
BREAKING CHANGE: the --use-clahe flag, the use_clahe parameter of
curvature_seq, curvature, batch and run_batch, and filter_curv_clahe
are removed. The flag now exits with status 2 and an explanatory
message; the Python names raise TypeError or AttributeError.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- analysis/curvature_pipeline.curvature_seq: removed the
extended_curvature parameter and both of its code paths: the
medial-axis skeleton (skimage medial_axis) used in place of
skeletonize, and the extra output columns curl_index, curl_index_std,
wave_count, wave_count_per_mm and length_total. Curvature now has one
path: filter_curv (frozen scikit-image 0.16.2 Frangi filter) ->
binarize -> remove particles -> skeletonize -> prune -> Taubin
windows.
- core/curvature.py: deleted curl_index_from_skeleton, wave_count and
their helper pixel_length_correction_coords (no other caller). The
first two are also removed from the fibermorph package exports and
__all__.
- Dropped the extended_curvature parameter from workflows.curvature,
workflows.batch and pipeline.batch.run_batch.
- cli.py: --extended-curvature no longer enables anything. It stays
registered, hidden from --help, only to reject old scripts with a
clear error (see Behavior change).
- gui/app.py: removed the "Show extended (experimental) metrics" toggle
and the Curl Index, Wave Count and Waves/mm columns from the Curvature
view, and the "Extended curvature metrics" switch from the Run Remote
view. The helper that runs one image now returns just the per-fragment
table. The per-image summary still shows "Total Length (mm)": the app
sums the fragment lengths itself; it never came from the removed
length_total column.
- Tests: deleted test_core_curvature_extended.py (10 tests) and the two
extended-output cases of the curvature pipeline tests. Added tests
that the removed parameters and functions are gone, that
--extended-curvature is rejected like --use-clahe (and that
--extended-features for cross-sections still parses), and that the
generated SBATCH script and the Curvature summary have no removed
flags or columns. Added two batch tests that compare batch curvature
output with the published v0.3.1 reference numbers. The golden-test
docstring and README no longer mention options.
Why
Extended curvature (curl index, wave count, medial-axis skeleton) is not
part of the published fibermorph curvature method (v0.3.1, 2020). It
came from an unvalidated fork, swapped the skeletonization step for a
different algorithm, and its results were not checked against the
published method. The medial-axis step also breaks ties at random
(skimage medial_axis called without a seed), so repeated runs on the
same image could give different curvature. Curvature output now comes
only from the published method.
Behavior change
- fibermorph --curvature ... --extended-curvature now stops before doing
any work with exit status 2 and "argument --extended-curvature: this
option was removed because extended curvature (curl index, wave count)
is not part of the published fibermorph curvature method". Scripts
that still pass it (for example SBATCH scripts generated earlier by
the web app) must have the flag deleted.
- run_batch and workflows.batch defaulted extended_curvature to True, as
did the Run Remote switch, so batch curvature output silently used the
medial-axis skeleton and carried the five extra columns. It now uses
the original method: the extra columns are gone and the numbers
change. The old batch values were not reproducible, because the
medial-axis step breaks ties at random: for the synthetic test image
with a 50 px window at 132 px/mm, curv_mean_mean came out between
about 0.80 and 0.82 from one run to the next. It is now 0.8024 on
every run, the v0.3.1 value. Batch curvature results produced before
this change cannot be reproduced exactly; re-run them.
- workflows.curvature and curvature_seq defaulted extended_curvature to
False, so their default output is unchanged. The command-line tool
also defaulted to off.
- Python callers passing extended_curvature=... now get a TypeError;
fibermorph.curl_index_from_skeleton and fibermorph.wave_count no
longer exist.
How to verify
python -m pytest fibermorph/test/test_cli_removed_options.py \
fibermorph/test/test_pipeline_batch.py \
fibermorph/test/test_curvature_golden.py \
fibermorph/test/test_gui_app.py
fibermorph --curvature -i in -o out --extended-curvature; echo $? # 2
BREAKING CHANGE: the --extended-curvature flag, the extended_curvature
parameter of curvature_seq, curvature, batch and run_batch, the
functions curl_index_from_skeleton and wave_count, and the columns
curl_index, curl_index_std, wave_count, wave_count_per_mm and
length_total of the curvature_seq and batch output are removed. Batch
curvature output changes from the (run-to-run varying) medial-axis
result to the original method.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- README.md: removed the CLAHE bullet from "What's new in v2.0", the
curvature example that used --use-clahe and --extended-curvature, and
the flag descriptions for both. The curvature section now says that
curvature uses the published method (v0.3.1) with no alternative
modes, that the two flags were removed, and what a command that still
passes them gets (an error, exit code 2).
- CHANGELOG.md [Unreleased]: added a "Removed" entry (what was removed,
why, what scripts that still pass the flags now get, and that
run_batch and workflows.batch curvature output now uses the original
method instead of the medial-axis path) and a summary under
"Breaking Changes" that names the removed columns (curl_index,
curl_index_std, wave_count, wave_count_per_mm and length_total of the
curvature_seq and batch output; the GUI's own "Total Length (mm)"
column stays). Both entries say that the old batch values were not
reproducible, because medial_axis breaks ties at random (on the
synthetic test image curv_mean_mean came out between about 0.80 and
0.82 from run to run, and is now 0.8024 every time), so batch
curvature results made before this change should be re-run. Rewrote
the other [Unreleased] entries that described the two options as
available: the GUI help-text entry, the golden-test entry ("default
options"), "Curvature output refocused", the diameter entry's note
about the medial-axis skeleton, and the scikit-image entry under
"Fixed". The released [2.0.0] section is left as the record of what
was added in May 2026.
- ROADMAP.md: replaced the item about validating extended curvature with
a note that it and CLAHE were removed, dropped the three follow-up
items about --extended-curvature, curl_index_from_skeleton and
wave_count, and limited the planned column reference to the
--extended-features output.
- CONTRIBUTING.md: the example pull request title "feat: validate
wave-count on synthetic fibers" named a removed feature as work
waiting to be done; it now reads "fix: handle an empty curvature
directory".
Why
The code no longer has these options, so documentation that describes
them as available, or lists work to validate them, would mislead users
and anyone reading the project plan. Scripts and notes written against
earlier builds need a clear statement of what changed.
Behavior change
None. Documentation only.
How to verify
git grep -n -i -E \
"clahe|extended[ _-]curvature|curl[ _-]?index|wave[ _-]?count" \
-- . ":!fibermorph/gui/visualizations.py"
lists only the README and ROADMAP notes that the options were removed,
the CHANGELOG, the hidden removed-flag handling in cli.py, and tests
that check the options are gone.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- fibermorph/cli.py: new function check_numeric_options(args), called
from parse_args() right after the --window_size check. A problem is
reported with parser.error(), so the command exits with code 2 and a
one-line message that starts "argument --<option>:". The rules:
--jobs must not be 0 (negative values are joblib's
count-from-all-CPUs settings: -1 is every
CPU, -2 all but one; they still work)
--resolution_mm, must be a finite number greater than 0, and
--resolution_mu still finite once converted to pixels per
unit (a mm_per_px or um_per_px value below
about 5.6e-309 overflows when inverted)
--minsize must be 0 or more
--maxsize must be greater than 0
--minsize / --maxsize --minsize must not be larger than --maxsize
The argparse types (int, float) are unchanged. The help text of each
option states its limit, and parse_args() documents them.
- README.md: the curvature and section option lists state the limits,
--resolution_mm is described as a float (it said integer, but the
option has always been a float), and a short "Checks on numeric
options" section lists them all.
- CHANGELOG.md ([Unreleased]): the change is described under Fixed. The
refusal of a negative --minsize has its own Breaking Changes entry,
next to the --window_size one, and the Fixed entry says which crash
is not covered (see Behavior change).
- fibermorph/test/test_cli.py: parsing tests for every rule, and
subprocess tests that run "python -m fibermorph" with 11 bad
combinations and expect exit code 2, no "Traceback", the option name
in the message and no output directory created; one more subprocess
test checks that --jobs -1 still runs.
Why
These options only had a type, so out-of-range values were accepted by
argparse and failed later, deep in the run, or gave nothing:
--jobs 0 ValueError: n_jobs == 0 in Parallel has no
meaning
--resolution_mm 0 / -5 ValueError: Resolution must be greater
than 0. (same for --resolution_mu)
--resolution_mm nan ValueError: cannot convert float NaN to
integer
--resolution_mm inf OverflowError: cannot convert float infinity
to integer
--resolution_mu nan/inf with --section: KeyError: "None of ['ID'] are
in the columns", or exit 0 after writing a
summary_section_data.csv with only its
header
--maxsize 0, or --minsize larger than --maxsize
with --section: the same KeyError (no section
can match)
main() converts both resolutions for every module, so a zero or
negative --resolution_mm also stopped --section and --raw2gray runs
that never use it.
Behavior change
- Each value listed above now exits with code 2 and a usage message
before any image is read, instead of exit code 1 with a traceback (or
exit code 0 with an empty table).
- The checks run whichever module is chosen, so a bad --resolution_mm
on a --section run, or a bad --resolution_mu on a --curvature run, is
now refused although the module would not use it. A run with all of
these options at valid values behaves as before.
- One command that used to run is now refused: a negative --minsize
(for example --minsize -5), which acted like a lower bound of 0. A
script that passes one needs 0 or a positive number. This includes a
script downloaded from the GUI's Run Remote view with a "Min diameter
(µm)" below 0; that box has no lower limit.
- Not changed: --jobs -1 and -2; --minsize 0; --minsize equal to
--maxsize; values of --resolution_mm / --resolution_mu given in
mm_per_px / um_per_px.
- Not fixed here: a --section run whose limits are valid but match no
section (for example --minsize 400 --maxsize 500 on images without
sections that size, or an absurdly large --resolution_mu) still ends
in the same KeyError. That comes from the section workflow, which
this change does not touch.
How to verify
python -m pytest -q fibermorph/test/test_cli.py
By hand, each of these exits with code 2 and prints one error line:
fibermorph --curvature -i <tiff folder> -o <out> --jobs 0
fibermorph --curvature -i <tiff folder> -o <out> --resolution_mm nan
fibermorph --section -i <tiff folder> -o <out> --resolution_mu 0
fibermorph --section -i <tiff folder> -o <out> \
--minsize 200 --maxsize 100
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What changed
- fibermorph/cli.py: new constant MAX_WINDOW_PX (1,000,000,000) and new
function check_window_pixel_size(args), called from parse_args() right
after check_numeric_options(). Each --window_size value is compared
with MAX_WINDOW_PX in pixels: as given for --window_unit px, and
multiplied by the pixels-per-mm of --resolution_mm (after conversion
from mm_per_px if chosen) for --window_unit mm. A longer window is
reported with parser.error(), so the command exits with code 2 and a
one-line message that starts "argument --window_size:" and names the
limit, for example:
argument --window_size: 1e+307 mm is inf pixels at --resolution_mm
132 px_per_mm, longer than the largest window allowed
(1,000,000,000 pixels)
The check needs a valid resolution, so it runs after
check_numeric_options(); a bad --resolution_mm is reported first.
Nothing is checked when --window_size is omitted or is "none". The
--window_size help text and the parse_args() docstring state the
limit.
- README.md (curvature options) and CHANGELOG.md ([Unreleased]): state
the limit. The Breaking Changes entry for zero and negative windows
now covers over-long windows as well.
- fibermorph/test/test_cli.py: parsing tests (class TestWindowSizeLimit)
for the boundary in px and in mm, huge values, a mm_per_px resolution,
a sweep with one long window, other modules, and the order of the
checks; subprocess tests that run "python -m fibermorph" with
"--window_unit mm --window_size 1e307", "--window_size 1e300" and
"--window_unit mm --window_size 10 --resolution_mm 1e308" and expect
exit code 2, no "Traceback" and no output directory; and one that runs
the largest allowed window to the end.
Why
Validation only asked for a finite number greater than 0, so very large
values passed it and then failed deep in the run, after the output
directory had been created:
mm, --window_size 1e307 OverflowError: cannot convert float
infinity to integer (window_iter in
fibermorph/core/curvature.py computes
int(window_size * resolution))
mm, --window_size 10 --resolution_mm 1e308
the same OverflowError
px, --window_size 1e300 OSError: [Errno 63] File name too long,
because the 300 digits of the whole
number become part of the output file
name ImageSum_<name>_WindowSize-
<digits>px.csv
Between about 1e9 and 1e200 pixels the run did not fail, but no hair is
that long, so every image gave a row with hair_count 0 and empty
measurements.
Behavior change
- A window longer than 1,000,000,000 pixels now exits with code 2 and a
usage message before any image is read, instead of exit code 1 with a
traceback (very large values) or exit code 0 with an empty
measurement (values up to about 1e200). The check runs whichever
module is chosen, as the other --window_size checks do.
- Not changed: every window of up to 1,000,000,000 pixels, including
windows longer than the image (they measure nothing, as before);
output file names; the 10 pixel rule; runs without --window_size.
How to verify
python -m pytest -q fibermorph/test/test_cli.py
By hand, each of these exits with code 2 and prints one error line, and
creates no output folder:
fibermorph --curvature -i <tiff folder> -o <out> \
--window_unit mm --window_size 1e307
fibermorph --curvature -i <tiff folder> -o <out> --window_size 1e300
fibermorph --curvature -i <tiff folder> -o <out> \
--window_unit mm --window_size 10 --resolution_mm 1e308
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Brings in three commits that leave curvature analysis with only the published fibermorph method (v0.3.1): - refactor(curvature)!: remove the CLAHE preprocessing option (filter_curv_clahe, --use-clahe, use_clahe= parameters, the GUI toggle, and that path's top/bottom 15% band crop). - refactor(curvature)!: remove the extended curvature metrics option (curl_index, curl_index_std, wave_count, wave_count_per_mm, length_total, the medial-axis skeleton path, --extended-curvature, extended_curvature= parameters, and the GUI toggles and SBATCH switch). - docs: README, CHANGELOG, ROADMAP and CONTRIBUTING updated. Behavior change: - Scripts that pass --use-clahe or --extended-curvature now stop with a clear error (exit code 2) saying the option was removed. - run_batch and workflows.batch used extended curvature by default, so their curvature columns came from the medial-axis skeleton and varied slightly from run to run. They now use the original method, and the results are identical on every run. Batch results made before this change cannot be reproduced exactly and should be re-run. - Default curvature results are unchanged; the golden tests against v0.3.1 pass. - Cross-section features, including --extended-features, are not affected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in four commits that fix how the command line reads its numeric options: - fix(cli): --window_size values are read as numbers. Before, they reached the curvature code as text, so any window in mm crashed with a TypeError and a pixel window such as "50.0" crashed with a ValueError. The same bug was in v0.3.1. Pixel windows must be whole numbers and become ints, so output files keep the ..._WindowSize-50px naming. mm windows stay floats (..._WindowSize-0.5mm). "none", or leaving the option out, fits whole hairs. - fix(cli): the 10-pixel window floor is documented, and the error messages are tidied. - fix(cli): --jobs 0, a resolution that is not a finite number greater than 0, a negative --minsize, --maxsize 0, and --minsize larger than --maxsize now stop with a usage message (exit code 2) before any image is read. Before, they ended in a traceback or an empty result. - fix(cli): a --window_size longer than 1,000,000,000 pixels is refused. Behavior change: commands that used to crash now run (mm windows) or stop with a clear message (out-of-range values). Valid commands give the same results and the same output names as before. The conflict with the removal of --use-clahe / --extended-curvature in fibermorph/cli.py was resolved by keeping both changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What changed
- fibermorph/cli.py: a --jobs larger than os.cpu_count() is reduced to
that count, with a one-line message on stderr ("fibermorph: --jobs N
is more than the C CPUs on this machine; using --jobs C."). The help
text says so.
- fibermorph/cli.py: the "--window_size is too long" errors print exact
numbers. Before, :g rounding made 1000000001 px read as "1e+09 px",
which looked equal to the 1,000,000,000 px limit.
- fibermorph/gui/app.py, Run Remote view: "Min diameter (µm)" has a
lower limit of 0, "Max diameter (µm)" of 1, and "Taubin window (px)"
of 1. A warning appears when Min diameter is larger than Max diameter.
These match the CLI's checks, so a generated SBATCH script no longer
carries a value that fibermorph refuses.
- Tests in test_cli.py and test_gui_app.py; README.md and CHANGELOG.md
updated.
Why
A huge --jobs crashed joblib (OverflowError, or OSError "Invalid
argument") after the output directory had been created. A value around
10,000 instead started thousands of worker processes, which kept
running after fibermorph exited. More jobs than CPUs never helped.
The Run Remote inputs had no lower limits, so they could write scripts
that the CLI's new checks stop.
Behavior change
--jobs above the machine's CPU count runs with one job per CPU instead
of crashing or hanging. On a cluster node, os.cpu_count() is the node's
CPU count, so a --jobs equal to the CPUs requested from SLURM is
unchanged. The Run Remote view refuses values below the limits above.
How to verify
pytest fibermorph/test/test_cli.py fibermorph/test/test_gui_app.py
fibermorph --curvature -i <dir> -o <out> --jobs 2147483648
(prints the message and completes)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cal/cluster On the Streamlit-hosted app (no GPU) the "Use SAM2 segmentation" switch could be turned on and every image then quietly fell back to watershed. - Add sam2_runtime_status() to section_sam2: (ok, reason) from the sam2 import and torch.cuda.is_available(). - Cross-Section: when SAM2 can't run, lock the switch off, show a warning with the one-line reason, and name Run Local (fibermorph-gui --local on a GPU machine) and Run Remote (cluster script) as the ways to run SAM2. The hosted checkpoint caption now only appears when SAM2 can run. - Run Local: add how to install SAM2 and start the local GUI with it. - Run Remote: note that SAM2 runs on the cluster's GPU node. - README: add an "Online vs local" table; CHANGELOG entry. - Tests: cover hosted/local unavailable, available, the real check on a GPU-less machine, and the Run Local/Run Remote text. Tests that need the local checkpoint box now simulate a GPU host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The section pipeline passes "" for --sam2-cfg and --sam2-checkpoint when
they are not given. The empty config name overrode the default, so
build_sam2 failed ("Cannot find primary config ''") and every image fell
back to watershed, even on a GPU node with SAM2 installed and an explicit
checkpoint. This also broke SAM2 in the scripts made by Run Remote.
- Treat an empty model config as "use the default config".
- Resolve an empty checkpoint as SAM2_CHECKPOINT, then the default.
- Look for the default checkpoint in fibermorph/checkpoints/ inside the
package (where the README and the GUI say to put it), falling back to
checkpoints/ at the repository root. It used to look only at the
repository root.
- Add tests with a stubbed sam2/torch so they run without a GPU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(sam2): make --use-sam2 load SAM2 instead of silently using watershed
feat(gui): show SAM2 as unavailable on the hosted app and point to lo…
fibermorph-architectureYou are over your plans usage limit. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.