1use serde::{Deserialize, Serialize};
2
3use crate::WsiDicomError;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum EncodeBackendPreference {
8 Auto,
9 CpuOnly,
10 PreferDevice,
11 RequireDevice,
12}
13
14impl EncodeBackendPreference {
15 pub(crate) fn to_signinum(self) -> signinum_j2k::EncodeBackendPreference {
16 match self {
17 Self::Auto => signinum_j2k::EncodeBackendPreference::Auto,
18 Self::CpuOnly => signinum_j2k::EncodeBackendPreference::CpuOnly,
19 Self::PreferDevice => signinum_j2k::EncodeBackendPreference::PreferDevice,
20 Self::RequireDevice => signinum_j2k::EncodeBackendPreference::RequireDevice,
21 }
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub enum CodecValidation {
28 Disabled,
29 RoundTrip,
30}
31
32impl CodecValidation {
33 pub(crate) fn to_j2k_validation(self) -> signinum_j2k::J2kEncodeValidation {
34 match self {
35 Self::Disabled => signinum_j2k::J2kEncodeValidation::External,
36 Self::RoundTrip => signinum_j2k::J2kEncodeValidation::CpuRoundTrip,
37 }
38 }
39
40 #[allow(dead_code)]
41 pub(crate) fn enabled(self) -> bool {
42 self == Self::RoundTrip
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum TransferSyntax {
49 JpegBaseline8Bit,
50 Jpeg2000,
51 Jpeg2000Lossless,
52 Htj2kLossless,
53 Htj2kLosslessRpcl,
54 ExplicitVrLittleEndian,
55}
56
57impl TransferSyntax {
58 pub fn uid(self) -> &'static str {
59 match self {
60 Self::JpegBaseline8Bit => "1.2.840.10008.1.2.4.50",
61 Self::Jpeg2000 => "1.2.840.10008.1.2.4.91",
62 Self::Jpeg2000Lossless => "1.2.840.10008.1.2.4.90",
63 Self::Htj2kLossless => "1.2.840.10008.1.2.4.201",
64 Self::Htj2kLosslessRpcl => "1.2.840.10008.1.2.4.202",
65 Self::ExplicitVrLittleEndian => "1.2.840.10008.1.2.1",
66 }
67 }
68
69 pub(crate) fn is_j2k_family(self) -> bool {
70 matches!(
71 self,
72 Self::Jpeg2000 | Self::Jpeg2000Lossless | Self::Htj2kLossless | Self::Htj2kLosslessRpcl
73 )
74 }
75
76 pub(crate) fn is_lossless_j2k_family(self) -> bool {
77 matches!(
78 self,
79 Self::Jpeg2000Lossless | Self::Htj2kLossless | Self::Htj2kLosslessRpcl
80 )
81 }
82
83 pub(crate) fn is_jpeg2000_passthrough_only(self) -> bool {
84 self == Self::Jpeg2000
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct DicomExportOptions {
91 pub tile_size: u32,
92 pub transfer_syntax: TransferSyntax,
93 pub jpeg_quality: u8,
94 pub encode_backend: EncodeBackendPreference,
95 pub codec_validation: CodecValidation,
96 pub source_device_decode: bool,
97 pub j2k_decomposition_levels: Option<u8>,
98 pub gpu_encode_inflight_tiles: Option<usize>,
99 pub gpu_encode_memory_mib: Option<u64>,
100}
101
102impl Default for DicomExportOptions {
103 fn default() -> Self {
104 Self {
105 tile_size: 512,
106 transfer_syntax: TransferSyntax::Htj2kLosslessRpcl,
107 jpeg_quality: 90,
108 encode_backend: EncodeBackendPreference::Auto,
109 codec_validation: CodecValidation::Disabled,
110 source_device_decode: false,
111 j2k_decomposition_levels: None,
112 gpu_encode_inflight_tiles: None,
113 gpu_encode_memory_mib: None,
114 }
115 }
116}
117
118impl DicomExportOptions {
119 pub fn validate(&self) -> Result<(), WsiDicomError> {
120 if self.tile_size == 0 {
121 return Err(WsiDicomError::InvalidOptions {
122 reason: "tile_size must be greater than zero".into(),
123 });
124 }
125 if !(1..=100).contains(&self.jpeg_quality) {
126 return Err(WsiDicomError::InvalidOptions {
127 reason: "jpeg_quality must be in the range 1..=100".into(),
128 });
129 }
130 if self.gpu_encode_inflight_tiles == Some(0) {
131 return Err(WsiDicomError::InvalidOptions {
132 reason: "gpu_encode_inflight_tiles must be greater than zero when provided".into(),
133 });
134 }
135 if self.gpu_encode_memory_mib == Some(0) {
136 return Err(WsiDicomError::InvalidOptions {
137 reason: "gpu_encode_memory_mib must be greater than zero when provided".into(),
138 });
139 }
140 if let Some(memory_mib) = self.gpu_encode_memory_mib {
141 let _ = usize::try_from(memory_mib)
142 .ok()
143 .and_then(|mib| mib.checked_mul(1024 * 1024))
144 .ok_or_else(|| WsiDicomError::InvalidOptions {
145 reason: "gpu_encode_memory_mib exceeds platform addressable memory".into(),
146 })?;
147 }
148 Ok(())
149 }
150}