Official repository for the paper: "M3-Gen: Interpretable Multimodal Generation of Gene Expression Profiles Using Clinical and Imaging Data"
M3-Gen learns to generate patient-level RNA-seq gene expression profiles conditioned on multimodal information extracted from TCGA:
A contrastive model first aligns image, text and RNA-seq representations in a shared embedding space; the resulting image/text embeddings are then used to condition a Wasserstein GAN (with gradient penalty) that generates synthetic gene expression profiles. Attention-based conditioning also yields an interpretable attention map over WSI patches for each generated sample.
The paper can be accessed here
data/ GDC manifests + TCGA cohort table used to build the dataset
src/
├── dataset/
│ ├── download_data.py Download WSIs + RNA-seq from GDC and assemble the dataset
│ ├── utils.py GDC API / cleaning helpers used by download_data.py
│ └── contrastive_dataset.py Dataset/Dataloader for the contrastive model
├── preprocessing/
│ ├── patch_preprocessing.py Otsu tissue segmentation + tiling of WSIs into patches
│ └── text_preprocessing.py LLM-based generation of clinical text descriptions
│
├── contrastive_model.py, rnaseq_contrastive_model.py Contrastive model architectures
├── train_contrastive.py Train image-text-RNA contrastive model
├── train_contrastive_rna_seq.py Train the RNA-seq branch only
├── train_contrastive_rna_seq_with_vae.py Same, with a VAE bottleneck (see src/vae/)
├── preprocess_embeddings.py Precompute WSI patch embeddings with a trained image encoder
│
├── vanilla_generative_model.py Unconditional WGAN-GP baseline
├── benchmark_generative_model.py Class-conditional (tumor type) WGAN-GP baseline
├── generative_model_conditional_concat.py Multimodal GAN, text/image/mean-conditioning via concatenation
├── generative_model_conditional_attention.py Multimodal GAN, attention-based conditioning (main M3-Gen model)
│
├── display_attention_weights.py, display_attention_weights_all.py Attention map visualization
├── compare_de_real_vs_generated.py Differential-expression + pathway-enrichment analysis
├── test_robustness_domain_shift.py Robustness to WSI staining/blur/folding augmentations
├── visualize_augmentations.py Visualize the augmentation pipeline used above
│
├── data_loader.py, multi_patch_gan_dataloader.py,
├── augmented_multi_patch_gan_dataloader.py, benchmark_gan_dataloader.py Dataloaders
├── generative_model_utils.py, img_manip.py Shared model/image utilities
└── corr_score.py, distribution_distances.py, unsupervised_metrics.py,
utility_evaluation.py, de_genes.py Evaluation metrics (imported by the scripts above, not run standalone)
All entry points below are plain Python scripts with argparse CLIs — run them directly, there are no notebooks or shell wrappers in this repository.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Notes:
requirements.txt pins CPU-agnostic versions of torch/torchvision. If you need CUDA wheels (recommended for training), install them from the official index for your CUDA version instead, e.g.:
pip install torch==2.6.0 torchvision==0.21.0 --index-url https://download.pytorch.org/whl/cu124
openslide-python needs the native OpenSlide library. openslide-bin (in requirements.txt) ships it as a wheel; on Linux you can alternatively install it via your package manager (apt install openslide-tools).text_preprocessing.py downloads a gated model from the Hugging Face Hub. Authenticate first — never hardcode tokens in scripts:
huggingface-cli login # or: export HF_TOKEN=...
python src/dataset/download_data.py \
-output_path /path/to/dataset \
-max_tissue_size 70000000 \
-num_workers 8
This reads the manifests/cohort table in data/ (override with -data_dir <dir>), downloads matching tissue slides and RNA-seq files from the GDC API, and writes dataset_info.pkl, metainfos.pkl, case_ids.txt, rna_seq.parquet and slides_info.parquet under /path/to/dataset.
python src/preprocessing/patch_preprocessing.py --dataset_path /path/to/dataset
Uses Otsu thresholding to find tissue regions and writes valid_patches.json (candidate 128/256/512/1024px tiles per case) into the dataset directory.
python src/preprocessing/text_preprocessing.py \
--dataset_path /path/to/dataset \
--model_name ContactDoctor/Bio-Medical-Llama-3-8B
Loads (4-bit quantized) an instruction-tuned LLM and turns each case's clinical metadata into a short descriptive paragraph, used later as the "text" modality.
Aligns image, text and RNA-seq embeddings in a shared space (image encoder: timm UNI foundation model; text encoder: a Hugging Face clinical language model).
python src/train_contrastive.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/contrastive_model \
--num_epochs 10 \
--batch_size 16 \
--multi_patch --num_patches 8
RNA-seq-only variants (used for ablations / the VAE bottleneck experiments):
python src/train_contrastive_rna_seq.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/contrastive_rna_seq
python src/train_contrastive_rna_seq_with_vae.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/contrastive_rna_seq_vae
Run python src/<script>.py --help for the full list of hyperparameters (learning rate, embedding dims, freezing encoders, etc.).
Once a contrastive model is trained, precompute per-case image embeddings (used to condition the generative models below):
python src/preprocess_embeddings.py \
--dataset_path /path/to/dataset \
--model_path /path/to/experiments/contrastive_model/<run_id> \
--num_workers 8
All generative-model scripts share the same dataset/experiment layout and most CLI flags. Common flags:
| Flag | Meaning |
|---|---|
--dataset_path | Dataset root (must contain rna_seq.parquet, dataset_info.pkl, patch_embeddings/) |
--output_path | Where checkpoints, logs and generated samples are written |
--epochs | Training epochs (default 500) |
--batch_size, --num_workers, --seed | Standard training knobs |
--latent_dims, --hidden_dims | Model capacity |
--efficient | Use the memory-efficient patch dataloader that preloads all patches upfront |
--test_only | Skip training and only run generation/evaluation from an existing checkpoint in --output_path |
python src/generative_model_conditional_attention.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/attention_latent512 \
--epochs 500 --batch_size 8 --latent_dims 512 --hidden_dims 256 \
--num_patches 64 --num_workers 8 --efficient
To evaluate an already-trained checkpoint instead of retraining, add --test_only (the checkpoint is expected at <output_path>/generator_last_epoch.pt).
python src/generative_model_conditional_concat.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/concat_mean \
--epochs 500 --batch_size 8 --latent_dims 256 \
--concat_type mean \
--efficient
--concat_type selects which modality is concatenated to the noise vector: text, image, or mean (average of the two).
Class-conditional (tumor type only, no imaging/text):
python src/benchmark_generative_model.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/benchmark \
--epochs 500 --batch_size 8 --latent_dims 256
Fully unconditional:
python src/vanilla_generative_model.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/vanilla \
--epochs 500 --batch_size 64 --latent_dims 64
python src/display_attention_weights.py \
--dataset_path /path/to/dataset \
--case_id <case_id> \
--model_path /path/to/experiments/contrastive_model/<run_id> \
--generator_path /path/to/experiments/attention_latent512/generator_last_epoch.pt \
--output_path /path/to/experiments/attention
Omit --case_id and use display_attention_weights_all.py with the same other arguments to render attention maps for every case in the dataset.
Exploratory biological-plausibility check: computes DE genes between two tumor types in real data and in data sampled from a trained generator, then compares the overlap (and, optionally, enriched pathways via Enrichr).
python src/compare_de_real_vs_generated.py \
--dataset_path /path/to/dataset \
--model_path /path/to/experiments/attention_latent512/generator_last_epoch.pt \
--output_dir /path/to/experiments/de_real_vs_generated \
--group_a BRCA --group_b LUAD \
--latent_dims 512 --hidden_dims 256 --num_patches 64 \
--device cuda \
--run_enrichment --gene_set_library KEGG_2021_Human
Drop --run_enrichment if gseapy is not installed or you don't need the pathway-overlap step. --device cpu works too (just slower).
Evaluates the attention-based generator when patches are corrupted with synthetic staining/focus/tissue-folding artifacts, and compares against a clean baseline.
python src/test_robustness_domain_shift.py \
--dataset_path /path/to/dataset \
--model_path /path/to/experiments/attention_latent512/generator_last_epoch.pt \
--image_encoder_path /path/to/experiments/contrastive_model/<run_id> \
--output_path /path/to/experiments/robustness_test \
--latent_dims 512 --hidden_dims 256 --num_patches 64 --efficient \
--embeddings_in_main --embedding_batch_size 64 \
--blur_mode local --augmentation_probability 0.33 \
--apply_stain_variation --apply_out_of_focus --apply_tissue_folding
Enable any subset of --apply_stain_variation, --apply_out_of_focus, --apply_tissue_folding to test that corruption in isolation or combined; a "clean" and an "augmented" run are always produced side by side under --output_path.
To preview what the augmentation pipeline actually does to a patch before running the full test:
python src/visualize_augmentations.py \
--dataset_path /path/to/dataset \
--output_dir /path/to/out \
--num_patches 8 --augmentation_probability 1.0
Every script above is a regular python foo.py --args invocation, so submitting it to SLURM only requires a small generic wrapper — there is no repository-specific job script to maintain. For example:
#!/usr/bin/bash
#SBATCH --job-name m3gen
#SBATCH --gpus 1
#SBATCH --time 24:00:00
#SBATCH --output logs/%x_%j.out
#SBATCH --error logs/%x_%j.err
module load CUDA/12.4.0
module load Python/3.12.3-GCCcore-14.2.0
source /path/to/venv/bin/activate
python src/generative_model_conditional_attention.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/attention_latent512 \
--epochs 500 --batch_size 8 --latent_dims 512 --efficient
Save this as job.sh and submit with sbatch job.sh. If you need a Hugging Face token on the compute node (text_preprocessing.py), export it from your shell environment or ~/.bashrc — do not commit it to a script.
If you use this code, please cite:
@article{m3gen,
title={M $\^{} 3$-Gen: Interpretable Multimodal Generation of Gene Expression Profiles Using Clinical and Imaging Data},
author={Panaccione, Francesca Pia and Sgaravatti, Carlo and Venere, Marco},
journal={arXiv preprint arXiv:2607.21343},
year={2026}
}
Official repository for the paper: "M3-Gen: Interpretable Multimodal Generation of Gene Expression Profiles Using Clinical and Imaging Data"
M3-Gen learns to generate patient-level RNA-seq gene expression profiles conditioned on multimodal information extracted from TCGA:
A contrastive model first aligns image, text and RNA-seq representations in a shared embedding space; the resulting image/text embeddings are then used to condition a Wasserstein GAN (with gradient penalty) that generates synthetic gene expression profiles. Attention-based conditioning also yields an interpretable attention map over WSI patches for each generated sample.
The paper can be accessed here
data/ GDC manifests + TCGA cohort table used to build the dataset
src/
├── dataset/
│ ├── download_data.py Download WSIs + RNA-seq from GDC and assemble the dataset
│ ├── utils.py GDC API / cleaning helpers used by download_data.py
│ └── contrastive_dataset.py Dataset/Dataloader for the contrastive model
├── preprocessing/
│ ├── patch_preprocessing.py Otsu tissue segmentation + tiling of WSIs into patches
│ └── text_preprocessing.py LLM-based generation of clinical text descriptions
│
├── contrastive_model.py, rnaseq_contrastive_model.py Contrastive model architectures
├── train_contrastive.py Train image-text-RNA contrastive model
├── train_contrastive_rna_seq.py Train the RNA-seq branch only
├── train_contrastive_rna_seq_with_vae.py Same, with a VAE bottleneck (see src/vae/)
├── preprocess_embeddings.py Precompute WSI patch embeddings with a trained image encoder
│
├── vanilla_generative_model.py Unconditional WGAN-GP baseline
├── benchmark_generative_model.py Class-conditional (tumor type) WGAN-GP baseline
├── generative_model_conditional_concat.py Multimodal GAN, text/image/mean-conditioning via concatenation
├── generative_model_conditional_attention.py Multimodal GAN, attention-based conditioning (main M3-Gen model)
│
├── display_attention_weights.py, display_attention_weights_all.py Attention map visualization
├── compare_de_real_vs_generated.py Differential-expression + pathway-enrichment analysis
├── test_robustness_domain_shift.py Robustness to WSI staining/blur/folding augmentations
├── visualize_augmentations.py Visualize the augmentation pipeline used above
│
├── data_loader.py, multi_patch_gan_dataloader.py,
├── augmented_multi_patch_gan_dataloader.py, benchmark_gan_dataloader.py Dataloaders
├── generative_model_utils.py, img_manip.py Shared model/image utilities
└── corr_score.py, distribution_distances.py, unsupervised_metrics.py,
utility_evaluation.py, de_genes.py Evaluation metrics (imported by the scripts above, not run standalone)
All entry points below are plain Python scripts with argparse CLIs — run them directly, there are no notebooks or shell wrappers in this repository.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Notes:
requirements.txt pins CPU-agnostic versions of torch/torchvision. If you need CUDA wheels (recommended for training), install them from the official index for your CUDA version instead, e.g.:
pip install torch==2.6.0 torchvision==0.21.0 --index-url https://download.pytorch.org/whl/cu124
openslide-python needs the native OpenSlide library. openslide-bin (in requirements.txt) ships it as a wheel; on Linux you can alternatively install it via your package manager (apt install openslide-tools).text_preprocessing.py downloads a gated model from the Hugging Face Hub. Authenticate first — never hardcode tokens in scripts:
huggingface-cli login # or: export HF_TOKEN=...
python src/dataset/download_data.py \
-output_path /path/to/dataset \
-max_tissue_size 70000000 \
-num_workers 8
This reads the manifests/cohort table in data/ (override with -data_dir <dir>), downloads matching tissue slides and RNA-seq files from the GDC API, and writes dataset_info.pkl, metainfos.pkl, case_ids.txt, rna_seq.parquet and slides_info.parquet under /path/to/dataset.
python src/preprocessing/patch_preprocessing.py --dataset_path /path/to/dataset
Uses Otsu thresholding to find tissue regions and writes valid_patches.json (candidate 128/256/512/1024px tiles per case) into the dataset directory.
python src/preprocessing/text_preprocessing.py \
--dataset_path /path/to/dataset \
--model_name ContactDoctor/Bio-Medical-Llama-3-8B
Loads (4-bit quantized) an instruction-tuned LLM and turns each case's clinical metadata into a short descriptive paragraph, used later as the "text" modality.
Aligns image, text and RNA-seq embeddings in a shared space (image encoder: timm UNI foundation model; text encoder: a Hugging Face clinical language model).
python src/train_contrastive.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/contrastive_model \
--num_epochs 10 \
--batch_size 16 \
--multi_patch --num_patches 8
RNA-seq-only variants (used for ablations / the VAE bottleneck experiments):
python src/train_contrastive_rna_seq.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/contrastive_rna_seq
python src/train_contrastive_rna_seq_with_vae.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/contrastive_rna_seq_vae
Run python src/<script>.py --help for the full list of hyperparameters (learning rate, embedding dims, freezing encoders, etc.).
Once a contrastive model is trained, precompute per-case image embeddings (used to condition the generative models below):
python src/preprocess_embeddings.py \
--dataset_path /path/to/dataset \
--model_path /path/to/experiments/contrastive_model/<run_id> \
--num_workers 8
All generative-model scripts share the same dataset/experiment layout and most CLI flags. Common flags:
| Flag | Meaning |
|---|---|
--dataset_path | Dataset root (must contain rna_seq.parquet, dataset_info.pkl, patch_embeddings/) |
--output_path | Where checkpoints, logs and generated samples are written |
--epochs | Training epochs (default 500) |
--batch_size, --num_workers, --seed | Standard training knobs |
--latent_dims, --hidden_dims | Model capacity |
--efficient | Use the memory-efficient patch dataloader that preloads all patches upfront |
--test_only | Skip training and only run generation/evaluation from an existing checkpoint in --output_path |
python src/generative_model_conditional_attention.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/attention_latent512 \
--epochs 500 --batch_size 8 --latent_dims 512 --hidden_dims 256 \
--num_patches 64 --num_workers 8 --efficient
To evaluate an already-trained checkpoint instead of retraining, add --test_only (the checkpoint is expected at <output_path>/generator_last_epoch.pt).
python src/generative_model_conditional_concat.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/concat_mean \
--epochs 500 --batch_size 8 --latent_dims 256 \
--concat_type mean \
--efficient
--concat_type selects which modality is concatenated to the noise vector: text, image, or mean (average of the two).
Class-conditional (tumor type only, no imaging/text):
python src/benchmark_generative_model.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/benchmark \
--epochs 500 --batch_size 8 --latent_dims 256
Fully unconditional:
python src/vanilla_generative_model.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/vanilla \
--epochs 500 --batch_size 64 --latent_dims 64
python src/display_attention_weights.py \
--dataset_path /path/to/dataset \
--case_id <case_id> \
--model_path /path/to/experiments/contrastive_model/<run_id> \
--generator_path /path/to/experiments/attention_latent512/generator_last_epoch.pt \
--output_path /path/to/experiments/attention
Omit --case_id and use display_attention_weights_all.py with the same other arguments to render attention maps for every case in the dataset.
Exploratory biological-plausibility check: computes DE genes between two tumor types in real data and in data sampled from a trained generator, then compares the overlap (and, optionally, enriched pathways via Enrichr).
python src/compare_de_real_vs_generated.py \
--dataset_path /path/to/dataset \
--model_path /path/to/experiments/attention_latent512/generator_last_epoch.pt \
--output_dir /path/to/experiments/de_real_vs_generated \
--group_a BRCA --group_b LUAD \
--latent_dims 512 --hidden_dims 256 --num_patches 64 \
--device cuda \
--run_enrichment --gene_set_library KEGG_2021_Human
Drop --run_enrichment if gseapy is not installed or you don't need the pathway-overlap step. --device cpu works too (just slower).
Evaluates the attention-based generator when patches are corrupted with synthetic staining/focus/tissue-folding artifacts, and compares against a clean baseline.
python src/test_robustness_domain_shift.py \
--dataset_path /path/to/dataset \
--model_path /path/to/experiments/attention_latent512/generator_last_epoch.pt \
--image_encoder_path /path/to/experiments/contrastive_model/<run_id> \
--output_path /path/to/experiments/robustness_test \
--latent_dims 512 --hidden_dims 256 --num_patches 64 --efficient \
--embeddings_in_main --embedding_batch_size 64 \
--blur_mode local --augmentation_probability 0.33 \
--apply_stain_variation --apply_out_of_focus --apply_tissue_folding
Enable any subset of --apply_stain_variation, --apply_out_of_focus, --apply_tissue_folding to test that corruption in isolation or combined; a "clean" and an "augmented" run are always produced side by side under --output_path.
To preview what the augmentation pipeline actually does to a patch before running the full test:
python src/visualize_augmentations.py \
--dataset_path /path/to/dataset \
--output_dir /path/to/out \
--num_patches 8 --augmentation_probability 1.0
Every script above is a regular python foo.py --args invocation, so submitting it to SLURM only requires a small generic wrapper — there is no repository-specific job script to maintain. For example:
#!/usr/bin/bash
#SBATCH --job-name m3gen
#SBATCH --gpus 1
#SBATCH --time 24:00:00
#SBATCH --output logs/%x_%j.out
#SBATCH --error logs/%x_%j.err
module load CUDA/12.4.0
module load Python/3.12.3-GCCcore-14.2.0
source /path/to/venv/bin/activate
python src/generative_model_conditional_attention.py \
--dataset_path /path/to/dataset \
--output_path /path/to/experiments/attention_latent512 \
--epochs 500 --batch_size 8 --latent_dims 512 --efficient
Save this as job.sh and submit with sbatch job.sh. If you need a Hugging Face token on the compute node (text_preprocessing.py), export it from your shell environment or ~/.bashrc — do not commit it to a script.
If you use this code, please cite:
@article{m3gen,
title={M $\^{} 3$-Gen: Interpretable Multimodal Generation of Gene Expression Profiles Using Clinical and Imaging Data},
author={Panaccione, Francesca Pia and Sgaravatti, Carlo and Venere, Marco},
journal={arXiv preprint arXiv:2607.21343},
year={2026}
}