1use super::*;
2
3pub trait FormatProbe: Send + Sync {
7 fn probe(&self, path: &Path) -> Result<ProbeResult, WsiError>;
8}
9
10#[derive(Debug)]
12#[non_exhaustive]
13pub struct ProbeResult {
14 pub detected: bool,
15 pub vendor: String,
16 pub confidence: ProbeConfidence,
17}
18
19impl ProbeResult {
20 pub fn detected(vendor: impl Into<String>, confidence: ProbeConfidence) -> Self {
22 Self {
23 detected: true,
24 vendor: vendor.into(),
25 confidence,
26 }
27 }
28
29 pub fn not_detected(vendor: impl Into<String>) -> Self {
33 Self {
34 detected: false,
35 vendor: vendor.into(),
36 confidence: ProbeConfidence::Likely,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, Eq, PartialEq)]
42#[non_exhaustive]
43pub enum ProbeConfidence {
44 Definite,
45 Likely,
46}
47
48pub trait DatasetReader: Send + Sync {
50 fn open(&self, path: &Path) -> Result<Box<dyn SlideReader>, WsiError>;
51}
52
53pub struct SlideReadContext<'a> {
56 tile_cache: Option<&'a TileCache>,
57 output: TileOutputPreference,
58 max_region_pixels: u64,
59}
60
61impl<'a> SlideReadContext<'a> {
62 pub(crate) fn new(
63 tile_cache: Option<&'a TileCache>,
64 output: TileOutputPreference,
65 max_region_pixels: u64,
66 ) -> Self {
67 Self {
68 tile_cache,
69 output,
70 max_region_pixels,
71 }
72 }
73
74 pub(crate) fn tile_cache(&self) -> Option<&'a TileCache> {
75 self.tile_cache
76 }
77
78 pub fn output(&self) -> &TileOutputPreference {
79 &self.output
80 }
81
82 pub fn max_region_pixels(&self) -> u64 {
83 self.max_region_pixels
84 }
85}
86
87pub trait SlideReader: Send + Sync {
132 fn dataset(&self) -> &Dataset;
133 fn tile_codec_kind(&self, _req: &TileRequest) -> TileCodecKind {
134 TileCodecKind::Other
135 }
136 fn level_source_kind(
137 &self,
138 scene: SceneId,
139 series: SeriesId,
140 level: LevelIdx,
141 ) -> Result<LevelSourceKind, WsiError> {
142 let dataset = self.dataset();
143 let scene_ref = dataset
144 .scenes
145 .get(scene.get())
146 .ok_or(WsiError::SceneOutOfRange {
147 index: scene.get(),
148 count: dataset.scenes.len(),
149 })?;
150 let series_ref = scene_ref
151 .series
152 .get(series.get())
153 .ok_or(WsiError::SeriesOutOfRange {
154 index: series.get(),
155 count: scene_ref.series.len(),
156 })?;
157 if level.get() as usize >= series_ref.levels.len() {
158 return Err(WsiError::LevelOutOfRange {
159 level: level.get(),
160 count: series_ref.levels.len() as u32,
161 });
162 }
163 Ok(LevelSourceKind::Physical)
164 }
165 fn read_tiles(
166 &self,
167 reqs: &[TileRequest],
168 output: TileOutputPreference,
169 ) -> Result<Vec<TilePixels>, WsiError> {
170 if matches!(output, TileOutputPreference::RequireDevice { .. }) {
171 return Err(WsiError::Unsupported {
172 reason: "RequireDevice not supported by this reader in Phase 2".into(),
173 });
174 }
175 reqs.iter()
176 .map(|req| self.read_tile_cpu(req).map(TilePixels::Cpu))
177 .collect()
178 }
179 fn read_tile(
180 &self,
181 req: &TileRequest,
182 output: TileOutputPreference,
183 ) -> Result<TilePixels, WsiError> {
184 let mut tiles = self.read_tiles(std::slice::from_ref(req), output)?;
185 match tiles.len() {
186 1 => Ok(tiles.remove(0)),
187 0 => Err(WsiError::TileRead {
188 col: req.col,
189 row: req.row,
190 level: req.level.get(),
191 reason: "empty tile batch result".into(),
192 }),
193 count => Err(WsiError::TileRead {
194 col: req.col,
195 row: req.row,
196 level: req.level.get(),
197 reason: format!("single tile read returned {count} tiles"),
198 }),
199 }
200 }
201 fn read_tile_cpu(&self, req: &TileRequest) -> Result<CpuTile, WsiError>;
202 fn read_raw_compressed_tile(&self, req: &TileRequest) -> Result<RawCompressedTile, WsiError> {
203 Err(WsiError::Unsupported {
204 reason: format!(
205 "raw compressed tile access is not available for tile ({}, {}) at level {}",
206 req.col,
207 req.row,
208 req.level.get()
209 ),
210 })
211 }
212 fn read_raw_compressed_display_tile(
213 &self,
214 req: &TileViewRequest,
215 ) -> Result<RawCompressedTile, WsiError> {
216 Err(WsiError::Unsupported {
217 reason: format!(
218 "raw compressed display tile access is not available for tile ({}, {}) at level {}",
219 req.col,
220 req.row,
221 req.level.get()
222 ),
223 })
224 }
225 fn read_tiles_cpu(&self, reqs: &[TileRequest]) -> Result<Vec<CpuTile>, WsiError> {
226 self.read_tiles(reqs, TileOutputPreference::cpu())?
227 .into_iter()
228 .map(|tile| match tile {
229 TilePixels::Cpu(cpu) => Ok(cpu),
230 TilePixels::Device(_) => Err(WsiError::Unsupported {
231 reason: "CPU tile request returned device payload".into(),
232 }),
233 })
234 .collect()
235 }
236 fn use_display_tile_cache(&self, _req: &TileViewRequest) -> bool {
237 true
238 }
239 fn read_region_fastpath(
240 &self,
241 _ctx: &mut SlideReadContext<'_>,
242 _req: &RegionRequest,
243 ) -> Option<Result<CpuTile, WsiError>> {
244 None
245 }
246 fn read_region(
247 &self,
248 req: &RegionRequest,
249 output: TileOutputPreference,
250 ) -> Result<TilePixels, WsiError> {
251 if matches!(output, TileOutputPreference::RequireDevice { .. }) {
252 return Err(WsiError::Unsupported {
253 reason: "region requires CPU composition; RequireDevice not supported in Phase 2"
254 .into(),
255 });
256 }
257 composite_region_from_source(self, None, req, DEFAULT_MAX_REGION_PIXELS)
258 .map(TilePixels::Cpu)
259 }
260 fn read_display_tile(&self, req: &TileViewRequest) -> Result<CpuTile, WsiError> {
261 read_display_tile_from_source(self, None, req, TileOutputPreference::cpu())
262 }
263 fn associated_image(&self, name: &str) -> Result<Option<CpuTile>, WsiError> {
264 match self.read_associated(name) {
265 Ok(tile) => Ok(Some(tile)),
266 Err(WsiError::AssociatedImageNotFound(_)) => Ok(None),
267 Err(err) => Err(err),
268 }
269 }
270 fn read_associated(&self, name: &str) -> Result<CpuTile, WsiError>;
271 fn recommended_shared_cache_bytes(&self) -> Option<u64> {
272 None
273 }
274}