use super::*;
pub trait FormatProbe: Send + Sync {
fn probe(&self, path: &Path) -> Result<ProbeResult, WsiError>;
}
#[derive(Debug)]
#[non_exhaustive]
pub struct ProbeResult {
pub detected: bool,
pub vendor: String,
pub confidence: ProbeConfidence,
}
impl ProbeResult {
pub fn detected(vendor: impl Into<String>, confidence: ProbeConfidence) -> Self {
Self {
detected: true,
vendor: vendor.into(),
confidence,
}
}
pub fn not_detected(vendor: impl Into<String>) -> Self {
Self {
detected: false,
vendor: vendor.into(),
confidence: ProbeConfidence::Likely,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[non_exhaustive]
pub enum ProbeConfidence {
Definite,
Likely,
}
pub trait DatasetReader: Send + Sync {
fn open(&self, path: &Path) -> Result<Box<dyn SlideReader>, WsiError>;
}
pub struct SlideReadContext<'a> {
tile_cache: Option<&'a TileCache>,
output: TileOutputPreference,
max_region_pixels: u64,
}
impl<'a> SlideReadContext<'a> {
pub(crate) fn new(
tile_cache: Option<&'a TileCache>,
output: TileOutputPreference,
max_region_pixels: u64,
) -> Self {
Self {
tile_cache,
output,
max_region_pixels,
}
}
pub(crate) fn tile_cache(&self) -> Option<&'a TileCache> {
self.tile_cache
}
pub fn output(&self) -> &TileOutputPreference {
&self.output
}
pub fn max_region_pixels(&self) -> u64 {
self.max_region_pixels
}
}
pub trait SlideReader: Send + Sync {
fn dataset(&self) -> &Dataset;
fn tile_codec_kind(&self, _req: &TileRequest) -> TileCodecKind {
TileCodecKind::Other
}
fn level_source_kind(
&self,
scene: SceneId,
series: SeriesId,
level: LevelIdx,
) -> Result<LevelSourceKind, WsiError> {
let dataset = self.dataset();
let scene_ref = dataset
.scenes
.get(scene.get())
.ok_or(WsiError::SceneOutOfRange {
index: scene.get(),
count: dataset.scenes.len(),
})?;
let series_ref = scene_ref
.series
.get(series.get())
.ok_or(WsiError::SeriesOutOfRange {
index: series.get(),
count: scene_ref.series.len(),
})?;
if level.get() as usize >= series_ref.levels.len() {
return Err(WsiError::LevelOutOfRange {
level: level.get(),
count: series_ref.levels.len() as u32,
});
}
Ok(LevelSourceKind::Physical)
}
fn read_tiles(
&self,
reqs: &[TileRequest],
output: TileOutputPreference,
) -> Result<Vec<TilePixels>, WsiError> {
if matches!(output, TileOutputPreference::RequireDevice { .. }) {
return Err(WsiError::Unsupported {
reason: "RequireDevice not supported by this reader in Phase 2".into(),
});
}
reqs.iter()
.map(|req| self.read_tile_cpu(req).map(TilePixels::Cpu))
.collect()
}
fn read_tile(
&self,
req: &TileRequest,
output: TileOutputPreference,
) -> Result<TilePixels, WsiError> {
let mut tiles = self.read_tiles(std::slice::from_ref(req), output)?;
match tiles.len() {
1 => Ok(tiles.remove(0)),
0 => Err(WsiError::TileRead {
col: req.col,
row: req.row,
level: req.level.get(),
reason: "empty tile batch result".into(),
}),
count => Err(WsiError::TileRead {
col: req.col,
row: req.row,
level: req.level.get(),
reason: format!("single tile read returned {count} tiles"),
}),
}
}
fn read_tile_cpu(&self, req: &TileRequest) -> Result<CpuTile, WsiError>;
fn read_raw_compressed_tile(&self, req: &TileRequest) -> Result<RawCompressedTile, WsiError> {
Err(WsiError::Unsupported {
reason: format!(
"raw compressed tile access is not available for tile ({}, {}) at level {}",
req.col,
req.row,
req.level.get()
),
})
}
fn read_raw_compressed_display_tile(
&self,
req: &TileViewRequest,
) -> Result<RawCompressedTile, WsiError> {
Err(WsiError::Unsupported {
reason: format!(
"raw compressed display tile access is not available for tile ({}, {}) at level {}",
req.col,
req.row,
req.level.get()
),
})
}
fn read_tiles_cpu(&self, reqs: &[TileRequest]) -> Result<Vec<CpuTile>, WsiError> {
self.read_tiles(reqs, TileOutputPreference::cpu())?
.into_iter()
.map(|tile| match tile {
TilePixels::Cpu(cpu) => Ok(cpu),
TilePixels::Device(_) => Err(WsiError::Unsupported {
reason: "CPU tile request returned device payload".into(),
}),
})
.collect()
}
fn use_display_tile_cache(&self, _req: &TileViewRequest) -> bool {
true
}
fn read_region_fastpath(
&self,
_ctx: &mut SlideReadContext<'_>,
_req: &RegionRequest,
) -> Option<Result<CpuTile, WsiError>> {
None
}
fn read_region(
&self,
req: &RegionRequest,
output: TileOutputPreference,
) -> Result<TilePixels, WsiError> {
if matches!(output, TileOutputPreference::RequireDevice { .. }) {
return Err(WsiError::Unsupported {
reason: "region requires CPU composition; RequireDevice not supported in Phase 2"
.into(),
});
}
composite_region_from_source(self, None, req, DEFAULT_MAX_REGION_PIXELS)
.map(TilePixels::Cpu)
}
fn read_display_tile(&self, req: &TileViewRequest) -> Result<CpuTile, WsiError> {
read_display_tile_from_source(self, None, req, TileOutputPreference::cpu())
}
fn associated_image(&self, name: &str) -> Result<Option<CpuTile>, WsiError> {
match self.read_associated(name) {
Ok(tile) => Ok(Some(tile)),
Err(WsiError::AssociatedImageNotFound(_)) => Ok(None),
Err(err) => Err(err),
}
}
fn read_associated(&self, name: &str) -> Result<CpuTile, WsiError>;
fn recommended_shared_cache_bytes(&self) -> Option<u64> {
None
}
}