Metadata-Version: 2.4
Name: girder-dashboards-porosity-modeling
Version: 0.1.0
Summary: Porosity modelling for laser powder bed fusion metals, as a girder-dashboards dashboard
Author: Kacper Kowalik
License: BSD-3-Clause
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: girder-dashboards>=0.2
Requires-Dist: girder-jobs>=5
Requires-Dist: girder-worker>=5
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: porosity_modeling>=0.1.0
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# girder-dashboards-porosity-modeling

Porosity modelling for laser powder bed fusion metals, as a dashboard for
[girder-dashboards](https://github.com/Xarthisius/girder-dashboards).

It is the web front end of [PorosityModel](https://github.com/imqcam/PorosityModel) —
the modular lack-of-fusion / keyhole / gas pore framework from CMU's EMIT Lab — and
nothing else. Every number it reports comes out of
`porosity_modeling.execute_porosity_model`, called with the arguments that
project's Dash GUI assembled. This package replaces the GUI; it does not
reimplement the science.

| | Dash GUI | This dashboard |
|---|---|---|
| Measured data | a path in a text box (`data/`), downloaded from Figshare when absent | two Girder items, configured by a site admin — or your own CSVs, uploaded per run |
| Material files | files inside the wheel | a curated Girder folder, plus the wheel's own |
| Parameter form | one flat sorted list of every field | two groups: process parameters, and the properties the melt pool model requires |
| Result | a PNG of a matplotlib figure | an interactive plot of the same numbers |
| Outputs | files in a directory on the server | a Girder folder per run, shared and downloadable like any other data |
| Concurrency | one process, one run | a Celery job per run, with an in-process fallback |

## Installing

```bash
pip install girder-dashboards-porosity-modeling
girder build                      # or restart Girder
```

Then enable it at `#plugins/dashboards/config` — like every dashboard, it is
provisioned **disabled**.

### Installing `porosity_modeling`

`porosity_modeling` is a hard requirement and is **not on PyPI**:
`imqcam/PorosityModel` is a private repository whose CI publishes the wheel as the
`porosity-modeling-dist` artifact of its `ci.yml` run (kept 90 days; tagged
releases also attach it to the GitHub release). Making that wheel resolvable is
the installer's job, and two ways are supported:

**1. A wheel directory (what a Docker build should do).** Copy the wheel into the
image and point pip at it, so the ordinary `pip install` of this package resolves
the dependency like any other:

```dockerfile
COPY porosity_modeling-*.whl /wheels/
ENV PIP_FIND_LINKS=/wheels
RUN pip install girder-dashboards-porosity-modeling
```

This is the recommended shape because it survives a plain `pip install -e .` — the
form deployment tooling such as `wholetale-docker/build_plugins.sh` uses — with
nothing extra to remember.

**2. Install the wheel first.**

```bash
pip install ./porosity_modeling-0.1.0-py3-none-any.whl
pip install girder-dashboards-porosity-modeling
```

Either way it has to happen in **both** the Girder environment and the Celery
worker environment. If it is missing, the dashboard says so on a banner and every
endpoint returns 503 rather than a run failing on an `ImportError` a minute in.

## Configuring

Settings live on the dashboard document and are edited from the gear on the
dashboard card (`#plugins/dashboards/config`).

| Setting | What it is |
|---|---|
| `poreDataItemId` | Girder item holding `poredf.csv` (or `poredf.csv.gz`) — one row per pore the X-µCT measured |
| `parametersItemId` | Girder item holding `parameters.csv` — one row per measured coupon |
| `materialFolderId` | Girder folder of material property files, one JSON item each |
| `exampleParamsItemId` | Girder item holding a process-parameter JSON for "Load example case". Falls back to the wheel's `params.json` |
| `defaultTotalPores` | Pre-filled sample count (10000) |
| `maxTotalPores` | Ceiling on one run (200000) |
| `defaultPoreModels` | Pore sources ticked on arrival (`["lof","keyhole","gas"]`) |
| `defaultMeltpoolModel` | Melt pool model pre-selected (`"Rosenthal"`) |

These are editable from the dashboard itself: an admin sees an **Instance dataset**
panel at the foot of the page with, for each setting, a Girder browser to pick an
existing item and an *Upload…* button to put a table there from their own machine —
into a public `Porosity Modeling Data` folder, because read access is checked against
whoever runs the model, so a private one would work for the admin and be missing for
everyone else. Plus a *Check the dataset* button that reports whether the pair can actually be used — including the
`Geometry` values the coupons contain and the (Power, Velocity) range they cover,
which are otherwise unknowable and are each a silent failure when a run gets them
wrong. The raw-JSON editor on the plugin config page still works and still reaches
the settings this panel does not edit.

Nothing has to be configured for the dashboard to work: with no dataset items it
offers only the upload-your-own path, and the wheel's bundled material models are
always listed. The datasets are the two committed in the PorosityModel repository
under `data/`; upload them into two items and name the items here.

The two tables are a **matched pair**, joined on their coupon `Name` column: the
pore table says which coupon each pore came from, and the coupon table says what
that coupon was built at and how much of it was inspected. Half of one dataset and
half of another share no coupon names, match nothing, and the run fails with "All
pore densities are zero" — which is why the dashboard takes both from one place or
both from the run folder, never one of each.

Neither is the *process parameters* of the build being predicted. Those are a form
field, one build's worth, looked up against the coupon table; `parameters.csv` is
the 61-coupon table looked up *in*. They share five column names, which is the whole
reason the distinction needs stating.

### Material items

An item in `materialFolderId` is offered when its JSON supplies every property the
selected melt pool model's `getpropnames()` names — compatibility is computed from
the keys, never from the file being called `*_rosenthal.json`. Two pieces of item
metadata are read when present:

| Key | Effect |
|---|---|
| `materialName` | Selecting the material also fills in the **Material** process parameter, which the bundled files cannot do — `"Material": "Ti6Al4V"` is a process parameter and is not in `ti64_rosenthal.json` |
| `description` | Shown with the material |

## A run

A run is a Girder folder in the user's own space, so it is shared, ACL'd and
deleted like any other data:

```
<user>/Porosity Modeling/
    2026-07-27 15-51-32/
        poredf.csv            only when the user uploaded their own
        parameters.csv        likewise
        params.json           these four are exactly what a `porosity-model`
        mp_params.json        CLI run writes into its output directory
        samples.csv
        number_density.pdf
        results.json          the numbers the dashboard plots
        run.log               everything the model printed
```

`number_density.pdf` is kept even though the dashboard draws its own chart: a run
folder should be the same artifact a CLI run produces. `results.json` carries the
output of the same `visualization.ndplot.get_number_density` call the PDF is drawn
from, so the plot on screen and the figure in the file are one calculation, not
two that ought to agree.

## Execution

The run is a Celery task on the `local` queue. Where no worker is consuming it,
the same code runs in a daemon thread of the Girder process instead — so a plain
`girder serve` is a working deployment. Both produce an ordinary Girder job, and
the dashboard cannot tell which happened.

The worker needs HTTP access to Girder and nothing else: both reference tables
arrive through `GirderFileId` transforms and every output leaves through
`girder_client`. It does need `girder-dashboards-porosity-modeling` and the
`porosity_modeling` wheel installed.

## REST API

Under `/api/v1/porosity`. Every route is `@access.user` and checks the dashboard's
own ACL, so disabling the dashboard disables the functionality rather than merely
hiding the card.

| Route | Purpose |
|---|---|
| `GET /porosity/capability` | dependency probe, worker availability, settings, model lists, whether the configured dataset is usable |
| `GET /porosity/parameters` | the fields a combination of models requires, partitioned into process and material |
| `GET /porosity/material` | the material library for a melt pool model, values inline, each flagged compatible or not |
| `GET /porosity/example` | the example process parameters |
| `GET`/`POST /porosity/run` | list runs; create a run folder |
| `GET`/`DELETE /porosity/run/{id}` | run state; delete the folder |
| `POST /porosity/run/{id}/execute` | schedule the run; returns the job |

## Development

```bash
(cd girder_dashboards_porosity_modeling/web_client && npm ci && npm run build)
pip install -e .
pytest girder_dashboards_porosity_modeling/tests
ruff check .
```

**Build the web client before running the Python tests.** `load()` calls
`registerPluginStaticContent`, which md5-hashes every listed file, so with no
bundle in `web_client/dist` it raises `FileNotFoundError` and every test using the
`server` fixture errors out. The girder-dashboards bundle must exist too, since
loading this plugin loads that one.

## Licence

BSD-3-Clause. The research code it drives, `PorosityModel`, is MIT, © its authors:
Justin P. Miner and Sneha Prabha Narra, Carnegie Mellon University. Please cite the
X-µCT datasets listed in that repository's README if you use the reference data.
