Metadata-Version: 2.4
Name: PyGlaucoMetrics
Version: 1.0.4
Summary: Visual field analysis for glaucoma without R dependencies
Author-email: Mousa Moradi <mmoradi2@meei.harvard.edu>, Mohammad Eslami <mohammad_eslami@meei.harvard.edu>, Bharath Erusalagandi <mmoradi2@meei.harvard.edu>
License: MIT
Project-URL: Homepage, https://github.com/Mousamoradi/PyGlaucoMetrics
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: numpy
Requires-Dist: pandas
Requires-Dist: matplotlib
Requires-Dist: scipy
Requires-Dist: PyQt5>=5.15
Requires-Dist: Pillow
Requires-Dist: seaborn
Requires-Dist: pingouin
Requires-Dist: requests
Requires-Dist: PyVisualFields
Provides-Extra: windows
Requires-Dist: pywin32; extra == "windows"

# PyGlaucoMetrics

**PyGlaucoMetrics** is an open-source, pure-Python package for glaucoma detection and visual field (VF) analysis — no R or rpy2 dependency required. It accepts Humphrey Field Analyzer (HFA) 24-2 test patterns and provides a full pipeline from raw VF data to ensemble glaucoma classification, severity staging, and progression analysis, with an interactive GUI.

[![PyPI version](https://badge.fury.io/py/PyGlaucoMetrics.svg)](https://pypi.org/project/PyGlaucoMetrics/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Mousamoradi/PyGlaucoMetrics/blob/main/LICENSE)
[![Python >=3.8](https://img.shields.io/badge/python-%3E%3D3.8-blue)](https://www.python.org/)

---

## Overview

PyGlaucoMetrics is designed as an accessible GUI application and Python library that implements selected visual field analysis components from [PyVisualFields](https://pypi.org/project/PyVisualFields/). It integrates glaucoma classification criteria, HAP2 severity staging, progression analysis, and an interactive visualization interface.

> **Note:** PyVisualFields is the underlying developer-facing library. PyGlaucoMetrics builds upon it to provide an end-user GUI and glaucoma-specific classification pipeline.

---

## Features

- **Pure Python** — all computations via `numpy`, `scipy`, and `pandas`; no R required
- **Automatic column standardization** — any CSV with any column naming convention is accepted; columns are automatically mapped to canonical names (`patientid`, `eyeid`, `l1`–`l54`, `td*`, `pd*`, etc.)
- **Missing block computation** — if TD, PD, TDP, or PDP values are missing, they are computed automatically from sensitivity data
- **Dataset summary popup** — on load, a summary window shows which data blocks and global indices are present or missing
- **Demo dataset** — download the UW HVF dataset directly from within the GUI (no manual download needed)
- **Five glaucoma classifiers** with GL/Non-GL labels and human-readable reasons:
  - HAP2 (Part 1: classification + Part 2: Early/Moderate/Severe staging)
  - UKGTS
  - LoGTS
  - Foster (requires GHT column)
  - Kang's
- **AUC-weighted ensemble** decision (weights from published Table 2, Moradi et al. 2025)
- **Six-panel VF visualization** — Sensitivity, TD Probability, PD Probability (row 1) + Sensitivity dB, TD Deviation, PD Deviation (row 2)
- **Progression analysis** — CIGTS, PLR Nouri 2012, VFI, Schell 2014, AGIS, Global Linear Regression (GLR), Pointwise Linear Regression (PLR)
- **Save results** — exports classifier CSVs, HAP2 severity bar chart, ensemble bar chart, and VF plots

---

## Installation

**Windows:**
```
pip install PyGlaucoMetrics[windows]
```

**Linux / macOS:**
```
pip install PyGlaucoMetrics
```

---

## Quick Start

### Launch the GUI

**Option 1 — Console script (recommended):**
```
pyglaucometrics-gui
```

**Option 2 — From Python:**
```python
from PyVisualFields.gui_pygl import main
main()
```

**Option 3 — Run the script directly:**
```
python gui_pygl.py
```

> **Note on import:** PyGlaucoMetrics is accessed as a submodule of PyVisualFields:
> ```python
> from PyVisualFields import PyGlaucoMetrics
> ```

### Use classifiers in Python
```python
import pandas as pd
from PyVisualFields import visualFields, vfprogression
from PyVisualFields import PyGlaucoMetrics
from PyVisualFields.utils import (
    canonicalize_vf_df, compute_missing_blocks,
    vf_blocks, missing_blocks, print_vf_summary
)

# 1. Load and standardize (any column naming convention accepted)
df = pd.read_csv('VF_Data.csv')
df = canonicalize_vf_df(df)           # standardize column names

# Check what blocks are present
print(vf_blocks(df))          # e.g. {'sens': True, 'td': True, ...}
print(missing_blocks(df))     # e.g. ['tdp', 'pdp']
print_vf_summary(df)          # human-readable summary with ✓/✗

# 2. Compute missing blocks (TD, PD, TDP, PDP) and global indices
df = compute_missing_blocks(df)
df_gi = visualFields.getgl(df)          # MD, PSD, VFI, GH, etc.
combined = pd.concat([df, df_gi], axis=1)
final_df = combined.loc[:, ~combined.columns.duplicated()]

# 3. Run classifiers
df_hap2   = PyGlaucoMetrics.Fn_HAP2(final_df)
df_hap2   = PyGlaucoMetrics.Fn_HAP2_part2(df_hap2)
df_ukgts  = PyGlaucoMetrics.Fn_UKGTS(final_df)
df_logts  = PyGlaucoMetrics.Fn_LoGTS(final_df)
df_foster = PyGlaucoMetrics.Fn_Foster(final_df)   # requires GHT column
df_kangs  = PyGlaucoMetrics.Fn_Kangs(final_df)

# 4. View results
print(df_hap2[['l1', 'l2', 'HAP2_clf', 'HAP2_reason']].head())
# Output:
#    l1  l2 HAP2_clf                                        HAP2_reason
# 0  26  28   Non-GL
# 3  24  25       GL  GHT outside; cluster of 3 PDP points < 5% with...

print(df_hap2[['l1', 'l2', 'HAP2_clf', 'severity(HAP2_part2)']].head())
# Output:
#    l1  l2 HAP2_clf severity(HAP2_part2)
# 0  26  28   Non-GL               Early
# 3  24  25       GL               Early

print(df_ukgts[['l1', 'l2', 'UKGTS_clf', 'UKGTS_reason']].head())
# Output:
#    l1  l2 UKGTS_clf                                       UKGTS_reason
# 3  24  25        GL  Cluster of 2 TDP points < 1%; Cluster of 3 TDP...

print(df_foster[['l1', 'l2', 'Foster_clf', 'Foster_reason']].head())
# Output:
#    l1  l2 Foster_clf                                 Foster_reason
# 3  24  25         GL  GHT outside AND cluster of 3 PDP points < 5%

print(df_kangs[['l1', 'l2', 'Kangs_clf', 'Kangs_reason']].head())
# Output:
#    l1  l2 Kangs_clf                         Kangs_reason
# 4  26  26        GL  Cluster of 3 points with TD < -5 dB
```

---

## GUI Workflow

1. Click **Load Dataset** — choose between:
   - **📥 Use UW Demo Dataset** — downloads automatically from the UW HVF repository
   - **📂 Load My Own CSV** — select any CSV file from disk
2. A **Dataset Summary** popup shows which blocks (sensitivity, TD, PD, TDP, PDP) and global indices (MD, PSD, GHT, VFI, etc.) are present or missing
3. Missing blocks are computed automatically
4. Enter an **index** (row number) to view VF plots and classifier predictions
5. Click **💾 Save Results** to export all outputs

---

## Input Format

Any CSV is accepted. Column names are automatically standardized. The following aliases are recognized:

| Canonical name | Accepted aliases |
|---|---|
| `patientid` | `id`, `patient_id`, `mrn`, `subjectid`, `pid` |
| `eyeid` | `eye`, `eye_id`, `laterality` |
| `date` | `examdate`, `testdate`, `vfdate` |
| `l1`–`l54` | `s1`–`s54`, `sens1`–`sens54`, `sensitivity1`–`sensitivity54` |
| `td1`–`td54` | `td1`–`td54` |
| `pdp1`–`pdp54` | `pdp1`–`pdp54` |
| `md` | `mtd` |
| `ght` | `ght` |

Eye values (`OD`/`OS`) are also normalized from `right`/`left`, `R`/`L`, `1`/`0`.

---

## Classifiers

| Classifier | Input | Criterion | Output columns |
|---|---|---|---|
| **HAP2 Part 1** | PD probabilities | Cluster of 3 PDP points < 5% with ≥1 < 1%; or GHT outside; or PSD p < 5% | `HAP2_clf`, `HAP2_reason` |
| **HAP2 Part 2** | Sensitivity + PDP + MD | Early / Moderate / Severe per HAP2 reference criteria (applied to all rows; Non-GL rows receive a severity based on their field characteristics) | `severity(HAP2_part2)` |
| **UKGTS** | TD probabilities + TD values | ≥2 TDP points < 1%; or ≥3 < 5%; or nasal step ≥10 dB | `UKGTS_clf`, `UKGTS_reason` |
| **LoGTS** | TD values | Cluster of 2 TD < −10 dB; or 3 TD < −8 dB | `LoGTS_clf`, `LoGTS_reason` |
| **Foster** | PD probabilities + GHT | GHT outside normal limits AND cluster of 3 PDP < 5% | `Foster_clf`, `Foster_reason` |
| **Kang's** | TD values | Cluster of 3 TD < −5 dB; or 2 TD < −10 dB | `Kangs_clf`, `Kangs_reason` |

### Ensemble Decision
Final GL/Non-GL label uses an **AUC-weighted ensemble** combining all five classifiers:

| Classifier | AUC weight |
|---|---|
| UKGTS | 0.815 |
| LoGTS | 0.821 |
| Foster | 0.675 |
| Kang's | 0.815 |
| HAP2 | 0.735 |

---

## HAP2 Severity Staging (Part II)

Based on Hodapp-Anderson-Parrish 2 (*Clinical Decisions in Glaucoma*):

| Stage | Criteria (all must be met for Early; any for Severe) |
|---|---|
| **Early** | MD > −6 dB AND no central 5° point < 15 dB AND ≤12 PDP points < 5% AND ≤4 PDP points < 1% |
| **Severe** | MD < −12 dB OR any central 5° point = 0 dB OR central < 15 dB in both hemifields OR ≥27 PDP points < 5% OR ≥14 PDP points < 1% |
| **Moderate** | Neither Early nor Severe |

Central 5° points: locations 23, 24, 32, 33 (1-based 24-2 HFA grid).

---

## Progression Analysis

| Method | Function | Min visits | Required columns |
|---|---|---|---|
| CIGTS | `progression_cigts()` | 5 | `tdp1`–`tdp54`, `eyeid` |
| PLR Nouri 2012 | `progression_plrnouri2012()` | 3 | `td1`–`td54`, `eyeid` |
| VFI | `progression_vfi()` | 5 | `vfi`, `eyeid` |
| Schell 2014 | `progression_schell2014()` | 5 | `td1`–`td54`, `eyeid` |
| AGIS | `progression_agis()` | 5 | `l1`–`l54`, `eyeid` |
| Global Linear Regression | `visualFields.glr()` | 2 | global indices |
| Pointwise Linear Regression | `visualFields.plr()` | 2 | `l1`–`l54` or `td*`/`pd*` |

Data is automatically sorted by date/age within each patient before progression analysis.

---

## Function Reference

### Data Utilities (`PyVisualFields.utils`)

| Function | Description |
|---|---|
| `canonicalize_vf_df(df)` | Standardize column names to canonical format |
| `canonicalize_vf_df(df, sort_byDateAge=True)` | Standardize and sort by date/age within patient |
| `compute_missing_blocks(df)` | Compute missing TD, PD, TDP, PDP blocks |
| `vf_blocks(df)` | Return dict of which blocks are present |
| `missing_blocks(df)` | Return list of missing block names |
| `investigate_vf_df(df)` | Return full summary dict of available data |
| `print_vf_summary(df)` | Print human-readable data summary |

### Glaucoma Classifiers (`from PyVisualFields import PyGlaucoMetrics`)

| Function | Description |
|---|---|
| `Fn_HAP2(df)` | HAP2 Part 1: GL/Non-GL with reason |
| `Fn_HAP2_part2(df)` | HAP2 Part 2: Early/Moderate/Severe severity |
| `Fn_UKGTS(df)` | UKGTS criterion: GL/Non-GL with reason |
| `Fn_LoGTS(df)` | LoGTS criterion: GL/Non-GL with reason |
| `Fn_Foster(df)` | Foster criterion: GL/Non-GL with reason (needs GHT) |
| `Fn_Kangs(df)` | Kang's criterion: GL/Non-GL with reason |

### Deviation Analysis (`PyVisualFields.visualFields`)

| Function | Description |
|---|---|
| `gettd(df)` | Compute total deviation |
| `gettdp(df)` | Compute TD probability |
| `getpd(df)` | Compute pattern deviation |
| `getpdp(df)` | Compute PD probability |
| `getgl(df)` | Compute global indices (MD, PSD, VFI, GH, etc.) |
| `getglp(df)` | Compute global index p-values |
| `glr(df, type, testSlope)` | Global linear regression |
| `plr(df, type, testSlope)` | Pointwise linear regression |
| `poplr(df, type, testSlope)` | PoPLR permutation regression |

### Visualization

| Function | Description |
|---|---|
| `visualFields.vfplot(df, type='s')` | Sensitivity plot |
| `visualFields.vfplot_tds(df)` | TD probability plot |
| `visualFields.vfplot_pds(df)` | PD probability plot |
| `vfprogression.plotValues(arr)` | Plot sensitivity/TD/PD values |
| `vfprogression.plotProbabilities(arr)` | Plot TDP/PDP probability maps |

### Built-in Datasets

| Function | Description |
|---|---|
| `visualFields.data_vfpwgRetest24d2()` | 24-2 retest dataset (30 glaucoma patients) |
| `visualFields.data_vfctrSunyiu24d2()` | SUNY-IU 24-2 control dataset |
| `visualFields.data_vfpwgSunyiu24d2()` | SUNY-IU 24-2 glaucoma dataset |
| `vfprogression.data_vfseries()` | Longitudinal VF series |
| `vfprogression.data_cigts()` | CIGTS study dataset |
| `vfprogression.data_vfi()` | VFI dataset |
| `vfprogression.data_schell2014()` | Schell 2014 dataset |

---

## Requirements

**Core (all platforms):**
```
numpy
pandas
matplotlib
scipy
PyQt5>=5.15
Pillow
seaborn
pingouin
requests
PyVisualFields
```

**Windows only** (installed automatically with `[windows]` extra):
```
pywin32
```

---

## Citation

If you use PyGlaucoMetrics in your research, please cite:

1. **Moradi et al. (2025)** — PyGlaucoMetrics paper:
   Moradi, M., Hashemabad, S.K., Vu, D.M., Soneru, A.R., Fujita, A., Wang, M., Elze, T., Eslami, M. and Zebardast, N. PyGlaucoMetrics: A Stacked Weight-Based Machine Learning Approach for Glaucoma Detection Using Visual Field Data. *Medicina*, 61(3), 541. https://doi.org/10.3390/medicina61030541

2. **Eslami et al. (2023)** — PyVisualFields:
   Eslami, M., Kazeminasab, S., Sharma, V., Li, Y., Fazli, M., Wang, M., Zebardast, N. and Elze, T. PyVisualFields: A Python Package for Visual Field Analysis. *Trans. Vis. Sci. Tech.*, 12(2), 6. https://doi.org/10.1167/tvst.12.2.6

3. **Moradi et al. (2024)** — ARVO abstract:
   Moradi, M. et al. PyGlaucoMetrics: An Open-Source Multi-Criteria Glaucoma Defect Evaluation. *Investigative Ophthalmology & Visual Science*, 65(7), OD38. https://iovs.arvojournals.org/article.aspx?articleid=2800368

---

## Links

- **PyPI**: https://pypi.org/project/PyGlaucoMetrics/
- **GitHub**: https://github.com/Mousamoradi/PyGlaucoMetrics
- **PyVisualFields**: https://pypi.org/project/PyVisualFields/

---

## License

MIT License — see [LICENSE](LICENSE) for details.
