Adding documentation, some code cleanups
* Not re-exporting SpaceSetObject outside of the crate * Removed unused SpaceObject definition * Factored out Point definition. * Remove `pub` visibility on definition not actually requiring it. * Added an assert for indexing into a Position, in the case where the position has only one dimension, the index MUST BE 0. * Commented `highest_resolution()` as this is not yet used.
This commit is contained in:
28
README.md
28
README.md
@@ -22,35 +22,9 @@ This enables the index implementations to be agnostic from the underlying data s
|
||||
|
||||
* Rust: https://www.rust-lang.org
|
||||
|
||||
## Quick start
|
||||
|
||||
## Building from sources
|
||||
|
||||
To build this project, you will need to run the following:
|
||||
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
### Installation
|
||||
|
||||
To install the software on the system you can use:
|
||||
|
||||
```sh
|
||||
cargo install --release
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
The binary `db-test` provided is used only as an integration test at this point. It will convert a json input to a binary representation, before building an index over it. Once this is achieved, it will run a couple of hard-coded queries over the index.
|
||||
|
||||
```sh
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For more information, please refer to the [documentation](https://epfl-dias.github.io/PROJECT_NAME/).
|
||||
For more information, please refer to the [documentation](https://epfl-dias.github.io/mercator_db/).
|
||||
|
||||
If you want to build the documentation and access it locally, you can use:
|
||||
|
||||
|
||||
@@ -9,15 +9,31 @@ use super::space_index::SpaceSetObject;
|
||||
use super::DataBase;
|
||||
use super::ResultSet;
|
||||
|
||||
/// Query Parameters.
|
||||
pub struct CoreQueryParameters<'a> {
|
||||
/// Database to use.
|
||||
pub db: &'a DataBase,
|
||||
/// Output reference space into which to convert results.
|
||||
pub output_space: Option<&'a str>,
|
||||
/// Volume value to use to select the index resolution.
|
||||
//FIXME: IS this necessary given view_port?
|
||||
pub threshold_volume: Option<f64>,
|
||||
/// Full definition of the view port, a.k.a the volume being
|
||||
/// displayed.
|
||||
pub view_port: &'a Option<(Vec<f64>, Vec<f64>)>,
|
||||
/// Index resolution to use.
|
||||
pub resolution: &'a Option<Vec<u32>>,
|
||||
}
|
||||
|
||||
impl CoreQueryParameters<'_> {
|
||||
/// Build a minimum bounding box out of the provided viewport, and
|
||||
/// rebase it in the target space.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `space`:
|
||||
/// Space to use for the encoded coordinates of the minimum
|
||||
/// bounding box.
|
||||
pub fn view_port(&self, space: &Space) -> Option<Shape> {
|
||||
if let Some((low, high)) = self.view_port {
|
||||
let view_port = Shape::BoundingBox(low.into(), high.into());
|
||||
@@ -31,14 +47,21 @@ impl CoreQueryParameters<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition of the volumetric objects identifiers.
|
||||
///
|
||||
/// We have two parts to it, first the *kind* and the actual, *id* used
|
||||
/// to distinguish different objects.
|
||||
// FIXME: Ids are expected unique, irrespective of the enum variant!
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub enum Properties {
|
||||
/// Spatial Features.
|
||||
Feature(String),
|
||||
/// Unoptimized arbitrary kind of *identifiers*.
|
||||
Unknown(String, String),
|
||||
}
|
||||
|
||||
impl Properties {
|
||||
/// Extract the *identifier* of this spatial object.
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
Properties::Feature(id) => id,
|
||||
@@ -46,6 +69,7 @@ impl Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the *kind* of spatial object.
|
||||
pub fn type_name(&self) -> &str {
|
||||
match self {
|
||||
Properties::Feature(_) => "Feature",
|
||||
@@ -53,6 +77,13 @@ impl Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiate a new *feature*.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`:
|
||||
/// The identifier of the object, which can be converted into a
|
||||
/// `String`.
|
||||
pub fn feature<S>(id: S) -> Properties
|
||||
where
|
||||
S: Into<String>,
|
||||
@@ -60,6 +91,17 @@ impl Properties {
|
||||
Properties::Feature(id.into())
|
||||
}
|
||||
|
||||
/// Instantiate a new arbitrary kind of object, with the given id.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`:
|
||||
/// The identifier of the object, which can be converted into a
|
||||
/// `String`.
|
||||
///
|
||||
/// * `type_name`:
|
||||
/// A value which can be converted into a `String`, and
|
||||
/// represent the **kind** of the object.
|
||||
pub fn unknown<S>(id: S, type_name: S) -> Properties
|
||||
where
|
||||
S: Into<String>,
|
||||
@@ -68,6 +110,7 @@ impl Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// Index over a single dataset
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Core {
|
||||
title: String,
|
||||
@@ -77,6 +120,43 @@ pub struct Core {
|
||||
}
|
||||
|
||||
impl Core {
|
||||
/// Instantiate a new index for a dataset.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `title`:
|
||||
/// The title to use for the new dataset.
|
||||
///
|
||||
/// * `version`:
|
||||
/// The revision of the new dataset.
|
||||
///
|
||||
/// * `spaces`:
|
||||
/// The list of reference spaces used within the dataset.
|
||||
///
|
||||
/// * `properties`:
|
||||
/// The *identifiers*, has an ordered list, which is referenced
|
||||
/// by the `space_objects` by offset within this list.
|
||||
///
|
||||
/// * `space_objects`:
|
||||
/// A list of links between volumetric positions and
|
||||
/// identifiers.
|
||||
///
|
||||
/// * `scales`:
|
||||
/// A list of resolutions for which to build indices. Each value
|
||||
/// represent the number of bits of precision to **remove** from
|
||||
/// the coordinates to build the index.
|
||||
///
|
||||
/// * `max_elements`:
|
||||
/// The minimum number of positions to use as a stopping
|
||||
/// condition while building automatically multiple resolutions
|
||||
/// of the index.
|
||||
///
|
||||
/// Each consecutive index will contains at most half the number
|
||||
/// of data points than the next finer-grained index.
|
||||
///
|
||||
/// The minimum number of elements contained within an index is
|
||||
/// this value or the number of *identifiers*, whichever is
|
||||
/// greater.
|
||||
pub fn new<S>(
|
||||
title: S,
|
||||
version: S,
|
||||
@@ -125,14 +205,17 @@ impl Core {
|
||||
})
|
||||
}
|
||||
|
||||
/// Title of the dataset.
|
||||
pub fn name(&self) -> &String {
|
||||
&self.title
|
||||
}
|
||||
|
||||
/// Revision of the dataset.
|
||||
pub fn version(&self) -> &String {
|
||||
&self.version
|
||||
}
|
||||
|
||||
/// List of *identifiers* contained in this dataset.
|
||||
pub fn keys(&self) -> &Vec<Properties> {
|
||||
&self.properties
|
||||
}
|
||||
@@ -164,13 +247,26 @@ impl Core {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Search by positions defining a volume.
|
||||
// Positions ARE DEFINED IN F64 VALUES IN THE SPACE. NOT ENCODED!
|
||||
/// Retrieve everything located at specific positions.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `parameters`:
|
||||
/// Search parameters, see [CoreQueryParameters](struct.CoreQueryParameters.html).
|
||||
///
|
||||
/// * `positions`:
|
||||
/// Volume to use to filter data points.
|
||||
///
|
||||
/// * `space_id`:
|
||||
/// *positions* are defined as decoded coordinates in this
|
||||
/// reference space.
|
||||
///
|
||||
/// [shape]: space/enum.Shape.html
|
||||
pub fn get_by_positions(
|
||||
&self,
|
||||
parameters: &CoreQueryParameters,
|
||||
positions: &[Position],
|
||||
from: &str,
|
||||
space_id: &str,
|
||||
) -> ResultSet {
|
||||
let CoreQueryParameters {
|
||||
db, output_space, ..
|
||||
@@ -178,7 +274,7 @@ impl Core {
|
||||
|
||||
let mut results = vec![];
|
||||
let count = positions.len();
|
||||
let from = db.space(from)?;
|
||||
let from = db.space(space_id)?;
|
||||
|
||||
// Filter positions based on the view port, if present
|
||||
let filtered = match parameters.view_port(from) {
|
||||
@@ -211,12 +307,21 @@ impl Core {
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
// Search by shape defining a volume:
|
||||
// * Hyperrectangle (MBB),
|
||||
// * HyperSphere (radius around a point),
|
||||
// * Point (Specific position)
|
||||
|
||||
// SHAPE IS DEFINED IN F64 VALUES IN THE SPACE. NOT ENCODED!
|
||||
/// Search using a [shape] which defines a volume.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `parameters`:
|
||||
/// Search parameters, see [CoreQueryParameters](struct.CoreQueryParameters.html).
|
||||
///
|
||||
/// * `shape`:
|
||||
/// Volume to use to filter data points.
|
||||
///
|
||||
/// * `space_id`:
|
||||
/// *shape* is defined as decoded coordinates in this
|
||||
/// reference space.
|
||||
///
|
||||
/// [shape]: space/enum.Shape.html
|
||||
pub fn get_by_shape(
|
||||
&self,
|
||||
parameters: &CoreQueryParameters,
|
||||
@@ -251,7 +356,16 @@ impl Core {
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
// Search by Id, a.k.a values
|
||||
/// Search by Id, a.k.a retrieve all the positions linked to this id.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `parameters`:
|
||||
/// Search parameters, see [CoreQueryParameters](struct.CoreQueryParameters.html).
|
||||
///
|
||||
/// * `id`:
|
||||
/// Identifier for which to retrieve is positions.
|
||||
///
|
||||
pub fn get_by_id<S>(
|
||||
&self,
|
||||
parameters: &CoreQueryParameters,
|
||||
@@ -305,8 +419,17 @@ impl Core {
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
// Search by Label, a.k.a within a volume defined by the positions of an Id.
|
||||
// FIXME: NEED TO KEEP TRACK OF SPACE IDS AND DO CONVERSIONS
|
||||
/// Search by label, a.k.a use an identifier to define the search
|
||||
/// volume.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `parameters`:
|
||||
/// Search parameters, see [CoreQueryParameters](struct.CoreQueryParameters.html).
|
||||
///
|
||||
/// * `id`:
|
||||
/// Identifier to use to define the search volume.
|
||||
///
|
||||
pub fn get_by_label<S>(&self, parameters: &CoreQueryParameters, id: S) -> ResultSet
|
||||
where
|
||||
S: Into<String>,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
mod db_core;
|
||||
pub mod space;
|
||||
mod space_db;
|
||||
mod space_index;
|
||||
pub(crate) mod space_index;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ironsea_index::Indexed;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::storage;
|
||||
pub use db_core::Core;
|
||||
@@ -14,27 +13,36 @@ pub use db_core::CoreQueryParameters;
|
||||
pub use db_core::Properties;
|
||||
use space::Position;
|
||||
use space::Space;
|
||||
pub use space_index::SpaceFields;
|
||||
pub use space_index::SpaceSetObject;
|
||||
|
||||
// (Space Name, Position, Fields)
|
||||
/// Selected tuples matching a query.
|
||||
///
|
||||
/// This is either:
|
||||
/// * `Err` with a reason stored as a `String`
|
||||
/// * `Ok`, with a vector of tuples defined as:
|
||||
/// `(Space Name, [(Position, Properties)])`
|
||||
pub type ResultSet<'r> = Result<Vec<(&'r String, Vec<(Position, &'r Properties)>)>, String>;
|
||||
pub type ReferenceSpaceIndex = ironsea_index_hashmap::Index<Space, String>;
|
||||
|
||||
type ReferenceSpaceIndex = ironsea_index_hashmap::Index<Space, String>;
|
||||
type CoreIndex = ironsea_index_hashmap::Index<Core, String>;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct SpaceObject {
|
||||
pub space_id: String,
|
||||
pub position: Position,
|
||||
pub value: Properties,
|
||||
}
|
||||
|
||||
/// Collection of datasets and their reference spaces.
|
||||
pub struct DataBase {
|
||||
reference_spaces: ReferenceSpaceIndex,
|
||||
cores: CoreIndex,
|
||||
}
|
||||
|
||||
impl DataBase {
|
||||
/// Instantiate a `DataBase` struct.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `spaces`:
|
||||
/// List of reference spaces.
|
||||
///
|
||||
/// * `cores`:
|
||||
/// List of datasets (cores) which will be queried through this
|
||||
/// `DataBase` struct.
|
||||
// TODO: Replace vectors with iterators?
|
||||
pub fn new(spaces: Vec<Space>, cores: Vec<Core>) -> Self {
|
||||
DataBase {
|
||||
reference_spaces: ReferenceSpaceIndex::new(spaces.into_iter()),
|
||||
@@ -42,6 +50,12 @@ impl DataBase {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a list of indices.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `indices`:
|
||||
/// The list of index file names to load.
|
||||
pub fn load(indices: &[&str]) -> Result<Self, String> {
|
||||
let mut spaces = HashMap::new();
|
||||
let mut cores = vec![];
|
||||
@@ -99,12 +113,17 @@ impl DataBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup a space within the reference spaces registered
|
||||
/// Returns an ordered list of the reference space names registered.
|
||||
pub fn space_keys(&self) -> &Vec<String> {
|
||||
self.reference_spaces.keys()
|
||||
}
|
||||
|
||||
// Lookup a space within the reference spaces registered
|
||||
/// Lookup a space within the reference spaces registered.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `name`:
|
||||
/// The name of the reference space to search for.
|
||||
pub fn space(&self, name: &str) -> Result<&Space, String> {
|
||||
if name == space::Space::universe().name() {
|
||||
Ok(space::Space::universe())
|
||||
@@ -115,12 +134,17 @@ impl DataBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup a space within the reference spaces registered
|
||||
/// Returns an ordered list of dataset (Core) names registered.
|
||||
pub fn core_keys(&self) -> &Vec<String> {
|
||||
self.cores.keys()
|
||||
}
|
||||
|
||||
// Lookup a dataset within the datasets registered
|
||||
/// Lookup a dataset within the datasets registered.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `name`:
|
||||
/// The name of the dataset (core) to search for.
|
||||
pub fn core(&self, name: &str) -> Result<&Core, String> {
|
||||
let r = self.cores.find(&name.to_string());
|
||||
|
||||
|
||||
@@ -4,11 +4,16 @@ use serde::Serialize;
|
||||
use super::coordinate::Coordinate;
|
||||
use super::position::Position;
|
||||
|
||||
/// Mathematical set numbers.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum NumberSet {
|
||||
/// [Natural numbers](https://en.wikipedia.org/wiki/Natural_number), here including **0**.
|
||||
N,
|
||||
/// [Integers](https://en.wikipedia.org/wiki/Integer).
|
||||
Z,
|
||||
/// [Rational](https://en.wikipedia.org/wiki/Rational_number) numbers.
|
||||
Q,
|
||||
/// [Real](https://en.wikipedia.org/wiki/Real_number) numbers.
|
||||
R,
|
||||
}
|
||||
|
||||
@@ -37,12 +42,19 @@ impl From<&NumberSet> for String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition of a fixed-precision, finite length axis.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct Graduation {
|
||||
/// Set of numbers allowed on the axis.
|
||||
pub set: NumberSet,
|
||||
/// Minimum value *inclusive*.
|
||||
pub minimum: f64,
|
||||
/// Maximum value *inclusive*.
|
||||
pub maximum: f64,
|
||||
/// Number of *ticks* or discrete values between `minimum` and
|
||||
/// `maximum`.
|
||||
pub steps: u64,
|
||||
/// Length between two distinct *ticks* on the axis.
|
||||
pub epsilon: f64,
|
||||
}
|
||||
|
||||
@@ -60,7 +72,7 @@ impl Graduation {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum UnitSI {
|
||||
enum UnitSI {
|
||||
// Partial list, which is tailored to the use case needs. Prevents possible
|
||||
// confusions between Mm and mm, for example.
|
||||
m,
|
||||
@@ -113,16 +125,45 @@ impl From<&str> for UnitSI {
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition of an axis of a base.
|
||||
///
|
||||
/// This links together valid values on this axis, as well as the
|
||||
/// direction in the Universe of the axis and the base length unit of
|
||||
/// the `1.0` value.
|
||||
// TODO: In the future this might become an Enum with AffineAxis, ArbitraryAxis, etc...
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct Axis {
|
||||
measurement_unit: UnitSI,
|
||||
graduation: Graduation,
|
||||
// Coordinates in Universe, expressed in f64, and in the Universe number of dimensions.
|
||||
pub unit_vector: Position,
|
||||
// Coordinates in Universe, expressed in f64, and in the Universe
|
||||
// number of dimensions.
|
||||
unit_vector: Position,
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
/// Instanciate a new Axis definition.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `unit`:
|
||||
/// SI Unit to use on this axis for the `1.0` value.
|
||||
/// See [measurement_unit](#method.measurement_unit).
|
||||
///
|
||||
/// * `unit_vector`:
|
||||
/// A vector providing the direction in the Universe space of
|
||||
/// this axis.
|
||||
///
|
||||
/// * `set`:
|
||||
/// The valid numbers on this axis.
|
||||
///
|
||||
/// * `minimum`:
|
||||
/// The minimum value described by this axis *included*.
|
||||
///
|
||||
/// * `maximum`:
|
||||
/// The maximum value described by this axis *included*.
|
||||
///
|
||||
/// * `steps`:
|
||||
/// The number of steps, or discrete *ticks* on this axis.
|
||||
pub fn new(
|
||||
unit: &str,
|
||||
unit_vector: Vec<f64>,
|
||||
@@ -142,20 +183,48 @@ impl Axis {
|
||||
})
|
||||
}
|
||||
|
||||
/// The unit, as in [SI unit] used on this axis, more specifically,
|
||||
/// a [metric prefix] of the **meter**.
|
||||
///
|
||||
/// Currently the following values are supported:
|
||||
/// * `m`
|
||||
/// * `dm`
|
||||
/// * `cm`
|
||||
/// * `mm`
|
||||
/// * `um`
|
||||
/// * `nm`
|
||||
/// * `pm`
|
||||
///
|
||||
/// [SI unit]: https://en.wikipedia.org/wiki/International_System_of_Units
|
||||
/// [metric prefix]: https://en.wikipedia.org/wiki/Metric_prefix
|
||||
pub fn measurement_unit(&self) -> &str {
|
||||
self.measurement_unit.to_str()
|
||||
}
|
||||
|
||||
/// The unit vector of the axis.
|
||||
///
|
||||
/// This vector is expressed in the Universe coordinate system.
|
||||
pub fn unit_vector(&self) -> &Position {
|
||||
&self.unit_vector
|
||||
}
|
||||
|
||||
/// The valid number range and properties on this axis.
|
||||
pub fn graduation(&self) -> &Graduation {
|
||||
&self.graduation
|
||||
}
|
||||
|
||||
// Project a point expressed in Universe coordinates from the origin of this
|
||||
// axis on this axis.
|
||||
/// Project a position on this axis.
|
||||
///
|
||||
/// The resulting coordinate is expressed as an encoded coordinate
|
||||
/// on this axis.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `position`:
|
||||
/// The position to project on this axis. It must be defined in
|
||||
/// Universe coordinates, but with any translations already
|
||||
/// applied so that the origin of the vector is the origin of
|
||||
/// this axis.
|
||||
pub fn project_in(&self, position: &Position) -> Result<Coordinate, String> {
|
||||
let max = self.graduation.maximum;
|
||||
let min = self.graduation.minimum;
|
||||
@@ -192,7 +261,19 @@ impl Axis {
|
||||
self.encode(d)
|
||||
}
|
||||
|
||||
// Convert a value on this axis to Universe coordinates, based from the origin of this axis.
|
||||
/// Convert an encoded coordinate expressed on this axis into a
|
||||
/// position.
|
||||
///
|
||||
/// The resulting position is expressed in the Universe reference
|
||||
/// space, but from the origin of this axis. Any required
|
||||
/// translation must be applied to this resulting position to obtain
|
||||
/// an absolute value in the Universe space.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `coordinate`:
|
||||
/// The coordinate to project out of this axis. It must be
|
||||
/// defined as an encoded coordinate on this axis.
|
||||
pub fn project_out(&self, coordinate: &Coordinate) -> Result<Position, String> {
|
||||
let d = self.decode(coordinate)?;
|
||||
|
||||
@@ -202,7 +283,13 @@ impl Axis {
|
||||
Ok(&self.unit_vector * d)
|
||||
}
|
||||
|
||||
// Value is expressed on the current Axis, not in absolute coordinates!
|
||||
/// Encode a coordinate expressed on this axis.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `val`:
|
||||
/// The coordinate to encode. It must be defined as a
|
||||
/// coordinate on this axis.
|
||||
pub fn encode(&self, val: f64) -> Result<Coordinate, String> {
|
||||
let max = self.graduation.maximum;
|
||||
let min = self.graduation.minimum;
|
||||
@@ -229,7 +316,13 @@ impl Axis {
|
||||
Ok(v.into())
|
||||
}
|
||||
|
||||
// Value is expressed on the current Axis, not in absolute coordinates!
|
||||
/// Decode a coordinate expressed on this axis.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `val`:
|
||||
/// The coordinate to decode. It must be defined as an encoded
|
||||
/// coordinate on this axis.
|
||||
pub fn decode(&self, val: &Coordinate) -> Result<f64, String> {
|
||||
let max = self.graduation.maximum;
|
||||
let min = self.graduation.minimum;
|
||||
|
||||
@@ -11,18 +11,36 @@ use std::ops::Sub;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Store efficiently a coordinate.
|
||||
///
|
||||
/// While you can manually create a `Coordinate` value directly, using
|
||||
/// the `From` trait will automatically choose the most efficient enum
|
||||
/// member to store the value. This it the recommended way of using this
|
||||
/// struct.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||
pub enum Coordinate {
|
||||
/// Encoded coordinates whose value is in the range `[0; 2^8[`.
|
||||
CoordinateU8(u8),
|
||||
/// Encoded coordinates whose value is in the range `[0; 2^16[`,
|
||||
/// but should be used only for the range `[2^8; 2^16[`.
|
||||
CoordinateU16(u16),
|
||||
/// Encoded coordinates whose value is in the range `[0; 2^32[`,
|
||||
/// but should be used only for the range `[2^16; 2^32[`.
|
||||
CoordinateU32(u32),
|
||||
/// Encoded coordinates whose value is in the range `[0; 2^64[`,
|
||||
/// but should be used only for the range `[2^32; 2^64[`.
|
||||
CoordinateU64(u64),
|
||||
// We currently assume that 2^64 is enough to store encoded position values per axis.
|
||||
//CoordinateU128(u128),
|
||||
/// Decoded coordinate value expressed as a floating point value over 64 bits.
|
||||
/// For details on the precision, please see the
|
||||
/// [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) reference.
|
||||
CoordinateF64(f64),
|
||||
}
|
||||
|
||||
impl Coordinate {
|
||||
/// Return the value as a `f64`, this may introduce a loss of
|
||||
/// precision for encoded values.
|
||||
pub fn f64(&self) -> f64 {
|
||||
match *self {
|
||||
Coordinate::CoordinateU8(v) => f64::from(v),
|
||||
@@ -33,6 +51,7 @@ impl Coordinate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the value as `u64`, this is valid only on encoded values.
|
||||
pub fn u64(&self) -> u64 {
|
||||
match *self {
|
||||
Coordinate::CoordinateU8(v) => u64::from(v),
|
||||
@@ -43,6 +62,8 @@ impl Coordinate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the value as `usize`, this is valid only on encoded
|
||||
/// values.
|
||||
pub fn as_usize(&self) -> usize {
|
||||
self.u64() as usize
|
||||
}
|
||||
|
||||
@@ -6,14 +6,39 @@ use super::coordinate::Coordinate;
|
||||
use super::position::Position;
|
||||
use super::MAX_K;
|
||||
|
||||
/// Kinds of space coordinate systems, or bases
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum CoordinateSystem {
|
||||
Universe { origin: Position },
|
||||
// Coordinates in Universe, expressed in f64, and in the Universe number of dimensions.
|
||||
AffineSystem { origin: Position, axes: Vec<Axis> },
|
||||
/// Absolute base, which allows to generate transformation between
|
||||
/// spaces by anchoring them relative to each other.
|
||||
Universe {
|
||||
/// A position which contains zeroes for all its coordinates,
|
||||
/// but has a coordinate per dimensions of the highest
|
||||
/// dimensions space referenced.
|
||||
origin: Position,
|
||||
},
|
||||
/// Base which needs only an affine transformation to map into the Universe.
|
||||
AffineSystem {
|
||||
/// Coordinates in Universe, expressed in f64, or decoded, and
|
||||
/// in the Universe number of dimensions.
|
||||
origin: Position,
|
||||
|
||||
/// The definition of the coordinate system, through its axes.
|
||||
axes: Vec<Axis>,
|
||||
},
|
||||
}
|
||||
|
||||
impl CoordinateSystem {
|
||||
/// Instantiate a new coordinate system.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `origin`:
|
||||
/// The translation vector in Universe coordinates of this
|
||||
/// base.
|
||||
///
|
||||
/// * `axes`:
|
||||
/// The list of axes defining the coordinate system.
|
||||
pub fn new(origin: Vec<f64>, axes: Vec<Axis>) -> Self {
|
||||
CoordinateSystem::AffineSystem {
|
||||
origin: origin.into(),
|
||||
@@ -21,6 +46,7 @@ impl CoordinateSystem {
|
||||
}
|
||||
}
|
||||
|
||||
/// The translation vector, in Universe coordinates.
|
||||
pub fn origin(&self) -> &Position {
|
||||
match self {
|
||||
CoordinateSystem::Universe { origin, .. } => origin,
|
||||
@@ -28,6 +54,7 @@ impl CoordinateSystem {
|
||||
}
|
||||
}
|
||||
|
||||
/// The axes definition of this base.
|
||||
pub fn axes(&self) -> &Vec<Axis> {
|
||||
match self {
|
||||
CoordinateSystem::Universe { .. } => {
|
||||
@@ -38,6 +65,7 @@ impl CoordinateSystem {
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of dimensions of positions within this base.
|
||||
pub fn dimensions(&self) -> usize {
|
||||
match self {
|
||||
CoordinateSystem::Universe { .. } => MAX_K,
|
||||
@@ -45,6 +73,10 @@ impl CoordinateSystem {
|
||||
}
|
||||
}
|
||||
|
||||
/// The smallest bounding box containing the whole base, expressed
|
||||
/// in decoded Universe coordinates.
|
||||
///
|
||||
// FIXME: Add the translation vector!
|
||||
pub fn bounding_box(&self) -> (Position, Position) {
|
||||
let mut low = Vec::with_capacity(self.dimensions());
|
||||
let mut high = Vec::with_capacity(self.dimensions());
|
||||
@@ -67,6 +99,9 @@ impl CoordinateSystem {
|
||||
(low.into(), high.into())
|
||||
}
|
||||
|
||||
/// The volume of this space.
|
||||
///
|
||||
// FIXME: This assumes orthogonal spaces!
|
||||
pub fn volume(&self) -> f64 {
|
||||
let (low, high) = self.bounding_box();
|
||||
let difference: Vec<_> = (high - low).into();
|
||||
@@ -80,8 +115,19 @@ impl CoordinateSystem {
|
||||
volume
|
||||
}
|
||||
|
||||
// The position is expressed in coordinates in the universe,
|
||||
// return a position in the current coordinate system.
|
||||
/// Rebase a position in this coordinate space.
|
||||
///
|
||||
/// Each coordinate is encoded individually, and a new `Position`
|
||||
/// is generated.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `position`:
|
||||
/// expressed in decoded Universe coordinates.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The encoded coordinates within this coordinate system.
|
||||
pub fn rebase(&self, position: &Position) -> Result<Position, String> {
|
||||
match self {
|
||||
CoordinateSystem::Universe { origin } => {
|
||||
@@ -106,8 +152,16 @@ impl CoordinateSystem {
|
||||
}
|
||||
}
|
||||
|
||||
// The position is expressed in coordinates in the current coordinate system,
|
||||
// return a position in Universe coordinates.
|
||||
/// Express the position in the Universe coordinate system.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `position`:
|
||||
/// expressed as an encoded coordinates in the coordinate system.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The position expressed in Universe decoded coordinates.
|
||||
pub fn absolute_position(&self, position: &Position) -> Result<Position, String> {
|
||||
match self {
|
||||
CoordinateSystem::Universe { origin } => {
|
||||
@@ -132,8 +186,19 @@ impl CoordinateSystem {
|
||||
}
|
||||
}
|
||||
|
||||
// The position is expressed in the current system
|
||||
// Encode each coordinate separately and return an encoded Position
|
||||
/// Encode a position expressed in the current coordinate system.
|
||||
///
|
||||
/// Each coordinate is encoded individually, and a new `Position`
|
||||
/// is generated.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `position`:
|
||||
/// expressed in the current coordinate system.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The encoded coordinates within this coordinate system.
|
||||
pub fn encode(&self, position: &[f64]) -> Result<Position, String> {
|
||||
let mut encoded = vec![];
|
||||
|
||||
@@ -155,8 +220,20 @@ impl CoordinateSystem {
|
||||
Ok(encoded.into())
|
||||
}
|
||||
|
||||
// The position is expressed in the current system as an encoded value,
|
||||
// return a position in the current system as f64 values.
|
||||
/// Decode a position expressed in the current coordinate system as
|
||||
/// an encoded value.
|
||||
///
|
||||
/// Each coordinate is decoded individually.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `position`:
|
||||
/// expressed in the current coordinate system, as encoded
|
||||
/// values.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The decoded coordinates within this coordinate system.
|
||||
pub fn decode(&self, position: &Position) -> Result<Vec<f64>, String> {
|
||||
let mut decoded = vec![];
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! Reference space definitions.
|
||||
//!
|
||||
//! This include notions such as shapes, positions, axes, etc…
|
||||
|
||||
mod axis;
|
||||
mod coordinate;
|
||||
mod coordinate_system;
|
||||
@@ -18,7 +22,14 @@ pub use coordinate_system::CoordinateSystem;
|
||||
pub use position::Position;
|
||||
pub use shape::Shape;
|
||||
|
||||
pub const MAX_K: usize = 3;
|
||||
// Maximum number of dimensions currently supported.
|
||||
//
|
||||
// **Note:** This will be deprecated as soon as support is implemented
|
||||
// in some dependencies. This is linked to limitations in
|
||||
// [ironsea_index_sfc_dbc].
|
||||
//
|
||||
// [ironsea_index_sfc_dbc]: https://github.com/epfl-dias/ironsea_index_sfc_dbc
|
||||
const MAX_K: usize = 3;
|
||||
|
||||
lazy_static! {
|
||||
static ref UNIVERSE: Space = Space {
|
||||
@@ -29,6 +40,7 @@ lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
/// A reference space, defined by its name and coordinate system.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct Space {
|
||||
name: String,
|
||||
@@ -36,6 +48,15 @@ pub struct Space {
|
||||
}
|
||||
|
||||
impl Space {
|
||||
/// Instantiate a new space.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `name`:
|
||||
/// Id of the reference space.
|
||||
///
|
||||
/// * `system`:
|
||||
/// Coordinate system defintion of the reference space
|
||||
pub fn new<S>(name: S, system: CoordinateSystem) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
@@ -46,54 +67,93 @@ impl Space {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Universe Space.
|
||||
///
|
||||
/// This space contains all of the spaces, and allows us to connect
|
||||
/// them between each others.
|
||||
pub fn universe() -> &'static Self {
|
||||
&UNIVERSE
|
||||
}
|
||||
|
||||
/// Transform a position from space `from` into a position in space `to`.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `position`:
|
||||
/// Position to transform, expressed as encoded coordinates.
|
||||
///
|
||||
/// * `from`:
|
||||
/// Space in which `position` is defined.
|
||||
///
|
||||
/// * `to`:
|
||||
/// Target space in which `position` should be expressed.
|
||||
pub fn change_base(position: &Position, from: &Space, to: &Space) -> Result<Position, String> {
|
||||
to.rebase(&from.absolute_position(position)?)
|
||||
}
|
||||
|
||||
/// Id of the reference space.
|
||||
pub fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Origin of the space, expressed in Universe.
|
||||
pub fn origin(&self) -> &Position {
|
||||
self.system.origin()
|
||||
}
|
||||
|
||||
/// Axes definition of the space.
|
||||
pub fn axes(&self) -> &Vec<Axis> {
|
||||
self.system.axes()
|
||||
}
|
||||
|
||||
/// Returns the bounding box enclosing the whole space.
|
||||
pub fn bounding_box(&self) -> (Position, Position) {
|
||||
self.system.bounding_box()
|
||||
}
|
||||
|
||||
/// Total volume of the reference space.
|
||||
pub fn volume(&self) -> f64 {
|
||||
self.system.volume()
|
||||
}
|
||||
|
||||
// The position is expressed in coordinates in the universe,
|
||||
// return a position in the current space.
|
||||
pub fn rebase(&self, position: &Position) -> Result<Position, String> {
|
||||
// `position` is expressed in the Universe, this return encoded
|
||||
// coordinates in the current space.
|
||||
fn rebase(&self, position: &Position) -> Result<Position, String> {
|
||||
self.system.rebase(position)
|
||||
}
|
||||
|
||||
// The position is expressed in coordinates in the current space,
|
||||
// The position is expressed in encoded coordinates in the current space,
|
||||
// return an absolute position in Universe.
|
||||
pub fn absolute_position(&self, position: &Position) -> Result<Position, String> {
|
||||
fn absolute_position(&self, position: &Position) -> Result<Position, String> {
|
||||
self.system.absolute_position(position)
|
||||
}
|
||||
|
||||
// The position is expressed in the current space as an encoded value,
|
||||
// return a position in the current system as f64 values
|
||||
/// Decode coordinates expressed in the current space, to their
|
||||
/// values within the axes definitions.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `position`:
|
||||
/// expressed in encoded coordinates within the current space.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The decoded position within the space.
|
||||
pub fn decode(&self, position: &Position) -> Result<Vec<f64>, String> {
|
||||
self.system.decode(position)
|
||||
}
|
||||
|
||||
// The position is expressed in the current space,
|
||||
// return a position expressed in the current space as an encoded value.
|
||||
/// Encode a position expressed in the current space within the axes
|
||||
/// value ranges.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `position`:
|
||||
/// expressed in the current space.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The encoded coordinates within the space.
|
||||
pub fn encode(&self, position: &[f64]) -> Result<Position, String> {
|
||||
self.system.encode(position)
|
||||
}
|
||||
|
||||
@@ -18,24 +18,31 @@ use serde::Serialize;
|
||||
|
||||
use super::coordinate::Coordinate;
|
||||
|
||||
/// Store a position as efficiently as possible in terms of space.
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, Serialize)]
|
||||
pub enum Position {
|
||||
/// 1 dimension positions.
|
||||
Position1(Coordinate),
|
||||
/// 2 dimensions positions.
|
||||
Position2([Coordinate; 2]),
|
||||
/// 3 dimensions positions.
|
||||
Position3([Coordinate; 3]),
|
||||
/// 4 dimensions positions.
|
||||
Position4([Coordinate; 4]),
|
||||
/// 5 dimensions positions.
|
||||
Position5([Coordinate; 5]),
|
||||
/// 6 dimensions positions.
|
||||
Position6([Coordinate; 6]),
|
||||
/// 7 dimensions positions.
|
||||
Position7([Coordinate; 7]),
|
||||
/// 8 dimensions positions.
|
||||
Position8([Coordinate; 8]),
|
||||
/// N dimensions positions.
|
||||
PositionN(Vec<Coordinate>),
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn new(coordinates: Vec<Coordinate>) -> Self {
|
||||
coordinates.into()
|
||||
}
|
||||
|
||||
/// Returns the number of dimensions or size of the vector.
|
||||
pub fn dimensions(&self) -> usize {
|
||||
match self {
|
||||
Position::Position1(_) => 1,
|
||||
@@ -50,7 +57,7 @@ impl Position {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns ||self||
|
||||
/// Compute `||self||`.
|
||||
pub fn norm(&self) -> f64 {
|
||||
if let Position::Position1(coordinates) = self {
|
||||
// the square root of a single number to the square is its positive value, so ensure it is.
|
||||
@@ -68,32 +75,48 @@ impl Position {
|
||||
}
|
||||
}
|
||||
|
||||
// Unit / Normalized vector from self.
|
||||
/// Compute the unit vector pointing in the same direction as `self`.
|
||||
pub fn unit(&self) -> Self {
|
||||
self * (1f64 / self.norm())
|
||||
}
|
||||
|
||||
// This multiplies self^T with other, producing a scalar value
|
||||
pub fn dot_product(&self, other: &Self) -> f64 {
|
||||
assert_eq!(self.dimensions(), other.dimensions());
|
||||
/// Multiplies `self` with `rhs`, producing a scalar value.
|
||||
///
|
||||
/// `self • rhs = product`
|
||||
///
|
||||
/// **Note:** The two vector sizes must be equal, a.k.a the two
|
||||
/// vectors must have the same number of dimensions.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// `rhs`:
|
||||
/// The right-hand side vector.
|
||||
pub fn dot_product(&self, rhs: &Self) -> f64 {
|
||||
assert_eq!(self.dimensions(), rhs.dimensions());
|
||||
|
||||
let mut product = 0f64;
|
||||
|
||||
for k in 0..self.dimensions() {
|
||||
product += (self[k] * other[k]).f64();
|
||||
product += (self[k] * rhs[k]).f64();
|
||||
}
|
||||
|
||||
product
|
||||
}
|
||||
|
||||
/// Remove bits of precision.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `scale`:
|
||||
/// Number of bits of precision to remove from each coordinates.
|
||||
pub fn reduce_precision(&self, scale: u32) -> Self {
|
||||
let mut position = Vec::with_capacity(self.dimensions());
|
||||
|
||||
for i in 0..self.dimensions() {
|
||||
position.push((self[i].u64() >> scale).into())
|
||||
position.push(self[i].u64() >> scale)
|
||||
}
|
||||
|
||||
Position::new(position)
|
||||
position.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +176,10 @@ impl Index<usize> for Position {
|
||||
|
||||
fn index(&self, k: usize) -> &Self::Output {
|
||||
match self {
|
||||
Position::Position1(coordinate) => coordinate,
|
||||
Position::Position1(coordinate) => {
|
||||
assert_eq!(k, 0);
|
||||
coordinate
|
||||
}
|
||||
Position::Position2(coordinates) => &coordinates[k],
|
||||
Position::Position3(coordinates) => &coordinates[k],
|
||||
Position::Position4(coordinates) => &coordinates[k],
|
||||
@@ -169,7 +195,10 @@ impl Index<usize> for Position {
|
||||
impl IndexMut<usize> for Position {
|
||||
fn index_mut(&mut self, k: usize) -> &mut Self::Output {
|
||||
match self {
|
||||
Position::Position1(coordinate) => coordinate,
|
||||
Position::Position1(coordinate) => {
|
||||
assert_eq!(k, 0);
|
||||
coordinate
|
||||
}
|
||||
Position::Position2(coordinates) => &mut coordinates[k],
|
||||
Position::Position3(coordinates) => &mut coordinates[k],
|
||||
Position::Position4(coordinates) => &mut coordinates[k],
|
||||
|
||||
@@ -5,16 +5,33 @@ use super::Coordinate;
|
||||
use super::Position;
|
||||
use super::Space;
|
||||
|
||||
/// Known shapes descriptions
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum Shape {
|
||||
/// A singular point in space.
|
||||
Point(Position),
|
||||
//HyperRectangle([Position; MAX_K]),
|
||||
/// A sphere in space.
|
||||
HyperSphere(Position, Coordinate),
|
||||
|
||||
/// Hyperrectangle whose faces have one of the axis as a normal.
|
||||
BoundingBox(Position, Position),
|
||||
//Nifti(nifti_data??),
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
/// Convert the encoded coordinates between two reference spaces.
|
||||
///
|
||||
/// The resulting shape is expressed in encoded coordinates in the
|
||||
/// target space.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `from`:
|
||||
/// Current reference space of the shape.
|
||||
///
|
||||
/// * `to`:
|
||||
/// Target reference space.
|
||||
pub fn rebase(&self, from: &Space, to: &Space) -> Result<Shape, String> {
|
||||
match self {
|
||||
Shape::Point(position) => Ok(Shape::Point(Space::change_base(position, from, to)?)),
|
||||
@@ -36,6 +53,20 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode the coordinates of the shape.
|
||||
///
|
||||
/// The encoded coordinates of the shapes are expressed in the
|
||||
/// provided space.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `space`:
|
||||
/// Reference space of the shape. It is used to decode the
|
||||
/// encoded coordinates into positions.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The shape with decoded positions within the space.
|
||||
pub fn decode(&self, space: &Space) -> Result<Shape, String> {
|
||||
let s = match self {
|
||||
Shape::Point(position) => Shape::Point(space.decode(position)?.into()),
|
||||
@@ -51,6 +82,19 @@ impl Shape {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Encode the positions of the shape.
|
||||
///
|
||||
/// The positions of the shapes are expressed in the provided space.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `space`:
|
||||
/// Reference space of the shape. It is used to encode the
|
||||
/// positions into encoded coordinates.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The shape with encoded coordinates within the space.
|
||||
pub fn encode(&self, space: &Space) -> Result<Shape, String> {
|
||||
let s = match self {
|
||||
Shape::Point(position) => {
|
||||
@@ -72,6 +116,10 @@ impl Shape {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Compute the minimum bounding box of the shape.
|
||||
///
|
||||
/// This is an hyperrectangle whose faces are perpendicular to an
|
||||
/// axis of the space, and which minimally covers the shape.
|
||||
pub fn get_mbb(&self) -> (Position, Position) {
|
||||
match self {
|
||||
Shape::Point(position) => (position.clone(), position.clone()),
|
||||
@@ -88,6 +136,12 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the shape overlaps with the given position.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `position`:
|
||||
/// The position to check.
|
||||
pub fn contains(&self, position: &Position) -> bool {
|
||||
match self {
|
||||
Shape::Point(reference) => reference == position,
|
||||
@@ -178,9 +232,8 @@ impl Shape {
|
||||
results
|
||||
}
|
||||
|
||||
// Transform a Shape into a list of Position which approximate the shape.
|
||||
// Note:
|
||||
// * All output positions are expressed within the space.
|
||||
/// Transform a Shape into a list of `Position` which approximate
|
||||
/// the shape.
|
||||
// TODO: Return an iterator instead, for performance!
|
||||
pub fn rasterise(&self) -> Result<Vec<Position>, String> {
|
||||
match self {
|
||||
@@ -200,10 +253,14 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
// Transform a Shape into a list of Position which approximate the shape.
|
||||
// Note:
|
||||
// * All input positions are expressed within the space.
|
||||
// * All output positions are expressed in absolute positions in Universe
|
||||
/// Transform a Shape into a list of `Position` which approximate
|
||||
/// the shape, in absolute, or Universe positions.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `space`:
|
||||
/// Reference space in which the shape is expressed.
|
||||
///
|
||||
// TODO: Return an iterator instead, for performance!
|
||||
pub fn rasterise_from(&self, space: &Space) -> Result<Vec<Position>, String> {
|
||||
Ok(self
|
||||
@@ -216,6 +273,7 @@ impl Shape {
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Compute the volume.
|
||||
pub fn volume(&self) -> f64 {
|
||||
match self {
|
||||
Shape::Point(_) => std::f64::EPSILON, // Smallest non-zero volume possible
|
||||
|
||||
@@ -193,11 +193,13 @@ impl SpaceDB {
|
||||
&self.reference_space
|
||||
}
|
||||
|
||||
/* Comment this for now, as this is not yet used.
|
||||
// The smallest volume threshold, which is the highest resolution, will
|
||||
// be at position 0
|
||||
fn highest_resolution(&self) -> usize {
|
||||
0
|
||||
}
|
||||
*/
|
||||
|
||||
// The highest volume threshold, which is the lowest resolution, will
|
||||
// be at position len - 1
|
||||
|
||||
@@ -139,7 +139,7 @@ impl SpaceIndex {
|
||||
self.index.find_by_value(id)
|
||||
}
|
||||
|
||||
/// Inputs and Results are also in encoded space coordinates.
|
||||
// Inputs and Results are also in encoded space coordinates.
|
||||
pub fn find_by_shape(
|
||||
&self,
|
||||
shape: &Shape,
|
||||
|
||||
30
src/lib.rs
30
src/lib.rs
@@ -1,3 +1,33 @@
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! # Mercator DB
|
||||
//!
|
||||
//! Database model for the Mercator spatial index.
|
||||
//!
|
||||
//! ## Mercator: Spatial Index
|
||||
//!
|
||||
//! **Mercator** is a spatial *volumetric* index for the
|
||||
//! [Human Brain Project]. It is a component of the [Knowledge Graph]
|
||||
//! service, which provides the spatial anchoring for the metadata
|
||||
//! registered as well as processes the volumetric queries.
|
||||
//!
|
||||
//! It is build on top of the Iron Sea database toolkit.
|
||||
//!
|
||||
//! ## Iron Sea: Database Toolkit
|
||||
//! **Iron Sea** provides a set of database engine bricks, which can be
|
||||
//! combined and applied on arbitrary data structures.
|
||||
//!
|
||||
//! Unlike a traditional database, it does not assume a specific
|
||||
//! physical structure for the tables nor the records, but relies on the
|
||||
//! developer to provide a set of extractor functions which are used by
|
||||
//! the specific indices provided.
|
||||
//!
|
||||
//! This enables the index implementations to be agnostic from the
|
||||
//! underlying data structure, and re-used.
|
||||
//!
|
||||
//! [Human Brain Project]: http://www.humanbrainproject.eu
|
||||
//! [Knowledge Graph]: http://www.humanbrainproject.eu/en/explore-the-brain/search/
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Bincode support
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::BufWriter;
|
||||
use std::io::Error;
|
||||
@@ -9,6 +11,12 @@ use serde::Serialize;
|
||||
|
||||
use super::model;
|
||||
|
||||
/// Deserialize a data structure.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `from`:
|
||||
/// File to read, which contains Bincode data.
|
||||
pub fn load<T>(from: &str) -> Result<T, Error>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
@@ -26,6 +34,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a data structure.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `data`:
|
||||
/// Data to serialize.
|
||||
///
|
||||
/// * `to`:
|
||||
/// File to use to store the serialized data.
|
||||
pub fn store<T>(data: T, to: &str) -> Result<(), Error>
|
||||
where
|
||||
T: Serialize,
|
||||
@@ -44,6 +61,32 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an index from the input files.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `name`:
|
||||
/// Index name, this value will also be used to generate file names
|
||||
/// as such:
|
||||
/// * `.spaces.bin` and `.objects.bin` will be appended for the
|
||||
/// input files.
|
||||
/// * `.index` will be appended for the index file.
|
||||
///
|
||||
/// * `version`:
|
||||
/// Parameter to distinguish revisions of an index.
|
||||
///
|
||||
/// * `scales`:
|
||||
/// An optional list of specific index resolutions to generates on
|
||||
/// top of the full resolution one.
|
||||
///
|
||||
/// * `max_elements`:
|
||||
/// If this is specified, automatically generates scaled indices, by
|
||||
/// halving the number elements between resolutions, and stop
|
||||
/// generating indices either when the number of points remaining is
|
||||
/// equal to the number of distinct Ids, or smaller or equal to this
|
||||
/// value.
|
||||
///
|
||||
/// **Note**: `max_elements` is ignored when `scales` is not `None`.
|
||||
pub fn build(
|
||||
name: &str,
|
||||
version: &str,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! JSON support
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::BufWriter;
|
||||
use std::io::Error;
|
||||
@@ -29,6 +31,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialise a JSON file.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `name`:
|
||||
/// Base name of the file,
|
||||
/// * `.xyz` will be automatically appended for the source file, while
|
||||
/// * `.bin` will be appended for the output file.
|
||||
pub fn from<T>(name: &str) -> Result<(), Error>
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! Persistent data functions and types.
|
||||
//!
|
||||
//! Serialisation / deserialisation functions and structures used to
|
||||
//! store and manipulate indices and data.
|
||||
|
||||
pub mod bincode;
|
||||
pub mod json;
|
||||
pub mod model;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! Model definitions for serialisation.
|
||||
//!
|
||||
//! The following definitions are used as part of the serialisation
|
||||
//! process to exchange objects either through network or to storage.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
@@ -5,32 +10,59 @@ use serde::Serialize;
|
||||
|
||||
use crate::database;
|
||||
use database::space;
|
||||
use database::space_index::SpaceSetObject;
|
||||
use database::Core;
|
||||
use database::SpaceSetObject;
|
||||
|
||||
/// Reference space definition.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Space {
|
||||
/// **Id** of the space.
|
||||
pub name: String,
|
||||
|
||||
/// Position of the origin of axis expressed in Universe coordinates.
|
||||
pub origin: Vec<f64>,
|
||||
|
||||
/// List of axes of the space.
|
||||
pub axes: Vec<Axis>,
|
||||
}
|
||||
|
||||
/// Reference space axis definition.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Axis {
|
||||
/// Length unit for the value `1.0`.
|
||||
pub measurement_unit: String,
|
||||
|
||||
/// Define the valid range of number on this axis.
|
||||
pub graduation: Graduation,
|
||||
|
||||
/// Vector which defines the direction of the axis in the Universe
|
||||
pub unit_vector: Vec<f64>,
|
||||
}
|
||||
|
||||
/// Valid range of numbers on the axis.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Graduation {
|
||||
/// Mathematical Number Set of numbers allowed.
|
||||
pub set: String,
|
||||
|
||||
/// Minimum value allowed, included.
|
||||
pub minimum: f64,
|
||||
|
||||
/// Maximum value allowed, excluded.
|
||||
pub maximum: f64,
|
||||
|
||||
/// Number of distinct positions between `[min; max[`
|
||||
pub steps: u64,
|
||||
}
|
||||
|
||||
/// A single spatial location.
|
||||
///
|
||||
/// This has a value per dimension of the space it is expressed in.
|
||||
pub type Point = Vec<f64>;
|
||||
|
||||
pub mod v1 {
|
||||
//! REST API objects, v1.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
@@ -39,25 +71,41 @@ pub mod v1 {
|
||||
use crate::database;
|
||||
use database::space;
|
||||
|
||||
use super::Point;
|
||||
use super::Properties;
|
||||
|
||||
/// Links Properties to a list of spatial volumes.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct SpatialObject {
|
||||
/// Definition of the `properties` to tag in space.
|
||||
pub properties: Properties,
|
||||
|
||||
/// List of volumes associated with `properties`.
|
||||
pub shapes: Vec<Shape>,
|
||||
}
|
||||
|
||||
/// Define a Shape, within a specific reference space.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Shape {
|
||||
/// Type of the shape, which is used to interpret the list of `vertices`.
|
||||
#[serde(rename = "type")]
|
||||
pub type_name: String,
|
||||
|
||||
/// Id of the reference space the points are defined in.
|
||||
#[serde(rename = "space")]
|
||||
pub reference_space: String,
|
||||
|
||||
/// List of spatial positions.
|
||||
pub vertices: Vec<Point>,
|
||||
}
|
||||
|
||||
type Point = Vec<f64>;
|
||||
|
||||
/// Convert a list of properties grouped by space id, then positions to a
|
||||
/// list of Spatial Objects for the rest API v1.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `list`:
|
||||
/// A list of (**Space Id**, [ ( *Spatial position*, `&Properties` ) ]) tuples.
|
||||
pub fn to_spatial_objects(
|
||||
list: Vec<(&String, Vec<(space::Position, &database::Properties)>)>,
|
||||
) -> Vec<SpatialObject> {
|
||||
@@ -95,6 +143,8 @@ pub mod v1 {
|
||||
}
|
||||
|
||||
pub mod v2 {
|
||||
//! REST API objects, v2.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
@@ -103,30 +153,68 @@ pub mod v2 {
|
||||
use crate::database;
|
||||
use database::space;
|
||||
|
||||
use super::Point;
|
||||
use super::Properties;
|
||||
|
||||
/// Links Properties to a list of spatial volumes.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct SpatialObject {
|
||||
/// Definition of the `properties` to tag in space.
|
||||
pub properties: Properties,
|
||||
|
||||
/// List of volumes associated with `properties`.
|
||||
pub volumes: Vec<Volume>,
|
||||
}
|
||||
|
||||
/// Defines a volume as the union of geometric shapes.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Volume {
|
||||
/// Reference space id.
|
||||
pub space: String,
|
||||
|
||||
/// List of geometric shapes defined in the reference space
|
||||
/// `space`.
|
||||
pub shapes: Vec<Shape>,
|
||||
}
|
||||
|
||||
/// Describes an homogeneous list of geometric shapes.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Shape {
|
||||
/// List of points.
|
||||
Points(Vec<Point>),
|
||||
|
||||
/// List of Bounding boxes or *hyper rectangles* for which each
|
||||
/// face is perpendicular to one of the axis of the reference
|
||||
/// space.
|
||||
///
|
||||
/// That property allows us to describe such a hyperrectangle
|
||||
/// with two corners:
|
||||
///
|
||||
/// * one for which all the coordinates are the smallest among
|
||||
/// all the corners, per dimension, which is called here
|
||||
/// *lower corner*
|
||||
///
|
||||
/// * one for which all the coordinates are the greatest among
|
||||
/// all the corners, per dimension, which is called
|
||||
/// *higher corner*.
|
||||
///
|
||||
/// The list simply stores tuples of (`lower corner`,
|
||||
/// `higher corner`), as this is enough to reconstruct all the
|
||||
/// corners of the bounding box.
|
||||
BoundingBoxes(Vec<(Point, Point)>),
|
||||
|
||||
/// List of hyperspheres, stored as (`center`, radius) tuples.
|
||||
HyperSpheres(Vec<(Point, f64)>),
|
||||
}
|
||||
|
||||
type Point = Vec<f64>;
|
||||
|
||||
/// Convert a list of properties grouped by space id, then positions to a
|
||||
/// list of Spatial Objects for the rest API v2.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `list`:
|
||||
/// A list of (**Space Id**, [ ( *Spatial position*, `&Properties` ) ]) tuples.
|
||||
pub fn to_spatial_objects(
|
||||
list: Vec<(&String, Vec<(space::Position, &database::Properties)>)>,
|
||||
) -> Vec<SpatialObject> {
|
||||
@@ -163,10 +251,15 @@ pub mod v2 {
|
||||
}
|
||||
}
|
||||
|
||||
/// **Properties** which are registered at one or more spatial locations.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Properties {
|
||||
/// The **type** of *Id*, this allows for different kinds of objects
|
||||
/// to have the same *Id*, but handled distinctly.
|
||||
#[serde(rename = "type")]
|
||||
pub type_name: String,
|
||||
|
||||
/// An arbitrary string.
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
@@ -244,6 +337,35 @@ impl From<&&database::Properties> for Properties {
|
||||
|
||||
pub use v1::SpatialObject;
|
||||
|
||||
/// Generate an index.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `name`:
|
||||
/// Name to give to the index.
|
||||
///
|
||||
/// * `version`:
|
||||
/// Parameter to distinguish revisions of an index.
|
||||
///
|
||||
/// * `spaces`:
|
||||
/// A list of the reference spaces. Only objects whose reference
|
||||
/// space is known will be indexed.
|
||||
///
|
||||
/// * `objects`:
|
||||
/// The data points to index.
|
||||
///
|
||||
/// * `scales`:
|
||||
/// An optional list of specific index resolutions to generates on
|
||||
/// top of the full resolution one.
|
||||
///
|
||||
/// * `max_elements`:
|
||||
/// If this is specified, automatically generates scaled indices, by
|
||||
/// halving the number elements between resolutions, and stop
|
||||
/// generating indices either when the number of points remaining is
|
||||
/// equal to the number of distinct Ids, or smaller or equal to this
|
||||
/// value.
|
||||
///
|
||||
/// **Note**: `max_elements` is ignored when `scales` is not `None`.
|
||||
pub fn build_index(
|
||||
name: &str,
|
||||
version: &str,
|
||||
|
||||
@@ -1,3 +1,113 @@
|
||||
//! # XYZ file format
|
||||
//!
|
||||
//! This module support reading files read by [MeshView] tool used at
|
||||
//! the [University of Oslo].
|
||||
//!
|
||||
//! # File structure
|
||||
//!
|
||||
//! Each files begins with:
|
||||
//!
|
||||
//! ```txt
|
||||
//! RGBA [Red] [Green] [Blue] [Alpha] # RGBA
|
||||
//! [X],[Y],[Z] # WHS Origin
|
||||
//! [X],[Y],[Z] # Bregma
|
||||
//!
|
||||
//! SCALE [F]
|
||||
//! ```
|
||||
//!
|
||||
//! * `RGBA [Red] [Green] [Blue] [Alpha]`: defines the color to use for
|
||||
//! the following points
|
||||
//! * `[X],[Y],[Z] # WHS Origin`: defines where the Waxholm Origin is
|
||||
//! in Voxel coordinates.
|
||||
//! * `[X],[Y],[Z] # Bregma`: same as above, for another reference
|
||||
//! space.
|
||||
//! * `SCALE [F]`: **TBC** Size of the voxels.
|
||||
//!
|
||||
//! The rest of the file contains (one per line):
|
||||
//! * coordinate triplets (x, y and z), each representing one point
|
||||
//! coordinate.
|
||||
//! * `RGB [Red] [Green] [Blue]`: Which applies from that line
|
||||
//! until further notice.
|
||||
//! * A comment Line, starting with `#`
|
||||
//!
|
||||
//! ## File Coordinate system
|
||||
//!
|
||||
//! Coordinates in MeshView follow RAS (Right-Anterior-Superior)
|
||||
//! orientation and are expressed in voxels:
|
||||
//! * First axis `x` starts from the left side of the volume, and
|
||||
//! points towards the right.
|
||||
//! * Second axis `y` starts from the backmost position in the volume,
|
||||
//! and points towards the front.
|
||||
//! * Third axis `z` starts from the bottom of the volume and points
|
||||
//! towards the top.
|
||||
//!
|
||||
//! # Waxholm Space
|
||||
//!
|
||||
//! ## Conversion to Waxholm Space
|
||||
//!
|
||||
//! The [Waxholm Space Atlas] of the Sprague Dawley Rat Brain (WHS) uses
|
||||
//! the same axis order and orientation as the MeshView tool, there is
|
||||
//! only a translation of the origin, and scaling have to be applied.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```txt
|
||||
//! RGBA 1 0 0 1 # RGBA
|
||||
//! 244,623,248 # WHS Origin
|
||||
//! 246,653,440 # Bregma
|
||||
//!
|
||||
//! #Aar27s49 26 0
|
||||
//! RGB 0.12941176470588237 0.403921568627451 0.1607843137254902
|
||||
//! 221.40199877 413.34541500312037 172.79973508489095
|
||||
//! 220.5800097805 412.82939421970866 173.56428074436994
|
||||
//!
|
||||
//! #Aar27s48 49 0
|
||||
//! RGB 0.12941176470588237 0.403921568627451 0.1607843137254902
|
||||
//! 237.35325687425 412.5720395183866 176.6713556605702
|
||||
//! ```
|
||||
//!
|
||||
//! ## Conversion to Waxholm
|
||||
//!
|
||||
//! Assuming the following extents of "WHS Rat 39 μm" in voxels:
|
||||
//!
|
||||
//! * Leftmost sagittal plane: `x = 0`
|
||||
//! * Backmost coronal plane: `y = 0`
|
||||
//! * Bottommost horizontal plane: `z = 0`
|
||||
//! * Rightmost sagittal plane: `x = 511`
|
||||
//! * Frontmost coronal plane: `y = 1023`
|
||||
//! * Topmost horizontal plane: `z = 511`
|
||||
//!
|
||||
//! **NOTE**: Directions are deliberately matching the default
|
||||
//! orientation of NIfTI data.
|
||||
//!
|
||||
//! 1. As per the `WHS Origin` directive, it is at 244, 623, 248 voxel
|
||||
//! coordinates, which means each coordinate must be subtracted with
|
||||
//! the corresponding value, then
|
||||
//! 2. the coordinates must be converted to millimeters, a.k.a
|
||||
//! multiplied by the atlas resolution. For the atlas of this example
|
||||
//! it is 0.0390625 [mm], isotropic.
|
||||
//!
|
||||
//! This gives us the following conversion formula:
|
||||
//!
|
||||
//! ```txt
|
||||
//! ⎡ 0.0390625 0 0 0 ⎤
|
||||
//! [ xw yw zw 1 ] = [ xq yq zq 1 ] * ⎢ 0 0.0390625 0 0 ⎥
|
||||
//! ⎢ 0 0 0.0390625 0 ⎥
|
||||
//! ⎣ -9.53125 -24.3359375 -9.6875 1 ⎦
|
||||
//! ```
|
||||
//!
|
||||
//! Where:
|
||||
//! * `[xw, yw, zw 1]` are WHS coordinates (RAS directions, expressed
|
||||
//! in millimeters).
|
||||
//! * `[xq, yq, zq 1]` are MeshView coordinates for the **WHS Rat 39 μm**
|
||||
//! package (RAS directions, expressed in 39.0625 μm voxels).
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//! [MeshView]: http://www.nesys.uio.no/MeshView/meshview.html?atlas=WHS_SD_rat_atlas_v2
|
||||
//! [University of Oslo]: https://www.med.uio.no/imb/english/research/groups/neural-systems/index.html
|
||||
//! [Waxholm Space Atlas]: https://www.nitrc.org/projects/whs-sd-atlas
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
@@ -102,8 +212,18 @@ fn convert(string: &str) -> Result<Vec<SpatialObject>, Error> {
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Read a XYZ file and convert it to the internal format for indexing.
|
||||
///
|
||||
/// This only converts the data point definitions, a reference space
|
||||
/// needs to be provided as well to be able to build an index.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `name`:
|
||||
/// Base name of the file,
|
||||
/// * `.xyz` will be automatically appended for the source file, while
|
||||
/// * `.bin` will be appended for the output file.
|
||||
pub fn from(name: &str) -> Result<(), Error> {
|
||||
// Convert Reference Space definitions
|
||||
let fn_in = format!("{}.xyz", name);
|
||||
let fn_out = format!("{}.bin", name);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user