Phenotype Representation for Interpretable Syndrome Mapping
PRISM is a statistical investigation of facial phenotype variation across rare-disease cohorts using 120 geometric measurements supplied by FaceKit. A pandas/SciPy analysis, FastAPI/SQLite backend and local dashboard support this study. Feature extraction is attributed to FaceKit; PRISM supplies the statistical investigation.
The active analysis uses 120 current FaceKit features: 1,845 selected GMDB patients, 3,409 FairFace reference image paths, 48 eligible syndromes, and 5,760 primary hypotheses. Global BH correction identifies 955 associations at q < 0.05. PDIDB synthetic images remain a separate extraction catalog. The configuration is in ACTIVE_ANALYSIS.json; the research record is preserved in a private read-only archive. See the current study workflow for provenance, validation, sensitivity analyses and the private report location.
PRISM is an exploratory research tool. Its results are not medical advice, a diagnosis, or a substitute for clinical evaluation.
- Architecture
- Requirements
- First-time setup
- Preparing input data
- Running the statistical analysis
- Statistical methods
- Analysis outputs
- Running the website
- Real data versus demo data
- Running the frontend and backend separately
- API
- Configuration
- Project structure
- Troubleshooting
- Database roadmap
- Acknowledgements
Face images / landmark data
│
▼
FaceKit
│ phenotype CSVs
▼
pandas + SciPy analysis
│ stats_results/*.csv
▼
FastAPI ─── SQLite
│ JSON API
▼
Next.js + React + Recharts
The application currently runs entirely on the local computer. No patient images or analysis data are uploaded by PRISM.
- macOS, Linux, or another Unix-like development environment
- Python 3.10 or newer
- Node.js 20 or newer
- npm
- Git
The current project has been tested with Python 3.11 and Node.js 24.
Run these commands from the PRISM directory:
cd /Users/joshua/Documents/PRISM
git submodule update --init --recursive
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip setuptools wheel
python -m pip install -r backend/requirements.txt
python -m pip install -e './FaceKit[all]'
cd frontend
npm install
cd ..The Python virtual environment lives in .venv/, and frontend packages live in
frontend/node_modules/. Both directories are ignored by Git.
Activate it in each new terminal before running Python commands:
source .venv/bin/activateExit it with:
deactivateActivation is optional if the environment's Python is called directly:
.venv/bin/python stats_testing/statistical_analysis.pyFor an editor's Run button, configure its Python interpreter as:
/Users/joshua/Documents/PRISM/.venv/bin/python
The default inputs are the validated private 120-feature cohorts resolved from
ACTIVE_ANALYSIS.json. Obsolete row-level inputs have been removed from data/
and preserved in private read-only archives. The condition-label taxonomy remains
because it supports the current study. If authorized current inputs are
missing, the pipeline stops; it does not substitute historical measurements.
Explicit input overrides must match the ordered current 120-feature schema. Keep per-person measurements and metadata in an ignored private directory.
Both files should have:
- A
frontal_okcolumn. Only rows equal toTrueare analyzed. - The same numeric FaceKit phenotype columns.
- At least the configured minimum number of valid observations per group.
- An optional
image_idcolumn.
The disease CSV may have a disease column containing individual cohort names.
If it does not, every row is assigned to one generic disease group. Any
existing disease label in the healthy CSV is replaced with healthy.
A combined file must contain a disease column, and healthy rows must use the
exact label healthy.
FaceKit is installed as the local FaceKit/ submodule. See its available
commands with:
source .venv/bin/activate
facekit --help
facekit extract-features --helpSee FaceKit/README.md for its full extraction workflow.
The analysis reads its current inputs and schema from ACTIVE_ANALYSIS.json.
To reproduce the primary analysis in a new private output directory, run:
cd /Users/joshua/Documents/PRISM
source .venv/bin/activate
python stats_testing/statistical_analysis.py --output-dir local_review/current_120_reproductionThis also works with an editor's Run button when the editor uses the .venv
interpreter.
Command-line arguments may select other authorized current-schema inputs:
python stats_testing/statistical_analysis.py \
--healthy-csv local_review/current_inputs/reference_120.csv \
--disease-csv local_review/current_inputs/disease_120.csv \
--output-dir local_review/current_120_customFor a combined file:
python stats_testing/statistical_analysis.py \
--input-csv local_review/current_inputs/combined_120.csv \
--output-dir local_review/current_120_combinedpython stats_testing/statistical_analysis.py \
--output-dir local_review/current_120_custom_settings \
--bootstrap-iterations 1000 \
--rank-bootstrap-iterations 500 \
--max-missing-fraction 0.20 \
--min-group-n 15 \
--effect-threshold 0.50 \
--kw-effect-threshold 0.06 \
--mad-threshold 5 \
--top-k 10Defaults:
| Setting | Default | Meaning |
|---|---|---|
bootstrap-iterations |
500 | Resamples used for effect-size confidence intervals |
rank-bootstrap-iterations |
200 | Resamples used to estimate ranking stability |
bootstrap-seed |
2026 | Random seed for reproducible bootstraps |
max-missing-fraction |
0.30 | Maximum allowed missing fraction in either group |
min-group-n |
10 | Minimum valid observations per group |
effect-threshold |
0.50 | Minimum absolute Hedges' g for high priority |
kw-effect-threshold |
0.06 | Minimum Kruskal-Wallis epsilon-squared |
mad-threshold |
5 | Scaled MAD cutoff for the robust analysis |
top-k |
10 | Number of positions used for rank stability |
Larger bootstrap settings improve precision but increase runtime substantially, especially when the disease CSV contains many cohorts.
The script performs three main analyses:
- All disease rows combined versus reference rows.
- Every eligible disease cohort versus reference rows.
- Kruskal-Wallis comparison across all sufficiently large groups.
The result tables include:
- Group counts, means, medians, and missingness.
- Mann-Whitney U tests and Benjamini-Hochberg FDR correction.
- Welch's t-test for the combined disease comparison.
- Cohen's d and small-sample-corrected Hedges' g.
- Cliff's delta and rank-biserial correlation.
- Bootstrap 95% confidence intervals for Hedges' g and Cliff's delta.
- Directional and direction-independent univariate ROC AUC.
- A robust reanalysis after removing values beyond the configured scaled-MAD threshold independently within each group.
- Bootstrap ranking stability: the fraction of bootstrap runs in which a feature appears in the top K by absolute Hedges' g.
Each two-group result also receives a Phenotype Difference Score from 0 to 100:
PDF = 100 × (0.45E + 0.20S + 0.25B + 0.10N)
Its components are:
E = min(|Hedges' g| / 1.5, 1)
S = min(-log10(FDR q) / 5, 1)
B = fraction of effect-size bootstrap samples with the observed direction
N = min(min(n_disease, n_healthy) / 50, 1)
Effect magnitude receives the greatest weight. The output includes the four
individual pdf_*_component columns, the bootstrap direction-stability value,
and the final phenotype_difference_score_pdf, making the calculation fully
auditable. PDF is not calculated when bootstrapping is disabled because its
direction-stability component is unavailable.
A two-group result is marked high_priority when:
global FDR q < 0.05 and |Hedges' g| >= 0.5
The Kruskal-Wallis high-priority rule is:
FDR q < 0.05 and epsilon-squared >= 0.06
Feature specificity is calculated as:
1 - (diseases with a high-priority effect / diseases tested)
The specificity-weighted effect score multiplies that specificity by the median absolute Hedges' g among qualifying diseases. This prevents a feature with no supported effects from appearing valuable merely because its raw specificity is 1.
The analysis creates stats_results/ automatically and writes:
| File | Contents |
|---|---|
stats_healthy_vs_disease.csv |
Complete combined disease-versus-healthy results |
stats_each_disease_vs_healthy.csv |
Complete disease-specific results |
stats_kruskal_all_groups.csv |
Complete multi-group results |
significant_healthy_vs_disease.csv |
Combined results with FDR q below 0.05 |
significant_each_disease_vs_healthy.csv |
Disease-specific results with global FDR q below 0.05 |
significant_kruskal_all_groups.csv |
Multi-group results with FDR q below 0.05 |
high_priority_healthy_vs_disease.csv |
Significant combined results meeting the effect threshold |
high_priority_each_disease_vs_healthy.csv |
Significant disease-specific results meeting the effect threshold |
high_priority_kruskal_all_groups.csv |
Significant multi-group results meeting the effect threshold |
feature_reliability.csv |
Valid counts, missing fractions, and eligibility by feature and group |
feature_rank_stability.csv |
Top-K bootstrap frequencies |
feature_specificity.csv |
Disease counts, specificity, and specificity-weighted effects |
analysis_configuration.csv |
Exact settings used for the run |
After generating statistics, start both services with:
cd /Users/joshua/Documents/PRISM
./scripts/dev.shOpen:
- Dashboard: http://localhost:3000
- FastAPI documentation: http://localhost:8000/docs
- API health check: http://localhost:8000/api/health
- Data-source summary: http://localhost:8000/api/summary
Stop both services with Control-C in the terminal running dev.sh.
At startup, the backend imports available stats_results CSVs into the ignored
SQLite file backend/data/prism.db.
If no analysis CSVs are available, the backend inserts a small illustrative dataset so the interface can be previewed. Those records are fake and must not be interpreted as scientific results. The dashboard displays a yellow demo banner in this state.
Check the current source at http://localhost:8000/api/summary:
{
"data_source": "demo",
"is_demo": true
}After real results are imported, those fields become:
{
"data_source": "analysis",
"is_demo": false
}To replace demo data:
- Configure the healthy and disease files.
- Run
stats_testing/statistical_analysis.py. - Confirm CSVs exist in
stats_results/. - Restart
./scripts/dev.sh.
The backend clears demo records and imports the analysis automatically. While the backend is already running, results can instead be reloaded with:
curl -X POST http://localhost:8000/api/admin/reloadUse two terminals when separate logs or debugging are helpful.
cd /Users/joshua/Documents/PRISM
source .venv/bin/activate
cd backend
uvicorn app.main:app --reload --port 8000cd /Users/joshua/Documents/PRISM/frontend
npm run devThe frontend expects the backend at http://localhost:8000 by default.
FastAPI provides interactive OpenAPI documentation at http://localhost:8000/docs.
Main endpoints:
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/api/health |
Server health check |
GET |
/api/summary |
Counts, average AUROC, and data-source status |
GET |
/api/diseases |
Available disease labels |
GET |
/api/results |
Filtered and sorted statistical results |
GET |
/api/specificity |
Ranked feature-specificity results |
GET |
/api/features/{feature} |
All results for one feature |
POST |
/api/admin/reload |
Re-import CSVs from stats_results/ |
Example queries:
curl 'http://localhost:8000/api/results?high_priority=true&limit=20'
curl --get \
--data-urlencode 'disease=Noonan syndrome' \
--data-urlencode 'sort_by=roc_auc' \
--data-urlencode 'descending=true' \
'http://localhost:8000/api/results'For a backend running somewhere other than port 8000:
cd frontend
cp .env.example .env.localThen edit .env.local:
NEXT_PUBLIC_API_URL=http://localhost:8000
Restart the frontend after changing environment variables.
The local backend currently permits frontend requests from:
http://localhost:3000http://127.0.0.1:3000
Update allow_origins in backend/app/main.py before hosting the frontend at a
different origin.
PRISM/
├── FaceKit/ # Git submodule and feature extractor
├── backend/
│ ├── app/
│ │ ├── database.py # SQLite connection and schema
│ │ ├── import_results.py # CSV normalization/import and demo seed
│ │ └── main.py # FastAPI routes
│ ├── data/prism.db # Generated local DB; ignored by Git
│ └── requirements.txt
├── data/ # Current extraction code and condition-label taxonomy
├── frontend/
│ ├── app/ # Next.js app and global styles
│ ├── components/ # Dashboard, cards, and charts
│ ├── lib/api.ts # Typed API client
│ └── package.json
├── scripts/dev.sh # Starts frontend and backend together
├── stats_results/ # Generated analysis CSVs
└── stats_testing/
└── statistical_analysis.py # Statistical pipeline
Set both DISEASE_FILE and HEALTHY_FILE near the top of
stats_testing/statistical_analysis.py, or provide both command-line flags.
Check the spelling and location. Relative paths are resolved from the PRISM
project directory, not from stats_testing/.
Activate the environment and reinstall Python requirements:
source .venv/bin/activate
python -m pip install -r backend/requirements.txt
python -m pip install -e './FaceKit[all]'cd frontend
npm installIf the shared npm cache has a permissions problem, use a temporary cache:
npm install --cache /tmp/prism-npm-cacheStop the existing development process with Control-C, or start services on
different ports. If the backend port changes, update frontend/.env.local.
Confirm that stats_results/stats_each_disease_vs_healthy.csv exists, restart
the backend, and inspect http://localhost:8000/api/summary. You can also call
the reload endpoint described above.
Bootstrap work scales with the number of cohorts, features, and iterations. For a quick validation run, lower the iteration counts:
python stats_testing/statistical_analysis.py \
--bootstrap-iterations 50 \
--rank-bootstrap-iterations 25Use the larger defaults or higher values for final research results.
SQLite is appropriate for the current local, single-user application. The API and CSV import layer keep storage concerns isolated so the project can later move to PostgreSQL or Supabase. That migration should add schema migrations, environment-based credentials, authentication, row-level security, and a clear policy for sensitive facial or medical data.
- Add feature and disease detail pages.
- Add downloadable filtered result tables.
- Add analysis-run versioning and provenance.
- Add authenticated multi-user support with PostgreSQL/Supabase.
- Consider an opt-in FaceKit image workflow only after privacy, consent, retention, and clinical-safety requirements are defined.
- Train a neural network using PDS score/implement on new input images? (Each disease might have a PDS vector signature (signed))
- add an example photo fro each disease as well as where the top features are on that photo
PRISM builds upon FaceKit, an open-source toolkit for rare-disease facial phenotype analysis developed by Hongzhuo Chen at the University of Pennsylvania.
I gratefully acknowledge Hongzhuo Chen and Dr. Kai Wang of the Children's Hospital of Philadelphia for their guidance and support throughout the development of this project.
I acknowledge the GestaltMatcher Database (GMDB) and FairFace as the image data resources used in this work.
I extend my sincere appreciation to the individuals, patients, and families whose contributions make this research possible. Use of these resources remains subject to their respective licenses, consent terms, data-use agreements, and citation requirements.
I also thank the maintainers and contributors of the open-source software on which PRISM depends, including FaceKit, Python, pandas, NumPy, SciPy, statsmodels, FastAPI, SQLite, React, Next.js, and Recharts.