wsi-dicom 0.7.0

Convert whole-slide imaging files to DICOM VL Whole Slide Microscopy
Documentation
use super::*;

pub(super) fn export_jpeg_passthrough_instance(
    slide: &Slide,
    request: &ExportRequest,
    metadata: &DicomMetadata,
    identity: &DicomExportIdentity,
    instance_number: u32,
    coordinate: InstanceCoordinate,
    level: &wsi_rs::Level,
) -> Result<InstanceReport, Error> {
    let tile_size = request.options.tile_size;
    let (matrix_columns, matrix_rows) = level.dimensions;
    let location = coordinate;
    let geometry = jpeg_baseline_route_frame_geometry(slide, level, location, tile_size)?;
    let (tiles_across, tiles_down) = (geometry.tiles_across, geometry.tiles_down);
    let (frame_columns, frame_rows) = (geometry.frame_columns, geometry.frame_rows);
    let frame_count = checked_frame_count_u32(tiles_across, tiles_down)?;
    let context = DicomInstanceContext::new(
        identity,
        &request.output_dir,
        require_pixel_spacing_mm(level_pixel_spacing_mm(slide, level))?,
        coordinate,
    )?;
    let icc_profile = resolve_icc_profile(
        slide,
        request,
        coordinate.scene_idx,
        coordinate.series_idx,
        coordinate.level_idx,
        level,
    )?;

    if let Some(direct_frames) =
        try_plan_direct_jpeg_passthrough_frames(slide, location, level, geometry)?
    {
        let mut pixel_profile = None;
        let mut metrics = ExportMetrics::default();
        let mut compressed_bytes = 0u64;
        let mut uncompressed_bytes = 0u64;
        let mut lengths = Vec::with_capacity(direct_frames.len());
        for frame in &direct_frames {
            ensure_consistent_pixel_profile(
                &mut pixel_profile,
                frame.profile,
                "JPEG passthrough pixel profile changed across frames",
            )?;
            compressed_bytes = compressed_bytes.saturating_add(frame.compressed_bytes);
            uncompressed_bytes = uncompressed_bytes.saturating_add(frame.uncompressed_bytes);
            lengths.push(frame.compressed_bytes);
            metrics.record_passthrough_frame();
            metrics.record_pixel_profile(frame.profile);
        }

        let profile = pixel_profile.ok_or_else(|| Error::Unsupported {
            reason: "slide level produced no frames".into(),
        })?;
        let offsets = pixel_data_offsets_from_lengths(&lengths)?;
        let object = context.build_dicom_object(InstanceDicomObjectParams {
            metadata,
            study_uid: identity.study_uid(),
            instance_number,
            frame_grid: FrameGrid {
                frame_columns,
                frame_rows,
                matrix_columns,
                matrix_rows,
            },
            frame_count,
            profile,
            pixel_data_offsets: PixelDataOffsetTables {
                offsets,
                lengths: lengths.clone(),
            },
            icc_profile: icc_profile.bytes.as_deref(),
            lossy_compression: Some(LossyCompressionMetadata {
                method: "ISO_10918_1",
                ratio: (compressed_bytes > 0)
                    .then_some(uncompressed_bytes as f64 / compressed_bytes as f64),
            }),
        })?;
        let mut direct_writer = DirectJpegPassthroughFrameWriter::new(
            slide,
            location,
            geometry,
            direct_frames.len(),
            DIRECT_JPEG_PASSTHROUGH_WRITE_CHUNK_FRAMES,
        );
        let write_started = Instant::now();
        write_dicom_object_with_direct_pixel_data(
            &context.path,
            object,
            context.file_meta(request.options.transfer_syntax.uid()),
            request.options.overwrite,
            &lengths,
            |idx, output| direct_writer.write_frame(idx, output),
        )?;
        metrics.record_write_duration(write_started.elapsed());

        return Ok(context.report(
            request.options.transfer_syntax.uid(),
            frame_count,
            icc_profile.source,
            metrics,
        ));
    }

    let spool_path = unique_spool_path(&context.path);
    let mut pixel_spool = PixelDataSpool::create(spool_path, frame_count as usize)?;
    let mut pixel_profile = None;
    #[cfg(all(feature = "metal", target_os = "macos"))]
    let mut metal_input = MetalInputTileReader::new(
        request.options.encode_backend,
        request.options.source_device_decode,
    );
    let mut metrics = ExportMetrics::default();
    let mut compressed_bytes = 0u64;
    let mut uncompressed_bytes = 0u64;
    let allow_raw_rgb_passthrough = raw_rgb_passthrough_has_no_geometry_fallback(level, geometry);
    let mut blank_jpeg_cache = None;

    for row in 0..tiles_down {
        let row_plan = plan_jpeg_baseline_row(
            slide,
            location,
            row,
            tiles_across,
            matrix_columns,
            matrix_rows,
            frame_columns,
            frame_rows,
            allow_raw_rgb_passthrough,
            request.options.jpeg_quality,
            &mut blank_jpeg_cache,
            "JPEG Baseline row frame count exceeds platform addressable memory",
            "JPEG Baseline tile x offset overflow",
            "JPEG Baseline tile y offset overflow",
        )?;
        record_jpeg_retile_rejections(&mut metrics, &row_plan.retile_rejections);
        let planned = row_plan.frames;

        let mut index = 0usize;
        while index < planned.len() {
            match &planned[index] {
                JpegBaselinePlannedFrame::Passthrough {
                    data,
                    profile,
                    uncompressed_bytes: frame_uncompressed_bytes,
                } => {
                    ensure_consistent_pixel_profile(
                        &mut pixel_profile,
                        *profile,
                        "JPEG passthrough pixel profile changed across frames",
                    )?;
                    compressed_bytes = compressed_bytes
                        .saturating_add(u64::try_from(data.len()).unwrap_or(u64::MAX));
                    uncompressed_bytes =
                        uncompressed_bytes.saturating_add(*frame_uncompressed_bytes);
                    let byte_started = Instant::now();
                    pixel_spool.push_frame(data)?;
                    metrics.record_write_duration(byte_started.elapsed());
                    metrics.record_passthrough_frame();
                    metrics.record_pixel_profile(*profile);
                    index += 1;
                }
                JpegBaselinePlannedFrame::Retile {
                    data,
                    profile,
                    uncompressed_bytes: frame_uncompressed_bytes,
                    retile_duration,
                } => {
                    ensure_consistent_pixel_profile(
                        &mut pixel_profile,
                        *profile,
                        "JPEG retile pixel profile changed across frames",
                    )?;
                    compressed_bytes = compressed_bytes
                        .saturating_add(u64::try_from(data.len()).unwrap_or(u64::MAX));
                    uncompressed_bytes =
                        uncompressed_bytes.saturating_add(*frame_uncompressed_bytes);
                    let byte_started = Instant::now();
                    pixel_spool.push_frame(data)?;
                    metrics.record_write_duration(byte_started.elapsed());
                    metrics.record_jpeg_retile_baseline_frame(*retile_duration);
                    metrics.record_pixel_profile(*profile);
                    index += 1;
                }
                JpegBaselinePlannedFrame::Blank {
                    data,
                    profile,
                    uncompressed_bytes: frame_uncompressed_bytes,
                    encode_duration,
                } => {
                    ensure_consistent_pixel_profile(
                        &mut pixel_profile,
                        *profile,
                        "blank JPEG Baseline pixel profile changed across frames",
                    )?;
                    compressed_bytes = compressed_bytes
                        .saturating_add(u64::try_from(data.len()).unwrap_or(u64::MAX));
                    uncompressed_bytes =
                        uncompressed_bytes.saturating_add(*frame_uncompressed_bytes);
                    let byte_started = Instant::now();
                    pixel_spool.push_frame(data)?;
                    metrics.record_write_duration(byte_started.elapsed());
                    metrics.record_cpu_input();
                    metrics.record_pixel_profile(*profile);
                    metrics.record_transcode_route(false, false);
                    metrics.record_jpeg_decode_fallback();
                    metrics.record_jpeg_cpu_encode(*encode_duration);
                    index += 1;
                }
                JpegBaselinePlannedFrame::Fallback(_) => {
                    let (next_index, fallback_frames) = jpeg_baseline_fallback_run(&planned, index);
                    index = next_index;

                    let mut fallback_batch = prepare_jpeg_baseline_fallback_batch_for_options(
                        slide,
                        #[cfg(all(feature = "metal", target_os = "macos"))]
                        &mut metal_input,
                        level,
                        location,
                        row,
                        &fallback_frames,
                        &request.options,
                        frame_columns,
                        frame_rows,
                        &mut metrics,
                    )?;

                    for (idx, metal_encoded) in
                        fallback_batch.metal_run.frames.iter_mut().enumerate()
                    {
                        let (
                            encoded,
                            profile,
                            input_decode_duration,
                            compose_duration,
                            encode_duration,
                        ) = take_consistent_jpeg_baseline_fallback_frame(
                            metal_encoded,
                            &mut fallback_batch.cpu_batch_results[idx],
                            request.options.encode_backend,
                            &mut pixel_profile,
                            "JPEG Baseline pixel profile changed across frames",
                        )?;
                        compressed_bytes = compressed_bytes
                            .saturating_add(u64::try_from(encoded.data.len()).unwrap_or(u64::MAX));
                        uncompressed_bytes = uncompressed_bytes.saturating_add(
                            jpeg_baseline_fallback_uncompressed_bytes(
                                frame_columns,
                                frame_rows,
                                profile,
                            )?,
                        );
                        let byte_started = Instant::now();
                        pixel_spool.push_frame(&encoded.data)?;
                        metrics.record_write_duration(byte_started.elapsed());
                        let encoded_on_device = jpeg_backend_uses_device(encoded.backend);
                        if encoded_on_device {
                            metrics.record_gpu_input();
                        } else {
                            metrics.record_cpu_input();
                        }
                        metrics.record_pixel_profile(profile);
                        metrics.record_transcode_route(encoded_on_device, encoded_on_device);
                        metrics.record_jpeg_decode_fallback();
                        metrics.record_input_decode_duration(input_decode_duration);
                        metrics.record_compose_duration(compose_duration);
                        match encoded.backend {
                            JpegBackend::Cpu | JpegBackend::Auto => {
                                metrics.record_jpeg_cpu_encode(encode_duration);
                            }
                            JpegBackend::Metal | JpegBackend::Cuda => {}
                        }
                    }
                }
            }
        }
    }

    let profile = pixel_profile.ok_or_else(|| Error::Unsupported {
        reason: "slide level produced no frames".into(),
    })?;
    let object = context.build_dicom_object(InstanceDicomObjectParams {
        metadata,
        study_uid: identity.study_uid(),
        instance_number,
        frame_grid: FrameGrid {
            frame_columns,
            frame_rows,
            matrix_columns,
            matrix_rows,
        },
        frame_count,
        profile,
        pixel_data_offsets: PixelDataOffsetTables {
            offsets: pixel_spool.offsets(),
            lengths: pixel_spool.lengths(),
        },
        icc_profile: icc_profile.bytes.as_deref(),
        lossy_compression: Some(LossyCompressionMetadata {
            method: "ISO_10918_1",
            ratio: (compressed_bytes > 0)
                .then_some(uncompressed_bytes as f64 / compressed_bytes as f64),
        }),
    })?;
    let write_started = Instant::now();
    write_dicom_object_with_spooled_pixel_data(
        &context.path,
        object,
        context.file_meta(request.options.transfer_syntax.uid()),
        request.options.overwrite,
        &mut pixel_spool,
    )?;
    metrics.record_write_duration(write_started.elapsed());

    Ok(context.report(
        request.options.transfer_syntax.uid(),
        frame_count,
        icc_profile.source,
        metrics,
    ))
}