Skip to content

Commit a35752d

Browse files
authored
Merge pull request #422 from dougyau/single-frame-gdcm
Decode single frame using GDCM
2 parents 9352c04 + d2e391f commit a35752d

File tree

1 file changed

+194
-12
lines changed

1 file changed

+194
-12
lines changed

pixeldata/src/gdcm.rs

Lines changed: 194 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Decode pixel data using GDCM when the default features are enabled.
22
33
use crate::*;
4+
use dicom_dictionary_std::tags;
45
use dicom_encoding::adapters::DecodeError;
56
use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
67
use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
@@ -18,28 +19,31 @@ where
1819
use super::attribute::*;
1920

2021
let pixel_data = pixel_data(self).context(GetAttributeSnafu)?;
21-
22+
2223
let cols = cols(self).context(GetAttributeSnafu)?;
2324
let rows = rows(self).context(GetAttributeSnafu)?;
2425

2526
let photometric_interpretation =
2627
photometric_interpretation(self).context(GetAttributeSnafu)?;
27-
let pi_type = GDCMPhotometricInterpretation::from_str(photometric_interpretation.as_str())
28-
.map_err(|_| {
29-
UnsupportedPhotometricInterpretationSnafu {
30-
pi: photometric_interpretation.clone(),
31-
}
32-
.build()
33-
})?;
28+
let pi_type = match photometric_interpretation {
29+
PhotometricInterpretation::PaletteColor => GDCMPhotometricInterpretation::PALETTE_COLOR,
30+
_ => GDCMPhotometricInterpretation::from_str(photometric_interpretation.as_str())
31+
.map_err(|_| {
32+
UnsupportedPhotometricInterpretationSnafu {
33+
pi: photometric_interpretation.clone(),
34+
}
35+
.build()
36+
})?,
37+
};
3438

