Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 39 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ winnow config train
# Show prediction configuration
winnow config predict

# Show compute-features configuration
winnow config compute-features

# Check configuration with overrides
winnow config train data_loader=mztab model_output_dir=models/my_model
winnow config predict fdr_method=database_grounded fdr_control.fdr_threshold=0.01
Expand Down Expand Up @@ -93,6 +96,30 @@ For comprehensive calibrator configuration options, see:
- [Configuration guide](configuration.md) - Complete parameter reference
- [Calibration API](api/calibration.md#handling-missing-features) - Feature implementation details

### `winnow compute-features`

Compute calibration features and write one enriched metadata CSV. Uses the same `data_loader`, `dataset` paths and `calibrator.features` stack as training, but does **not** fit the MLP or save a model.

```bash
# Defaults (configs/compute_features.yaml)
winnow compute-features

# Paths and output file
winnow compute-features dataset.spectrum_path_or_directory=data/spectra.ipc dataset.predictions_path=data/preds.csv dataset_output_path=results/features.csv

# De novo spectra (no ground truth): labelled=false; remove retention_time_feature if present
winnow compute-features labelled=false '~calibrator.features.retention_time_feature'
```

**Common parameters:**

- `data_loader`, `dataset.*`: Same as `winnow train`
- `dataset_output_path`: Output CSV path
- `filter_empty_predictions`: Drop empty or invalid predictions (default: true)
- `labelled`: If true (default), spectrum data must include a `sequence` column; runs each feature's `prepare()` (e.g. iRT calibrator).

Feature selection matches training: override `calibrator.features` or use `~calibrator.features.<name>` to drop entries. See [Configuration guide](configuration.md#compute-features-configuration).

### `winnow predict`

Apply calibration and FDR control to new data using a trained model. By default, uses a pretrained general model from Hugging Face Hub.
Expand Down Expand Up @@ -154,6 +181,10 @@ For training (`winnow train`), you need:
- **Spectral data**: MS/MS spectra and metadata
- **Unique identifiers**: Each PSM must have a unique `spectrum_id` in both input files

### Feature export (`winnow compute-features`)

Same inputs as training for loading spectra and predictions. With default `labelled=true`, the spectrum file must include a `sequence` column (as for training). With `labelled=false`, pure *de novo* inputs are allowed if the configured feature set does not include `retention_time_feature`, which requires labelled data to fit an iRT predictor model.

### Prediction data

For prediction (`winnow predict`), you need:
Expand Down Expand Up @@ -233,10 +264,16 @@ Prediction produces two CSV files in the `output-folder` directory:
- `psm_q_value`: Q-values
- `psm_pep`: Posterior error probabilities (non-parametric method only)
- `sequence`: Ground truth sequences (if available, for database-ground method)
- `num_matches`: Number of matched residues between the predicted and ground-truth peptide (if labelled)
- `correct`: Whether the predicted sequence is correct (if labelled)
- Filtered to only include PSMs passing the FDR threshold

This separation allows users to work with metadata and features separately from predictions and error metrics, making downstream analysis more convenient.

### Compute-features output

A single CSV at `dataset_output_path`: full metadata after feature computation. No calibrated scores, FDR columns or FDR row filtering (unlike `winnow predict`).

## Example workflows

### Quick start with defaults
Expand Down Expand Up @@ -328,10 +365,12 @@ View available options:
```bash
winnow --help # List all commands
winnow train --help # Command-specific help
winnow compute-features --help
winnow predict --help
winnow config --help # Config command help

winnow config train # View resolved training configuration
winnow config compute-features # View resolved feature computation configuration
winnow config predict # View resolved prediction configuration
```

Expand Down
39 changes: 39 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ configs/
│ ├── nonparametric.yaml
│ └── database_grounded.yaml
├── train.yaml # Main training config
├── compute_features.yaml # Feature-only export (no MLP fit)
├── calibrator.yaml # Model architecture and features
└── predict.yaml # Main prediction config
```
Expand All @@ -64,6 +65,9 @@ winnow train model_output_dir=models/my_model

# Change multiple parameters
winnow predict data_loader=mztab fdr_control.fdr_threshold=0.01 fdr_method=database_grounded

# Compute-features overrides
winnow compute-features dataset_output_path=results/features.csv labelled=false
```

### Nested parameters
Expand Down Expand Up @@ -126,6 +130,37 @@ dataset_output_path: results/calibrated_dataset.csv
- `model_output_dir`: Where to save trained model
- `dataset_output_path`: Where to save calibrated training results

## Compute-features configuration

### Main config (`configs/compute_features.yaml`)

Loads data like `train.yaml` (same `defaults`: `residues`, `calibrator`, `data_loader`), runs `ProbabilityCalibrator.compute_features` only, and writes one CSV. No `model_output_dir` and no MLP training.

```yaml
defaults:
- _self_
- residues
- calibrator
- data_loader: instanovo

dataset:
spectrum_path_or_directory: data/spectra.ipc
predictions_path: data/predictions.csv

dataset_output_path: results/metadata.csv
filter_empty_predictions: true
labelled: true
```

**Key parameters:**

- `dataset.*`, `data_loader`: Same meaning as in training config
- `dataset_output_path`: CSV path for metadata after feature computation
- `filter_empty_predictions`: If true, apply the same empty-prediction filter as train/predict
- `labelled`: If true, spectrum data must include `sequence` (ground truth); runs feature `prepare()` (needed for e.g. `RetentionTimeFeature`). If false, you must not include `retention_time_feature` in `calibrator.features` (validation error otherwise)

The feature set is the `calibrator.features` block from `calibrator.yaml` (shared with training). Override or drop features with Hydra the same way as for `winnow train`.

### Calibrator config (`configs/calibrator.yaml`)

Controls model architecture and calibration features:
Expand Down Expand Up @@ -577,6 +612,9 @@ winnow config train
# View prediction configuration
winnow config predict

# View compute-features configuration
winnow config compute-features

# View configuration with overrides
winnow config train data_loader=mztab model_output_dir=custom/path
winnow config predict fdr_method=database_grounded fdr_control.fdr_threshold=0.01
Expand Down Expand Up @@ -615,6 +653,7 @@ my_configs/
├── residues.yaml # Override residue masses/modifications
├── calibrator.yaml # Override calibrator features
├── train.yaml # Override training config (if needed)
├── compute_features.yaml # Override compute-features config (if needed)
├── predict.yaml # Override prediction config (if needed)
├── data_loader/ # Override data loaders (if needed)
│ └── instanovo.yaml
Expand Down
8 changes: 4 additions & 4 deletions tests/datasets/test_data_loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,14 +686,14 @@ def test_evaluate_no_label_columns_when_no_labels(self, loader):
dataset = pd.DataFrame({"prediction": [["A", "G"]]})
result = loader._evaluate_predictions(dataset, has_labels=False)
assert "correct" not in result.columns
assert "valid_peptide" not in result.columns
assert "valid_sequence" not in result.columns
assert "num_matches" not in result.columns

def test_evaluate_adds_valid_peptide_when_has_labels(self, loader):
def test_evaluate_adds_valid_sequence_when_has_labels(self, loader):
dataset = pd.DataFrame({"sequence": [["A", "G"]], "prediction": [["A", "G"]]})
result = loader._evaluate_predictions(dataset, has_labels=True)
assert "valid_peptide" in result.columns
assert result["valid_peptide"].iloc[0]
assert "valid_sequence" in result.columns
assert result["valid_sequence"].iloc[0]

def test_evaluate_correct_flag_true_on_full_match(self, loader):
seq = ["P", "E", "P"]
Expand Down
17 changes: 17 additions & 0 deletions winnow/configs/compute_features.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# --- Compute calibration features (no MLP fit / no model save) ---
defaults:
- _self_
- residues
- calibrator
- data_loader: instanovo # Options: instanovo, mztab, pointnovo, winnow

dataset:
# Dataset paths (same as train):
spectrum_path_or_directory: examples/example_data/spectra.ipc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to change this to spectra.mgf later if you also merge #173

predictions_path: examples/example_data/predictions.csv

dataset_output_path: results/metadata.csv
# Drop rows with empty or non-list predictions (same helper as train/predict).
filter_empty_predictions: true
# If true, compute features for a labelled dataset (needed for RetentionTimeFeature iRT mapping, where we train a model to predict iRT from retention time using a high-confidence labelled subset of the dataset).
labelled: true
56 changes: 43 additions & 13 deletions winnow/datasets/calibration_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,10 @@ def save(self, data_dir: Path) -> None:
data_dir (Path): Directory to save the dataset. This will contain `metadata.csv` and
optionally, `predictions.pkl` for serialized beam search results.
"""
data_dir.mkdir(parents=True, exist_ok=True)
with (data_dir / "metadata.csv").open(mode="w") as metadata_file:
output_metadata = self.metadata.copy(deep=True)
if "sequence" in output_metadata.columns:
output_metadata["sequence"] = output_metadata["sequence"].apply(
tokens_to_proforma
)
output_metadata["prediction"] = output_metadata["prediction"].apply(
tokens_to_proforma
)
output_metadata.to_csv(metadata_file, index=False)
self.save_metadata(data_dir / "metadata.csv")

if self.predictions:
with (data_dir / "predictions.pkl").open(mode="wb") as predictions_file:
pickle.dump(self.predictions, predictions_file)
self.save_predictions(data_dir / "predictions.pkl")

@property
def confidence_column(self) -> str:
Expand Down Expand Up @@ -156,6 +145,47 @@ def filter_entries(

return CalibrationDataset(predictions=predictions, metadata=metadata)

def save_metadata(self, path: Union[Path, str]) -> None:
"""Save the dataset metadata to a CSV file.

Args:
path (Union[Path, str]): Path to the output CSV file.
"""
if isinstance(path, str):
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)

output_metadata = self.metadata.copy(deep=True)

if "sequence" in output_metadata.columns:
output_metadata["sequence"] = output_metadata["sequence"].apply(
tokens_to_proforma
)
if "prediction" in output_metadata.columns:
output_metadata["prediction"] = output_metadata["prediction"].apply(
tokens_to_proforma
)
if "prediction_untokenised" in output_metadata.columns:
output_metadata = output_metadata.drop(columns=["prediction_untokenised"])
if "valid_sequence" in output_metadata.columns:
output_metadata = output_metadata.drop(columns=["valid_sequence"])
if "valid_prediction" in output_metadata.columns:
output_metadata = output_metadata.drop(columns=["valid_prediction"])
output_metadata.to_csv(path, index=False)

def save_predictions(self, path: Union[Path, str]) -> None:
"""Save the dataset predictions to a pickle file.

Args:
path (Union[Path, str]): Path to the output pickle file.
"""
if isinstance(path, str):
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)

with path.open(mode="wb") as predictions_file:
pickle.dump(self.predictions, predictions_file)

def to_csv(self, path: Union[Path, str]) -> None:
"""Saves the dataset metadata to a CSV file.

Expand Down
4 changes: 2 additions & 2 deletions winnow/datasets/data_loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ def _evaluate_predictions(
pd.DataFrame: The processed dataframe.
"""
if has_labels:
dataset["valid_peptide"] = dataset["sequence"].apply(
dataset["valid_sequence"] = dataset["sequence"].apply(
lambda peptide: isinstance(peptide, list)
)
dataset["valid_prediction"] = dataset["prediction"].apply(
Expand Down Expand Up @@ -807,7 +807,7 @@ def _evaluate_predictions(
.map_elements(
lambda x: isinstance(x, pl.Series), return_dtype=pl.Boolean
)
.alias("valid_peptide"),
.alias("valid_sequence"),
]
)

Expand Down
Loading
Loading