Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
09fcf77
Add cleaned FEA CLI workflow
wallematthias Jun 22, 2026
3324685
Refactor FE helpers into domain package
wallematthias Jun 23, 2026
51513dd
Streamline FEA generation and reporting
wallematthias Jun 24, 2026
c784eb4
Clean FEA output naming and references
wallematthias Jun 25, 2026
689df8f
Update FEA CLI naming test expectations
wallematthias Jun 25, 2026
8b0f4ca
Improve femur crop safety and CI test runner
wallematthias Jun 25, 2026
1899c89
Cache CI conda environment
wallematthias Jun 25, 2026
1344e0c
Revert "Cache CI conda environment"
wallematthias Jun 25, 2026
cd6b158
Use cached conda OpenCV CI setup
wallematthias Jun 25, 2026
e1bff23
Merge remote-tracking branch 'origin/master' into ogo-fea
wallematthias Jun 26, 2026
7449c8b
Implement fixed-thickness spine cap workflow
wallematthias Jun 29, 2026
82d613c
Refine FE workflow generation
wallematthias Jul 14, 2026
10928e1
Harden FE workflow execution
wallematthias Jul 14, 2026
f6b7650
Harden FE helper compatibility
wallematthias Jul 14, 2026
f0aa638
Lock maintained FE workflow defaults
wallematthias Jul 14, 2026
aa8f75e
Stabilize editable package installs
wallematthias Jul 14, 2026
2703ddd
Make CI dependency installs non-interactive
wallematthias Jul 14, 2026
fabc8f0
Speed up CI dependency resolution
wallematthias Jul 14, 2026
c172f93
Clarify FE helper module boundaries
wallematthias Jul 14, 2026
b623c3a
Fold FE builders into domain modules
wallematthias Jul 14, 2026
aa2a5fd
Stabilize FE workflow geometry
wallematthias Jul 15, 2026
913297a
Centralize FE solve profile settings
wallematthias Jul 20, 2026
11a1c7e
Report Pistoia metrics for FE profiles
wallematthias Jul 20, 2026
0ad6793
Align hip FEA preprocessing defaults
wallematthias Jul 21, 2026
e0a5964
Use post-ICP flat femur crop by default
wallematthias Jul 21, 2026
d21beef
Set hip rough pre-ICP crop to 120 mm
wallematthias Jul 21, 2026
80f8776
Use straight distal support for post-ICP hip crop
wallematthias Jul 21, 2026
da15dc6
Use oblique post-ICP femur ratio crop
wallematthias Jul 21, 2026
5b75fbd
Allow transformed femur crop face voxel offset
wallematthias Jul 21, 2026
76a7137
Preserve rough femur crop face through ICP
wallematthias Jul 21, 2026
32caaea
Use distal slab for short femur oblique crop
wallematthias Jul 21, 2026
e66fd1f
Use stored oblique femur crop plane for support
wallematthias Jul 21, 2026
d38fb01
Use flat post-ICP femur crop by default
wallematthias Jul 21, 2026
6abbacf
Add GT-relative femur length study crop
wallematthias Jul 21, 2026
b06f4a8
Support fixed femur ICP transforms
wallematthias Jul 21, 2026
c926365
docs: specify masked pistoia length study
wallematthias Jul 28, 2026
17ab509
Update hip and L1 FEA workflows
wallematthias Aug 31, 2026
d5bc738
docs: clarify spine FEA command
wallematthias Aug 31, 2026
30a6cdb
docs: add vertebra FEA quickstart guide
wallematthias Aug 31, 2026
0794311
docs: expand vertebra FEA setup guide
wallematthias Sep 1, 2026
b542e05
docs: fix vertebra FEA install instructions
wallematthias Sep 1, 2026
0477aad
build: add netCDF4 runtime dependency
wallematthias Sep 1, 2026
71e0f88
fix: define femur shaft length after GT support
wallematthias Sep 1, 2026
8908f4a
docs: explain FEA workflow for new users
wallematthias Sep 1, 2026
d02f415
docs: map FEA walkthrough to implementation
wallematthias Sep 1, 2026
d5ba76c
feat: support label-selected Pistoia masks
wallematthias Sep 1, 2026
0e29280
fix: use VTK ICP as spine registration default
wallematthias Sep 8, 2026
f72d0cc
feat: expose spine ICP tuning options
wallematthias Sep 8, 2026
5453542
test: update spine ICP dry-run expectation
wallematthias Sep 8, 2026
2b875c0
feat: lock hip and spine FEA recipe defaults
wallematthias Oct 2, 2026
e2b7d94
feat: consolidate FEA validation, model QC, and short-shaft defaults
wallematthias Oct 8, 2026
90b761c
Merge maintained ARC disk geometry with current FEA validation and re…
wallematthias Oct 8, 2026
a624cbd
Initialize spine ICP by centroids and reject anatomical flips
wallematthias Oct 8, 2026
d3d1798
Clean disconnected vertebral body fragments before FEA registration
wallematthias Oct 9, 2026
1ce8c73
Use compact WebP previews for FEA QC and portable galleries
wallematthias Oct 9, 2026
41f018f
Keep each hip support connected and add FEA review sorting
wallematthias Oct 9, 2026
0642bcf
Preserve gallery scroll position when saving review decisions
wallematthias Oct 9, 2026
075852c
Integrate paginated FEA review galleries and reproducible cohort reco…
wallematthias Oct 9, 2026
bda0243
Write compact three-view FEA debug previews
wallematthias Oct 9, 2026
8669883
Simplify FEA guides and document workflow helpers
wallematthias Oct 9, 2026
fae39ac
Merge master into ogo-fea and preserve CI compatibility
wallematthias Oct 9, 2026
ad55357
Use upstream conda setup for CI dependencies
wallematthias Oct 9, 2026
e0520a7
Simplify FEA crop workflow and clarify generation stages
wallematthias Oct 10, 2026
938195b
Simplify FEA orchestration and protect shared helper contracts
wallematthias Oct 10, 2026
16d90f0
Group alignment and QC modules and remove study-specific examples
wallematthias Oct 10, 2026
c95060b
Remove study-specific documentation and unused FEA helpers
wallematthias Oct 10, 2026
cfd3cb2
Remove obsolete FEA exports and move benchmark builders to tests
wallematthias Oct 10, 2026
e47acac
Pin Ogo macOS CI to Intel runner
wallematthias Oct 10, 2026
6f0a25d
Run FEA CI with Python 3.10 and VTK 9 dependencies
wallematthias Oct 10, 2026
898ffb7
Declare QC image dependencies and simplify CI environment solving
wallematthias Oct 10, 2026
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: 6 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ jobs:
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest]
python-version: [3.7]
os: [ubuntu-latest, macos-15-intel]
python-version: ["3.10"]

