---
title: "The Factory"
description: "Reconciles vxData against a set of rules, and runs the inference that closes the gap."
image: "https://docs.virdx.dev/img/virdx-social-card.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.virdx.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The Factory

The VirDx factory provides implementations of efficient, vxData-native inference for our various data-generating processes.

## Overview

The factory serves two purposes:

1. It provides a unified API for inference of different workloads.
2. It provides rules and a loop to automatically trigger the above inference of workloads based on the state of vxData.

This `factory` app is structured as follows:

```bash
deployments/              # scripts to trigger image builds
src/factory/
  services/               # implementations of inference workloads
histo_preprocessing/  # histo.preprocessing inference 
viseg/                # viseg-anatomy inference 
  inference.py            # factory purpose 1: inference
  sync.py                 # factory purpose 2: triggering inference based on data
  registry.py             # definition of inference workloads and data trigger rules
  api.py                  # exposes the FastAPI entrypoints
  core.py                 # abstract base classes
  argo.py                 # util for talking to the argo workflows API
  vxdata.py               # util for talking to the vxData API
```

## Adding an Inference Workload

An inference workload, as defined in `services/`, needs to implement `factory.core.Service`: a protocol that enforces the definition + implementation of:

- An `Input` pydantic model: the atomic unit of input to its logic.
- An `Output` pydantic model: the unit of output from its logic.
- A `Params` pydantic model: allowing for configuration of the inference logic, such as threshold values in processing steps, or model IDs to use.
- A `Config` pydantic model: extending `Params`, but adding parameters that may impact the diff/data-selection process (see below).
- A `dispatch` function: effectively taking in a list of `Input` and the service's config as input. All existing inference workloads dispatch their job to Argo Workflows. The implementation here should know best how to deal with large numbers of input cases. Different parallelization strategies may be suited for different workloads. 
- A `diff` function: for the sync of data, the inference workload needs to define what data it expects as input and what data is recognized as output of its logic. This operates on dataframes provided by vxData - it should make use of the vxData schemas here. The goal of this function is to determine what cases to run its own inference on in order to consider the state of vxData to be "fully processed". This most likely is dependent on configurable parameters! 

Add your service module to `registry.py` to be served as an inference service.

Take a look at existing implementations for examples.
Both existing implementations submit an Argo Workflow using the `Dockerfile.inference` image, which has a heavy histo+viseg environment installed. In this image, the `run` functions are called, which perform the entire inference workflow: loading data from vxData, running inference, re-integrating results. Nothing else to do.

> Note that this structure is very likely to change to some extent in the future. There are a few things that still bother me about the abstraction that I want to get rid of. There's at least one layer of abstraction too many.

## Adding a Data Sync Rule

In `registry.py`, extend the list of `RULES` with a configured inference service instantiation. 
See the file for examples.

## Available Inference Endpoints

The factory exposes a REST API for anyone to hit with requests to process certain resources from vxData with certain processing pipelines. 

We currently implement two inference modules:

- Viseg Anatomy Segmentation
- Histo Preprocessing

We're planning on adding histo domain representation inference and diffsim as soon as they have inference interfaces.

### Viseg Anatomy Segmentation

Given any number of `Volume` resources, this job runs inference of a specific Viseg model to produce a multi-class prostate anatomy segmentation based on provided MRI.

For available configuration options, see [its `Params` object](https://github.com/virdx/mono/blob/main/apps/factory/src/factory/services/viseg/schemas.py).

To request inference in Python, you can use an arbitrary HTTP client library to hit the REST API:
```py
identifiers = ["volume/EE12345/t2_tse_tra"]  # an example volume, can be arbitrarily many
config = {
"model_id": "DEFAULT",  # TODO: allow for loading of arbitrary ClearML model
"compute_uncertainty_score": True,
}  # every field is optional, so `{"cases": [...]}` is a valid body
request_body = {
"config": config,
"cases": [{"volume": resource} for resource in identifiers],
}
response = httpx.post(
"https://inference.fra.virdx.dev/inference/viseg", json=request_body
)
response.raise_for_status()
print(response.json())  # [{"name": "factory-viseg-abc12", "url": "https://argo.../abc12"}]
```

### Histo Preprocessing

Given any number of raw `HistoScan` resources, this job runs `histo.preprocessing` to detect
tissue and write back one component scan per gross section found on the slide.

For available configuration options, see [its `Params` object](https://github.com/virdx/mono/blob/main/apps/factory/src/factory/services/histo_preprocessing/schemas.py).

```py
identifiers = ["histoscan/EE12345/01/wsi-1"]  # a raw slide, can be arbitrarily many
request_body = {
"config": {
    "tissue_detection_mag": 1.0,
    "tissue_detection": {
        "early_dilate_ksize": 100,
    },
},
"cases": [{"scan": resource} for resource in identifiers],
}
response = httpx.post(
"https://inference.fra.virdx.dev/inference/histo-preprocessing", json=request_body
)
response.raise_for_status()
```

## Sync Rules

The factory defines a set of *data generation rules* to automate large parts of our routine data generation across our entire data ecosystem in vxData.

You'll find a live overview over deployed rules and their materialization status at `https://dashboard.fra.virdx.dev/factory`. 
Rules are synced automatically: a scheduled job POSTs `/sync`, which submits whatever every
rule finds missing and is not already running.
In case manual triggering of the sync is necessary, use the frontend or the REST-API.

Every sync is a fresh comparison rather than a queue, so nothing is lost by a missed run:
the next one recomputes the gap from vxData and from the jobs currently in flight. Cases
already running are subtracted, so triggering a sync twice does not submit the work twice.

### Triggering a sync

```py
# one rule, everything it finds missing
httpx.post("https://inference.fra.virdx.dev/rules/viseg-anat-t2-tse-tra/sync", json={})

# a hand-picked subset, by case UID as listed in `GET /rules/{producer}`
httpx.post(
"https://inference.fra.virdx.dev/rules/viseg-anat-t2-tse-tra/sync",
json={"cases": ["viseg-anat-t2-tse-tra/7444446b50c9"]},
)

# every rule; `dry_run` submits nothing, and the per-rule counts land in the API log
httpx.post("https://inference.fra.virdx.dev/sync", json={"dry_run": True})
```

Each call returns the workflows it started, as `{"name": ..., "url": ...}`, so a caller can
link straight to the job in Argo.

## API Reference

Consider the auto-generated `https://inference.fra.virdx.dev/docs` for full API docs.

Source: https://docs.virdx.dev/documentation/factory/index.mdx
