slideforge 0.1.1

Fast whole-slide image (WSI) tile extraction in Rust, with tissue detection, stain normalization, and TFRecord output.
Documentation
mod cli;

use std::path::{Path, PathBuf};
use std::process::ExitCode;

use crate::cli::{Cli, Command, DatasetArgs, ExtractArgs};
use clap::Parser;
use slideforge::{
    dataset::{Dataset, DatasetOutputs},
    error::WsiError,
    slide::{Slide, SlideOutputs},
};

fn main() -> ExitCode {
    env_logger::init();
    let result = match Cli::parse().command {
        Command::Extract(extraction_args) => run_slide_extraction(extraction_args),
        Command::Dataset(extraction_args) => run_dataset_extraction(extraction_args),
    };
    match result {
        Ok(()) => ExitCode::SUCCESS,
        Err(err) => {
            eprintln!("error: {err}");
            ExitCode::FAILURE
        }
    }
}

fn default_tfrecord_name(slide_path: &Path) -> PathBuf {
    let stem = slide_path
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("slide");
    PathBuf::from(format!("{stem}.tfrecord"))
}

pub fn run_slide_extraction(args: ExtractArgs) -> Result<(), WsiError> {
    let slide = Slide::open(&args.slide)?;
    let options = args.pipeline.build();
    let outputs = SlideOutputs {
        tfrecord_file: Some(
            args.tfrecord_file
                .unwrap_or_else(|| default_tfrecord_name(&args.slide)),
        ),
        tile_dir: args.tile_dir,
        report_path: args.report,
    };
    slide.extract(&options, &outputs, |_tile| Ok(()))
}

fn run_dataset_extraction(args: DatasetArgs) -> Result<(), WsiError> {
    let dataset = Dataset::from_dir(&args.dataset_dir)?;
    println!(
        "Found {} slide(s) in {}",
        dataset.len(),
        args.dataset_dir.display()
    );

    if args.tile_subdir.is_some() && !args.tiles {
        eprintln!("warning: --tile-subdir has no effect without --tiles");
    }

    let options = args.pipeline.build();
    let outputs = DatasetOutputs {
        tiles: args.tiles,
        tfrecords: true,
        tile_subdir: args.tile_subdir,
        report_path: args.report,
    };
    let report = dataset.extract(&options, &args.output_root, outputs, |_slide, _tile| Ok(()))?;
    println!(
        "Done. {}/{} slides succeeded, {} tiles kept, {} dropped.",
        report.succeeded(),
        report.total_slides(),
        report.total_kept(),
        report.total_dropped()
    );
    Ok(())
}