name: ${{ matrix.os }}, Python ${{ matrix.python-version }}

Expand All @@ -37,7 +38,7 @@ jobs:
python-version: ${{ matrix.python-version }}
miniconda-version: "latest"
activate-environment: test
channels: numerics88,simpleitk,conda-forge
channels: numerics88,conda-forge
auto-activate-base: false
- uses: actions/cache@v3
id: conda-env-cache
Expand All @@ -47,11 +48,11 @@ jobs:
- name: Create and install dependencies
if: steps.conda-env-cache.outputs.cache-hit != 'true'
run: |
conda install -n test n88tools click flake8 matplotlib nose numpy opencv pandas pbr scipy simpleitk six sphinx pydicom gdcm "setuptools<65.6"
conda install -y -n test --override-channels -c numerics88 -c conda-forge vtkbone "vtk>=9.2" "numpy<2" pandas nibabel pyyaml netcdf4 pbr pytest wheel "setuptools<65.6"
- name: Install package
run: |
conda activate test
pip install -e .
- name: Run tests
run: |
nosetests tests
python -m pytest tests
8 changes: 8 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ which was presented at the ASBMR in 2018 and published in 2020:
For further information about the Bone Imaging Laboratory see https://bonelab.ucalgary.ca.
Please contact us for further questions, comments or interest in contributing to this project.

Finite Element Modelling
========================

For hip and spine models, start with the `FEA run guide <ogo/fea/README.md>`_.
The `vertebra quickstart <docs/fea/vertebra_fea_quickstart.md>`_ covers installation
and a first L1 model; the `code map <docs/fea/implementation.md>`_ locates the
registration, material laws, supports and QC functions.

Install
=======

Expand Down
51 changes: 51 additions & 0 deletions docs/fea/implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# FEA code map

Start at `ogo/cli/GenerateFEM.py`. It parses `ogoFEA`, calls the anatomy builder,
audits the model, runs FAIM/N88 and writes modelling metadata.

| Task | File / function |
| --- | --- |
| CLI and site arguments | `GenerateFEM.build_parser`, `build_spine_command`, `build_femur_command` |
| Reporting endpoint and criteria | `GenerateFEM.solve_report_profile`, `critical_volume_percent`, `critical_strain` |
| Spine preparation and model | `spine._prepare_spine_inputs`, `_register_spine_inputs`, `process_vertebra` |
| Hip preparation and model | `femur._prepare_femur_inputs`, `_register_femur_inputs`, `sidewaysFallFe` |
| Hip multistart alignment | `alignment/hip.py: estimate_femur_icp` |
| Physical scan-end/shaft measurements | `shaft_geometry.capture_distal_scan_face`, `measure_available_shaft`, `verify_model_shaft` |
| Shared transforms | `alignment/`, `image_io.py` |
| PMMA/contact geometry | `boundary.generate_bone_cap_mask`, `generate_projected_material_disk_vtk` |
| Density laws and material tables | `material_laws.py`, `materials.build_bone_pmma_material_table` |
| N88 mesh and file writing | `model.create_microfe_model`, `write_model` |
| Solver discovery, Pistoia and results | `ogo/util/faim.py` |
| Model construction record | `metadata.py` |
| Generation measurements / QC rules | `qc/validation.py: measure_model`, `evaluate` |
| Legacy measurement recovery | `qc/legacy.py: backfill_measurements` |
| Three-view previews | `qc/render.py`, using `ogo/cli/Visualize.py` |
| Gallery and review records | `qc/gallery.py`, `qc/review.py`, `qc/gallery_review.js` |

