# Cytovanni

Cytovanni is a Python library for computational standardization of flow cytometry data.

- GitHub repository: https://github.com/anders-biostat/Cytovanni
- Documentation: https://cytovanni.readthedocs.io

## Container Setup

The recommended runtime is an Apptainer container with all dependencies. Jupyter and Apptainer must already be installed on the host. Ask the user where they want to store the container files (`.sif` and `init_kernel.sh`) — these paths are hardcoded, so the location matters.

### 1. Download and build the container

```bash
curl -L https://raw.githubusercontent.com/anders-biostat/Cytovanni/main/container/container.def -o /your/path/container.def
apptainer build -F /your/path/cytovanni-container.sif /your/path/container.def
```

Building takes several minutes and requires internet access.

### 2. Create the kernel launch script

Create `/your/path/init_kernel.sh`:
```bash
#!/bin/bash
apptainer exec --nv /your/path/cytovanni-container.sif python -m ipykernel "$@"
```

Then make it executable: `chmod +x /your/path/init_kernel.sh`

`--nv` enables GPU access. Use `--bind /extra/path` to mount additional directories (the user home directory is mounted automatically).

### 3. Register the Jupyter kernel

```bash
mkdir -p ~/.local/share/jupyter/kernels/cytovanni-container
```

Create `~/.local/share/jupyter/kernels/cytovanni-container/kernel.json`:
```json
{
  "argv": [
    "/your/path/init_kernel.sh",
    "-f",
    "{connection_file}"
  ],
  "display_name": "Python (Cytovanni)",
  "language": "python"
}
```

Download the kernel logo:
```bash
curl -L https://raw.githubusercontent.com/anders-biostat/Cytovanni/main/container/logo-64x64.png -o ~/.local/share/jupyter/kernels/cytovanni-container/logo-64x64.png
```

The kernel will appear as **Python (Cytovanni)** in the Jupyter launcher.

### Adding extra packages

To add packages, modify `container.def` before building:
```bash
sed '/    # custom packages/a\    uv pip install --system package1 package2' -i container.def
```

## R Container (CytoNorm)

```bash
curl -L https://raw.githubusercontent.com/anders-biostat/Cytovanni/main/container/Rcontainer.def -o /your/path/Rcontainer.def
apptainer build -F /your/path/cytovanni-R-container.sif /your/path/Rcontainer.def
```

Create `/your/path/init_R_kernel.sh`:
```bash
#!/bin/bash
for i in "$@"; do
    if [[ "$prev" == "-f" ]]; then
        CONN_FILE="$i"
    fi
    prev="$i"
done
apptainer exec /your/path/cytovanni-R-container.sif R --slave -e "IRkernel::main('${CONN_FILE}')"
```

Register with `kernel.json` using `"display_name": "R (Cytovanni)"` and `"language": "R"`.

## CytometerConfiguration Setup

Every Cytovanni workflow requires a `CytometerConfiguration` object. The TL;DR: only `channels_fluorescence`, `max_linear_range`, and `cytometer` usually need to be set.

### Step 1: Find available channels

```python
cytovanni.io.readfcs_available_channels("/path/to/file.fcs")
```

This returns the channel names as they appear in the .fcs file, e.g. `['FSC-A', 'FSC-H', 'FSC-W', 'SSC-A', 'SSC-H', 'SSC-W', 'BV421-A', ...]`.

### Step 2: Create the configuration

```python
cytoconfig = cytovanni.utils.CytometerConfiguration(
    channels_fluorescence=['BB515-A', 'BV421-A', ...],  # always required, varies per instrument
    max_linear_range=2e5,                                # approximate detector linearity limit
    cytometer="MyInstrument",                            # name string, used for bookkeeping
)
```

All other parameters have sensible defaults:
- `channels_scatter` — defaults to `["FSC-A","FSC-H","FSC-W","SSC-A","SSC-H","SSC-W"]`, usually the same across instruments
- `channels_meta` — defaults to `["Time"]`
- `default_scatter_gate` — defaults to `["FSC-A", "SSC-A"]`, used by bead gating functions
- `try_adding_HW` — if `True`, automatically loads `-H` and `-W` variants of fluorescence channels where available (default `False`)
- `fct_hash_settings` — hash function to detect cytometer setting changes between batches; rarely needs changing

### Reuse: subclass or save to JSON

For repeated use, either subclass:

```python
class MyCytometerConfig(cytovanni.utils.CytometerConfiguration):
    def __init__(self):
        super().__init__(
            channels_fluorescence=[...],
            cytometer="MyInstrument",
            max_linear_range=2e5,
        )
```

Or save/load as JSON:

```python
cytoconfig.write_json("cytoconfig.json")
cytoconfig = cytovanni.utils.CytometerConfiguration.load_json("cytoconfig.json")
```

Note: `fct_hash_settings` is never saved to JSON and always reverts to the default when loading.
