Skip to main content

wsi_dicom_annotations/annotations/model/
algorithm.rs

1use crate::{Error, Result};
2
3use super::code::{validate_text, DicomCode, MAX_LONG_TEXT_BYTES};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum GenerationType {
7    Manual,
8    Semiautomatic,
9    Automatic,
10}
11
12impl GenerationType {
13    #[must_use]
14    pub const fn dicom_value(self) -> &'static str {
15        match self {
16            Self::Manual => "MANUAL",
17            Self::Semiautomatic => "SEMIAUTOMATIC",
18            Self::Automatic => "AUTOMATIC",
19        }
20    }
21
22    pub(crate) fn from_dicom(value: &str) -> Result<Self> {
23        match value.trim() {
24            "MANUAL" => Ok(Self::Manual),
25            "SEMIAUTOMATIC" => Ok(Self::Semiautomatic),
26            "AUTOMATIC" => Ok(Self::Automatic),
27            other => Err(Error::Unsupported(format!(
28                "generation type {other:?} is not recognized"
29            ))),
30        }
31    }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct AlgorithmIdentification {
36    family: DicomCode,
37    name_code: Option<DicomCode>,
38    name: String,
39    version: String,
40    parameters: Option<String>,
41    source: Option<String>,
42}
43
44impl AlgorithmIdentification {
45    pub fn new(
46        family: DicomCode,
47        name: impl Into<String>,
48        version: impl Into<String>,
49    ) -> Result<Self> {
50        let name = name.into();
51        let version = version.into();
52        validate_text("algorithm name", &name, 64)?;
53        validate_text("algorithm version", &version, 64)?;
54        Ok(Self {
55            family,
56            name_code: None,
57            name,
58            version,
59            parameters: None,
60            source: None,
61        })
62    }
63
64    #[must_use]
65    pub fn with_name_code(mut self, code: DicomCode) -> Self {
66        self.name_code = Some(code);
67        self
68    }
69
70    pub fn with_parameters(mut self, parameters: impl Into<String>) -> Result<Self> {
71        let parameters = parameters.into();
72        validate_text("algorithm parameters", &parameters, MAX_LONG_TEXT_BYTES)?;
73        self.parameters = Some(parameters);
74        Ok(self)
75    }
76
77    pub fn with_source(mut self, source: impl Into<String>) -> Result<Self> {
78        let source = source.into();
79        validate_text("algorithm source", &source, 64)?;
80        self.source = Some(source);
81        Ok(self)
82    }
83
84    #[must_use]
85    pub fn family(&self) -> &DicomCode {
86        &self.family
87    }
88
89    #[must_use]
90    pub fn name_code(&self) -> Option<&DicomCode> {
91        self.name_code.as_ref()
92    }
93
94    #[must_use]
95    pub fn name(&self) -> &str {
96        &self.name
97    }
98
99    #[must_use]
100    pub fn version(&self) -> &str {
101        &self.version
102    }
103
104    #[must_use]
105    pub fn parameters(&self) -> Option<&str> {
106        self.parameters.as_deref()
107    }
108
109    #[must_use]
110    pub fn source(&self) -> Option<&str> {
111        self.source.as_deref()
112    }
113}
114
115pub(crate) fn validate_generation(
116    generation_type: GenerationType,
117    algorithms: &[AlgorithmIdentification],
118) -> Result<()> {
119    match generation_type {
120        GenerationType::Manual if !algorithms.is_empty() => Err(Error::InvalidInput(
121            "manual annotations cannot identify a generation algorithm".into(),
122        )),
123        GenerationType::Semiautomatic | GenerationType::Automatic if algorithms.is_empty() => {
124            Err(Error::InvalidInput(
125                "automatic and semiautomatic annotations require an algorithm identification"
126                    .into(),
127            ))
128        }
129        _ => Ok(()),
130    }
131}