- add doc strings
This commit is contained in:
1 parent
b950f4a39d
commit
5ad9a44ecd
16 files changed
+993
-428
No files matched your search
+14
-319
@@ -1,9 +1,10 @@
|
||||
use crate::axes::{Ax, Axis, Operation, Shape, Slice, SliceInfoElemDef, slice_info};
|
||||
use crate::cache::{ArrayCache, ArrayKeyRef, FrameCache, FrameKeyRef, ReaderKeyRef};
|
||||
use crate::error::Error;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::readers::{Dimensions, DynReader, Frame, Reader};
|
||||
use crate::stats::MinMax;
|
||||
use indexmap::{Equivalent, IndexMap};
|
||||
use indexmap::IndexMap;
|
||||
use itertools::{Itertools, iproduct};
|
||||
use ndarray::{
|
||||
Array, Array0, Array1, Array2, ArrayD, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix5, IxDyn,
|
||||
@@ -13,15 +14,15 @@ use num::traits::ToBytes;
|
||||
use num::{Bounded, FromPrimitive, ToPrimitive, Zero};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
use std::any::{Any, type_name};
|
||||
use std::any::type_name;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter::Sum;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{AddAssign, Deref, Div};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn idx_bnd(idx: isize, bnd: isize) -> Result<isize, Error> {
|
||||
if idx < -bnd {
|
||||
@@ -47,6 +48,7 @@ fn slc_bnd(idx: isize, bnd: isize) -> Result<isize, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
/// a trait for numeric types that can be used as pixel values
|
||||
pub trait Number:
|
||||
'static
|
||||
+ Send
|
||||
@@ -74,318 +76,6 @@ impl<T> Number for T where
|
||||
{
|
||||
}
|
||||
|
||||
/// maximum number of frames held in the cache
|
||||
const DEFAULT_FRAME_CACHE_SIZE: usize = 128;
|
||||
|
||||
/// maximum number of materialized arrays held in the cache
|
||||
const DEFAULT_ARRAY_CACHE_SIZE: usize = 2;
|
||||
|
||||
/// identity of the reader a frame was read from
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct ReaderKey {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
series: usize,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
/// borrowed view of [`ReaderKey`] for cache lookups without allocation.
|
||||
/// hashes byte-identically to [`ReaderKey`] (str/String and Path/PathBuf hash
|
||||
/// the same), so it only matches the same reader identity.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct ReaderKeyRef<'a> {
|
||||
name: &'a str,
|
||||
path: &'a Path,
|
||||
series: usize,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
impl Hash for ReaderKeyRef<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
self.path.hash(state);
|
||||
self.series.hash(state);
|
||||
self.position.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<ReaderKey> for ReaderKeyRef<'_> {
|
||||
fn equivalent(&self, key: &ReaderKey) -> bool {
|
||||
self.name == key.name
|
||||
&& self.path == key.path.as_path()
|
||||
&& self.series == key.series
|
||||
&& self.position == key.position
|
||||
}
|
||||
}
|
||||
|
||||
impl ReaderKeyRef<'_> {
|
||||
fn to_owned(&self) -> ReaderKey {
|
||||
ReaderKey {
|
||||
name: self.name.to_string(),
|
||||
path: self.path.to_path_buf(),
|
||||
series: self.series,
|
||||
position: self.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FrameKey = (ReaderKey, usize, usize, usize);
|
||||
|
||||
/// borrowed view of [`FrameKey`] for cache lookups without allocation.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct FrameKeyRef<'a> {
|
||||
reader: ReaderKeyRef<'a>,
|
||||
c: usize,
|
||||
z: usize,
|
||||
t: usize,
|
||||
}
|
||||
|
||||
impl Hash for FrameKeyRef<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.reader.hash(state);
|
||||
self.c.hash(state);
|
||||
self.z.hash(state);
|
||||
self.t.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<FrameKey> for FrameKeyRef<'_> {
|
||||
fn equivalent(&self, key: &FrameKey) -> bool {
|
||||
let (rk, c, z, t) = key;
|
||||
self.reader.equivalent(rk) && self.c == *c && self.z == *z && self.t == *t
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameKeyRef<'_> {
|
||||
fn to_owned(&self) -> FrameKey {
|
||||
(self.reader.to_owned(), self.c, self.z, self.t)
|
||||
}
|
||||
}
|
||||
|
||||
/// identity of a materialized array in the process-wide cache
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct ArrayKey {
|
||||
reader: ReaderKey,
|
||||
dtype: &'static str,
|
||||
slice: Vec<SliceInfoElem>,
|
||||
axes: Vec<Axis>,
|
||||
operations: Vec<(Axis, Operation)>,
|
||||
}
|
||||
|
||||
/// borrowed view of [`ArrayKey`] for cache lookups without allocation.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct ArrayKeyRef<'a> {
|
||||
reader: ReaderKeyRef<'a>,
|
||||
dtype: &'static str,
|
||||
slice: &'a [SliceInfoElem],
|
||||
axes: &'a [Axis],
|
||||
operations: &'a IndexMap<Axis, Operation>,
|
||||
}
|
||||
|
||||
impl Hash for ArrayKeyRef<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.reader.hash(state);
|
||||
self.dtype.hash(state);
|
||||
self.slice.hash(state);
|
||||
self.axes.hash(state);
|
||||
self.operations.len().hash(state);
|
||||
for (ax, op) in self.operations.iter() {
|
||||
ax.hash(state);
|
||||
op.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<ArrayKey> for ArrayKeyRef<'_> {
|
||||
fn equivalent(&self, key: &ArrayKey) -> bool {
|
||||
self.reader.equivalent(&key.reader)
|
||||
&& self.dtype == key.dtype
|
||||
&& self.slice == key.slice
|
||||
&& self.axes == key.axes
|
||||
&& self.operations.len() == key.operations.len()
|
||||
&& self
|
||||
.operations
|
||||
.iter()
|
||||
.zip(&key.operations)
|
||||
.all(|((ax, op), (key_ax, key_op))| ax == key_ax && op == key_op)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayKeyRef<'_> {
|
||||
fn to_owned(&self) -> ArrayKey {
|
||||
ArrayKey {
|
||||
reader: self.reader.to_owned(),
|
||||
dtype: self.dtype,
|
||||
slice: self.slice.to_vec(),
|
||||
axes: self.axes.to_vec(),
|
||||
operations: self
|
||||
.operations
|
||||
.iter()
|
||||
.map(|(ax, op)| (*ax, op.clone()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// process-wide LRU cache of frames, shared between all views and threads
|
||||
static GLOBAL_FRAME_CACHE: OnceLock<FrameCache> = OnceLock::new();
|
||||
|
||||
/// thread-safe LRU cache of frames read from the underlying reader
|
||||
struct FrameCache {
|
||||
inner: Mutex<FrameCacheInner>,
|
||||
}
|
||||
|
||||
struct FrameCacheInner {
|
||||
map: IndexMap<FrameKey, Arc<Frame>>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl Default for FrameCache {
|
||||
fn default() -> Self {
|
||||
Self::new(DEFAULT_FRAME_CACHE_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameCache {
|
||||
fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(FrameCacheInner {
|
||||
map: IndexMap::with_capacity(capacity),
|
||||
capacity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn global() -> &'static FrameCache {
|
||||
GLOBAL_FRAME_CACHE.get_or_init(FrameCache::default)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.inner.lock().unwrap().capacity
|
||||
}
|
||||
|
||||
fn set_capacity(&self, capacity: usize) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.capacity = capacity;
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn get<Q>(&self, key: &Q) -> Option<Arc<Frame>>
|
||||
where
|
||||
Q: ?Sized + Hash + Equivalent<FrameKey>,
|
||||
{
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(idx) = inner.map.get_index_of(key) {
|
||||
let (key, frame) = inner.map.shift_remove_index(idx).unwrap();
|
||||
inner.map.insert(key, frame.clone());
|
||||
Some(frame)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&self, key: FrameKey, frame: Arc<Frame>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.map.insert(key, frame);
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn len(&self) -> usize {
|
||||
self.inner.lock().unwrap().map.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FrameCache {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FrameCache").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// process-wide LRU cache of materialized arrays, shared between all views and threads
|
||||
static GLOBAL_ARRAY_CACHE: OnceLock<ArrayCache> = OnceLock::new();
|
||||
|
||||
/// thread-safe LRU cache of materialized arrays produced by `as_array_dyn`
|
||||
struct ArrayCache {
|
||||
inner: Mutex<ArrayCacheInner>,
|
||||
}
|
||||
|
||||
struct ArrayCacheInner {
|
||||
map: IndexMap<ArrayKey, Arc<dyn Any + Send + Sync>>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl Default for ArrayCache {
|
||||
fn default() -> Self {
|
||||
Self::new(DEFAULT_ARRAY_CACHE_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayCache {
|
||||
fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(ArrayCacheInner {
|
||||
map: IndexMap::with_capacity(capacity),
|
||||
capacity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn global() -> &'static ArrayCache {
|
||||
GLOBAL_ARRAY_CACHE.get_or_init(ArrayCache::default)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.inner.lock().unwrap().capacity
|
||||
}
|
||||
|
||||
fn set_capacity(&self, capacity: usize) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.capacity = capacity;
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn get<T, Q>(&self, key: &Q) -> Option<Arc<ArrayD<T>>>
|
||||
where
|
||||
Q: ?Sized + Hash + Equivalent<ArrayKey>,
|
||||
T: Any + Send + Sync,
|
||||
{
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(idx) = inner.map.get_index_of(key) {
|
||||
let (key, array) = inner.map.shift_remove_index(idx).unwrap();
|
||||
inner.map.insert(key, array.clone());
|
||||
array.downcast::<ArrayD<T>>().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn insert<T: Any + Send + Sync>(&self, key: ArrayKey, array: ArrayD<T>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.map.insert(key, Arc::new(array));
|
||||
while inner.map.len() > inner.capacity {
|
||||
inner.map.shift_remove_index(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn len(&self) -> usize {
|
||||
self.inner.lock().unwrap().map.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ArrayCache {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ArrayCache").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// sliceable view on an image file
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -634,6 +324,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
self.shape()[0]
|
||||
}
|
||||
|
||||
/// whether the view has no pixels in the first dimension
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.shape()[0] == 0
|
||||
}
|
||||
@@ -643,6 +334,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
self.shape().into_iter().product()
|
||||
}
|
||||
|
||||
/// the size of the view along an axis, or `None` if the axis is not present
|
||||
pub fn size_ax(&self, ax: Axis) -> Option<usize> {
|
||||
self.axes()
|
||||
.iter()
|
||||
@@ -745,6 +437,7 @@ impl<D: Dimension, R: Reader> View<D, R> {
|
||||
.with_operations(self.operations.clone()))
|
||||
}
|
||||
|
||||
/// reduce an axis with an operation (max, min, sum or mean), returning a view of lower dimensionality
|
||||
pub fn operate<A: Ax>(
|
||||
&self,
|
||||
axis: A,
|
||||
@@ -1450,6 +1143,7 @@ where
|
||||
|
||||
/// trait to define a function to retrieve the only item in a 0d array
|
||||
pub trait Item {
|
||||
/// the single item in the 0d array, cast to `T`
|
||||
fn item<T>(&self) -> Result<T, Error>
|
||||
where
|
||||
T: Number,
|
||||
@@ -1459,6 +1153,7 @@ pub trait Item {
|
||||
}
|
||||
|
||||
impl<R: Reader> View<Ix5, R> {
|
||||
/// create a view on the image at `path`, parsing series and position from the file name
|
||||
pub fn from_path<P>(path: P) -> Result<Self, Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
@@ -1492,6 +1187,7 @@ impl<D: Dimension, R: Reader> Display for View<D, R> {
|
||||
|
||||
/// trait to convert numbers to bytes
|
||||
pub trait ToBytesVec {
|
||||
/// the number as a vector of bytes in native endianness
|
||||
fn to_bytes_vec(&self) -> Vec<u8>;
|
||||
}
|
||||
|
||||
@@ -1516,12 +1212,11 @@ to_bytes_vec_impl!(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::axes::{Axis, Operation};
|
||||
use crate::cache::{ArrayCache, ArrayKey, ArrayKeyRef, FrameCache, ReaderKey, ReaderKeyRef};
|
||||
use crate::error::Error;
|
||||
use crate::readers::{DynReader, Frame, Reader};
|
||||
use crate::stats::MinMax;
|
||||
use crate::view::{
|
||||
ArrayCache, ArrayKey, ArrayKeyRef, FrameCache, Item, ReaderKey, ReaderKeyRef,
|
||||
};
|
||||
use crate::view::Item;
|
||||
use indexmap::IndexMap;
|
||||
use ndarray::{Array, Array4, Array5, NewAxis};
|
||||
use ndarray::{Array2, ArrayD, IxDyn, SliceInfoElem, s};
|
||||
|
||||
Reference in new issue
Block a user