Anatomy defaults are constants at the top of `spine.py` and `femur.py`.
Shared helpers own geometry/material logic; the CLI owns solving and reporting.
Changing a reporting criterion must not silently change model construction.
Hip supports only the GT-relative flat shaft crop; the initial fixed-length crop
is used for registration only. Spine settings are explicit keyword arguments;
misspelled setting names raise an error before reading inputs.

Spine cleans the body label, crops, estimates centroid-initialized ICP, resamples,
builds caps/materials and applies compression. Hip captures the original distal
scan face, rough-crops for registration, transforms the full scan, builds proximal
supports, checks available coverage, then crops and constrains the shaft.
Reference scaling changes the registration target, not native bone dimensions.
The modelling JSON records spine label smoothing and resolved contact settings.

For hip, the saved-model length check is
`min(z of Greater_Trochanter_PMMA_Nodes) - median(z of Distal_Femur_Nodes)`.
Coverage also accounts for the most proximal transformed scan-end point and
resampling clearance, so the final face must be complete, not an oblique remnant.

QC measurements are written during generation; validation reads these CSVs
without reopening models. Legacy recovery uses registered anatomy and saved
shaft geometry where available, recording what was recovered. Manual inclusion
is separate from the automatic construction checks.

Tests are in `tests/fea/` and `tests/util/`; run focused tests for the module
being edited. Imaging/mesh tests require vtkbone and SimpleITK. Document changes
to defaults and preserve their matching CLI/provenance tests.
66 changes: 66 additions & 0 deletions docs/fea/vertebra_fea_quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# L1 FEA quickstart

## Install

Install Git and conda first, then:

```bash
git clone https://github.com/Bonelab/Ogo.git
cd Ogo
git checkout ogo-fea
conda create -n ogo -c numerics88 -c conda-forge \
git pandas nibabel pyyaml vtkbone netcdf4 pbr "setuptools<69" wheel python=3
conda activate ogo
PBR_VERSION=0.0.1 python -m pip install -e .
ogoFEA --help
```

`PBR_VERSION` supplies installation metadata only. The Numerics88 channel
provides vtkbone; solving additionally requires FAIM/N88 tools and a license.
Use tools already on `PATH`, or add `--faim_bin_dir /path/to/n88/bin` to commands.
A separate solver conda environment can be selected with `--faim_env NAME`.

## Generate and inspect

Use density-calibrated QCT in mg/cm3 K2HPO4-equivalent units and a segmentation
in the same physical space. This example uses body label 20 and process label 48;
replace these with your dataset's labels. NIfTI geometry must preserve anatomical
orientation, with physical z along the superior-inferior axis.

```bash
ogoFEA spine qct.nii.gz labels.nii.gz --vertebra L1:20:48 \
--output_path derivatives/fea --threads 4 --no-solve
```

Inspect `*_qc_3d.webp`: the body should be upright, processes posterior, and
both PMMA caps in contact with the body. Check `*_bc_audit.json` and
`*_qc_metrics.csv`. Registration/segmentation failures should be resolved
before interpreting mechanical results.

## Solve and report both regions

```bash
ogoFEA spine qct.nii.gz labels.nii.gz --vertebra L1:20:48 \
--pistoia_mask_label 20 --critical_volume 12 --masked_critical_volume 35 \
--critical_strain 0.007 --output_path derivatives/fea --threads 4
```

This reports reaction force at 0.68%, stiffness, full-bone Pistoia at 12%
critical volume, and body-only Pistoia at 35%, all in `*_results.csv`.
Critical volume is a percentage of evaluated bone; critical strain 0.007 means
0.7% effective strain. These criteria must be justified for the intended study.
The model-space ROI is saved as `*_pistoia_mask.nii.gz`.

For a separate binary body mask, replace `--pistoia_mask_label 20` with
`--pistoia_mask body_mask.nii.gz`. The full-bone result is still included.
For full-bone Pistoia alone, omit the ROI and add `--run_pistoia`.

## Review a cohort

```bash
ogoValidateFEA derivatives/fea --output derivatives/qc --gallery-zip --workers 4
```

Extract `gallery.zip`, open `gallery.html`, review and export the inclusion CSV.
Browser decisions do not overwrite project files. See the
[workflow guide](../../ogo/fea/README.md) for defaults, outputs and legacy models.
14 changes: 9 additions & 5 deletions ogo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
'__version__',
'version_info',
)
from pbr.version import VersionInfo

_v = VersionInfo('ogo').semantic_version()
__version__ = _v.release_string()
version_info = _v.version_tuple()
try:
from pbr.version import VersionInfo
except ImportError:
__version__ = '0+unknown'
version_info = (0,)
else:
_v = VersionInfo('ogo').semantic_version()
__version__ = _v.release_string()
version_info = _v.version_tuple()
Loading
Loading