3539
let transfer_syntax = &self.meta().transfer_syntax;
3640
let registry =
3741
TransferSyntaxRegistry
38-
.get(&&transfer_syntax)
42+
.get(transfer_syntax)
3943
.context(UnknownTransferSyntaxSnafu {
4044
ts_uid: transfer_syntax,
4145
})?;
42-
let ts_type = GDCMTransferSyntax::from_str(&registry.uid()).map_err(|_| {
46+
let ts_type = GDCMTransferSyntax::from_str(registry.uid()).map_err(|_| {
4347
UnsupportedTransferSyntaxSnafu {
4448
ts: transfer_syntax.clone(),
4549
}
@@ -66,7 +70,7 @@ where
6670
};
6771
if fragments.len() > 1 {
6872
// Bundle fragments and decode multi-frame dicoms
69-
let dims = [cols.into(), rows.into(), number_of_frames.into()];
73+
let dims = [cols.into(), rows.into(), number_of_frames];
7074
let fragments: Vec<_> = fragments.iter().map(|frag| frag.as_slice()).collect();
7175
decode_multi_frame_compressed(
7276
fragments.as_slice(),
@@ -144,6 +148,184 @@ where
144148
window,
145149
})
146150
}
151+
152+
fn decode_pixel_data_frame(&self, frame: u32) -> Result<DecodedPixelData<'_>> {
153+
use super::attribute::*;
154+
155+
let pixel_data = pixel_data(self).context(GetAttributeSnafu)?;
156+
157+
let cols = cols(self).context(GetAttributeSnafu)?;
158+
let rows = rows(self).context(GetAttributeSnafu)?;
159+
160+
let photometric_interpretation =
161+
photometric_interpretation(self).context(GetAttributeSnafu)?;
162+
let pi_type = match photometric_interpretation {
163+
PhotometricInterpretation::PaletteColor => GDCMPhotometricInterpretation::PALETTE_COLOR,
164+
_ => GDCMPhotometricInterpretation::from_str(photometric_interpretation.as_str())
165+
.map_err(|_| {
166+
UnsupportedPhotometricInterpretationSnafu {
167+
pi: photometric_interpretation.clone(),
168+
}
169+
.build()
170+
})?,
171+
};
172+
173+
let transfer_syntax = &self.meta().transfer_syntax;
174+
let registry =
175+
TransferSyntaxRegistry
176+
.get(transfer_syntax)
177+
.context(UnknownTransferSyntaxSnafu {
178+
ts_uid: transfer_syntax,
179+
})?;
180+
let ts_type = GDCMTransferSyntax::from_str(registry.uid()).map_err(|_| {
181+
UnsupportedTransferSyntaxSnafu {
182+
ts: transfer_syntax.clone(),
183+
}
184+
.build()
185+
})?;
186+
187+
let samples_per_pixel = samples_per_pixel(self).context(GetAttributeSnafu)?;
188+
let bits_allocated = bits_allocated(self).context(GetAttributeSnafu)?;
189+
let bits_stored = bits_stored(self).context(GetAttributeSnafu)?;
190+
let high_bit = high_bit(self).context(GetAttributeSnafu)?;
191+
let pixel_representation = pixel_representation(self).context(GetAttributeSnafu)?;
192+
let planar_configuration = if let Ok(el) = self.element(tags::PLANAR_CONFIGURATION) {
193+
el.uint16().unwrap_or(0)
194+
} else {
195+
0
196+
};
197+
let rescale_intercept = rescale_intercept(self);
198+
let rescale_slope = rescale_slope(self);
199+
let number_of_frames = number_of_frames(self).context(GetAttributeSnafu)?;
200+
let voi_lut_function = voi_lut_function(self).context(GetAttributeSnafu)?;
201+
let voi_lut_function = voi_lut_function.and_then(|v| VoiLutFunction::try_from(&*v).ok());
202+
203+
let decoded_pixel_data = match pixel_data.value() {
204+
Value::PixelSequence(v) => {
205+
let fragments = v.fragments();
206+
let gdcm_error_mapper = |source: GDCMError| DecodeError::Custom {
207+
message: source.to_string(),
208+
source: Some(Box::new(source)),
209+
};
210+
211+
let frame = frame as usize;
212+
let data = if number_of_frames == 1 && fragments.len() > 1 {
213+
fragments.iter().flat_map(|frame| frame.to_vec()).collect()
214+
} else {
215+
fragments[frame].to_vec()
216+
};
217+
218+
match ts_type {
219+
GDCMTransferSyntax::ImplicitVRLittleEndian
220+
| GDCMTransferSyntax::ExplicitVRLittleEndian => {
221+
// This is just in case of encapsulated uncompressed data
222+
let frame_size = cols * rows * samples_per_pixel * (bits_allocated / 8);
223+
data.chunks_exact(frame_size as usize)
224+
.nth(frame)
225+
.map(|frame| frame.to_vec())
226+
.unwrap_or_default()
227+
}
228+
_ => {
229+
let buffer = [data.as_slice()];
230+
let dims = [cols.into(), rows.into(), 1];
231+
232+
decode_multi_frame_compressed(
233+
&buffer,
234+
&dims,
235+
pi_type,
236+
ts_type,
237+
samples_per_pixel,
238+
bits_allocated,
239+
bits_stored,
240+
high_bit,
241+
pixel_representation as u16,
242+
)
243+
.map_err(gdcm_error_mapper)
244+
.context(DecodePixelDataSnafu)?
245+
.to_vec()
246+
}
247+
}
248+
}
249+
Value::Primitive(p) => {
250+
// Uncompressed data
251+
let frame_size = cols as usize
252+
* rows as usize
253+
* samples_per_pixel as usize
254+
* (bits_allocated as usize / 8);
255+
p.to_bytes()
256+
.chunks_exact(frame_size)
257+
.nth(frame as usize)
258+
.map(|frame| frame.to_vec())
259+
.unwrap_or_default()
260+
}
261+
Value::Sequence(_) => InvalidPixelDataSnafu.fail()?,
262+
};
263+
264+
// Convert to PlanarConfiguration::Standard
265+
let decoded_pixel_data = if planar_configuration == 1 && samples_per_pixel == 3 {
266+
interleave_planes(
267+
cols as usize,
268+
rows as usize,
269+
bits_allocated as usize,
270+
decoded_pixel_data,
271+
)
272+
} else {
273+
decoded_pixel_data
274+
};
275+
276+
let window = match (
277+
window_center(self).context(GetAttributeSnafu)?,
278+
window_width(self).context(GetAttributeSnafu)?,
279+
) {
280+
(Some(center), Some(width)) => Some(WindowLevel { center, width }),
281+
_ => None,
282+
};
283+
284+
Ok(DecodedPixelData {
285+
data: Cow::from(decoded_pixel_data),
286+
cols: cols.into(),
287+
rows: rows.into(),
288+
number_of_frames: 1,
289+
photometric_interpretation,
290+
samples_per_pixel,
291+
planar_configuration: PlanarConfiguration::Standard,
292+
bits_allocated,
293+
bits_stored,
294+
high_bit,
295+
pixel_representation,
296+
rescale_intercept,
297+
rescale_slope,
298+
voi_lut_function,
299+
window,
300+
})
301+
}
302+
}
303+
304+
fn interleave_planes(cols: usize, rows: usize, bits_allocated: usize, data: Vec<u8>) -> Vec<u8> {
305+
let frame_size = cols * rows * (bits_allocated / 8);
306+
let mut interleaved = Vec::with_capacity(data.len());
307+
308+
let mut i = 0;
309+
while i < frame_size {
310+
interleaved.push(data[i]);
311+
if bits_allocated > 8 {
312+
interleaved.push(data[i + 1])
313+
}
314+
315+
interleaved.push(data[i + frame_size]);
316+
if bits_allocated > 8 {
317+
interleaved.push(data[i + frame_size + 1])
318+
}
319+
320+
interleaved.push(data[i + frame_size * 2]);
321+
if bits_allocated > 8 {
322+
interleaved.push(data[i + frame_size * 2 + 1])
323+
}
324+
325+
i = if bits_allocated > 8 { i + 2 } else { i + 1 };
326+
}
327+
328+
interleaved
147329
}
148330

149331
#[cfg(test)]
@@ -225,7 +407,7 @@ mod tests {
225407
#[case("pydicom/SC_rgb_rle_2frame.dcm", 1)]
226408
#[case("pydicom/JPEG2000.dcm", 0)]
227409
#[case("pydicom/JPEG2000_UNC.dcm", 0)]
228-
fn test_parse_dicom_pixel_data_individual_frames(#[case] value: &str, #[case] frame: u32) {
410+
fn test_parse_dicom_pixel_data_individual_frames(#[case] value: &str, #[case] frame: u32) {
229411
let test_file = dicom_test_files::path(value).unwrap();
230412
println!("Parsing pixel data for {}", test_file.display());
231413
let obj = open_file(test_file).unwrap();

0 commit comments

Comments
 (0)