enet4 / dicom-rs Goto Github PK
View Code? Open in Web Editor NEWRust implementation of the DICOM standard
Home Page: https://dicom-rs.github.io
License: Apache License 2.0
Rust implementation of the DICOM standard
Home Page: https://dicom-rs.github.io
License: Apache License 2.0
(1001,041e) ST [Date: 20210212025552[^]Date: 20210217033201[^]Date: 20210226002420[^]] # 70, 1 Unknown Tag & Data
Attempting to read in rust fails with:
Error: ParseMetaDataSet { source: UndefinedValueLength { tag: Tag(0x0022, 0x0016), backtrace: Backtrace(()) } }
dcmtk - dcmdump works fine to read the above tag which is where the line came from above.
The attributes in the struct dicom_object::meta::FileMetaTable
retain the padding from the encoded DICOM file meta group, and so they are usually padded for even length, even when built via the builder. While this makes it easy to serialize, it is not very useful when inspecting the attributes.
This calls for new methods to FileMetaTable
which automatically trim the padding character out before returning the string slice.
Example signature (should be one per textual attribute):
fn transfer_syntax(&self) -> &str { unimplemented!() }
Hello, I'm researching if would it be possible to parse DICOM files, extract pixel data and resize it as thumbnail in i.e. JPEG, so it would be possible to preview in any web browser, if it's possible how could I approach it by dicom-rs?
There are some cases which produce this error. The transfer syntax is 1.2.840.10008.1.2.4.70
. I'd be happy to help fixing it
And this is the error message:
ParseMetaDataSet { source: NotDicom { backtrace: Backtrace(()) } }
Some DICOM file is just OK, but I have several files that will occur the error above. If you need the files, I will upload it after anonymization dealings be done.
Laying out some ideas and issues to keep in mind in order to improve the overall experience with the dicom-object API.
from_*
/write
/save
functions could be more intuitive.
DicomObject
trait is not sufficiently powerful enough to be usable.
meta_mut()
would probably help, but then it would become too easy to provide inconsistent information, unless it is safeguarded with getters and setters. Related with #70.
Hi - I just implemented a DICOM RLE decoder in Rust which you can find here: https://github.com/chafey/dicomrle-rs
I wanted to let you know in case you want to use it for adding support for RLE decoding to this library. I would also appreciate any feedback as I am new to Rust. Thanks!
Hello,
This may seem silly issue but i spent hours trying to figure out a way to solve this and i couldn't.
use dicom::core::*;
use dicom::object::*;
fn main() {
let obj = open_file("01.dcm");
let patient_name = obj.element_by_name("PatientName").to_str();
let modality = obj.element_by_name("Modality").to_str();
let pixel_data_bytes = obj.element(Tag(0x7FE0, 0x0010)).to_bytes();
}
I keep getting an error that element_by_name
and element
are not found.
As defined in the roadmap, some situations while working with DICOM data may not be critical to impose a soft error or panic, but should still be called out in some way. Logging comes to play as a non-invasive mechanism to report abnormal situations which do not impede the process, but may bring other issues along the way. As such, it is still relevant to provide this information to developers and to end users of CLI tools.
The two phases for fulfilling this issue would be:
log
, slog
, or tracing
.
tracing
it is.dicom-dump
, dicom-scproxy
, and so on). One that prints to stdout/stderr would suffice.A single trailing space of a valid time or date can interfere with the core crate's capability of transforming the DICOM value into a chrono date/time value.
let val = PrimitiveValue::from("170435.45 ");
assert_eq!(val.to_time()?, NaiveTime::from_hms_micro(17, 4, 35, 450_000));
There's a TODO
https://github.com/Enet4/dicom-rs/blob/master/object/src/mem.rs#L305
Is it clear how it should be implemented? It may fix #10
This would enable separate crates to install new transfer syntax implementations into end programs.
The way I currently imagine this is:
We need to create a new extension trait for DICOM objects, through which the various implementations can retrieve all the necessary properties, such as the dimensions, photometric interpretation, and the pixel data proper.
Some quick pseudo-Rust to get the idea across:
pub trait PixelDataObject {
fn rows(&self) -> u16;
fn columns(&self) -> u16;
fn samples_per_pixel(&self) -> u16;
fn bits_allocated(&self) -> u8;
fn bits_stored(&self) -> u8;
fn rescale(&self) -> Rescale;
fn photometric_interpretation(&self) -> PhotometricInterpretation;
/// Should return either a byte slice/vector if native pixel data
/// or byte fragments if encapsulated
fn raw_pixel_data(&self) -> RawPixelData;
}
This trait would be implemented by InMemDicomObject
, and perhaps by FileDicomObject<O>
.
TransferSyntax
codecWith the trait above, transfer syntax implementations would then rely on a trait object of said trait to retrieve the image and/or frames of the image.
More details about this will be defined, but for now the short explanation is that Codec
could not serve its purpose so well in its current state, and will need to be modified.
Hey,
I'm trying to use dicom-rs
with wasm bindgen for a subassembly app. So far I have something like this function to load a dicom file using JS fetch
pub async fn load_url(url: String) -> Result<(), JsValue> {
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);
let request = Request::new_with_str_and_init(&url, &opts)?;
let window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
// `resp_value` is a `Response` object.
assert!(resp_value.is_instance_of::<Response>());
let resp: Response = resp_value.dyn_into().unwrap();
// Convert this other `Promise` into a rust `Future`.
let array_buffer = JsFuture::from(resp.array_buffer()?).await?;
let typebuf: js_sys::Uint8Array = js_sys::Uint8Array::new(&array_buffer);
let mut slice = vec![0; typebuf.length() as usize];
typebuf.copy_to(&mut slice[..]);
let mut file = Cursor::new(slice);
let obj = from_reader(&mut file).unwrap();
// let patient_name = obj.element_by_name("PatientName").unwrap().to_str().unwrap();
// console::log_1(&patient_name.to_string().into());
Ok(())
}
Problem is the line let obj = from_reader(&mut file).unwrap()
fails with :
I'm using the latest master. Not sure if the function I'm calling on the dicom-rs side is the right one. The import is the following: use dicom_object::from_reader;
It would be interesting to have the project's dcmdump print colored output for better readability and analysis.
This would give tag numbers, attribute names, and respective values, in different colours if the output device is a TTY.
termcolor
seems to be the right way to do this.
I am not very experienced with Rust so the issue might be explained easily.
Please explain how to switch from ValueReadStrategy::Preserved
to ValueReadStrategy::Interpreted
option. My issue is the following: I get a DICOM element of DS
type however the element holds PrimitiveValue::Strs
instead of PrimitiveValue::F64
because of using decode::read_value_preserved
rather than decode::read_value
inside the library based on what I have studied in the code.
My sample code is following:
fn print_value<I, P>(value: &DicomValue<I, P>) -> &str {
if let Ok(_) = value.string() {
return "string";
}
if let Ok(_) = value.uint16() {
return "uint16";
}
if let Ok(_) = value.float64() {
return "float64";
}
return "unknown"
}
I call print_value
for each DataElement
of a file. Almost any 16-bit image file contains the Window Center
/Window Width
tags which are float-point values. However, my code never returns float64
.
The abstraction FileMetaTable
, which contains the processed properties of a DICOM file's meta information group, is defined as a plain data struct. It is easy for a user to construct a table in such a way that always results in a corrupted DICOM file. And once modifying the attributes of an existing this file meta group becomes a feature of the crate, better facilities should be provided to ensure that the file remains consistent.
This issue serves to:
FileMetaTable
, or another type which would take the same responsibilities.
into_element_iter
and write
do not validate the length, but perhaps they should.&mut self
methods for setting and clearing file meta information properties while doing the necessary adjustments.&self
methods for retrieving clean values of these propertiesMany important DICOM attributes rely on a textual value representation even if their intent is to encode numbers (in emphasis, DS and IS). On the other hand, it might be easier for some users to collect numbers in binary form and create a DICOM value directly from them.
The idea is the following: provide the method into_text_value(self)
that consumes the value (PrimitiveValue
and Value<I, P>
) and returns a value (Self
) which is always represented as text, performing a conversion if necessary.
let instance_number = dicom_value!(U16, [10]);
let instance_number_text = instance_number.into_text_value();
assert_eq!(instance_number_text, dicom_value!(Strs, ["10"]));
Is it a goal or non-goal?
It worked out of the box before 0.1, but not supported by inventory yet.
After reading PixelData there may be a number of transformations which should be applied to individual pixels like RescaleSlope / RescaleIntercept / WindowCenter / WIndowWidth / and may be more.
Is there a rust crate or other convenient tool for correct transformations of pixels after reading them?
Hi,
I have this valid dicom file. i tried to run the simple program in the readme but i receive the following error:
Error: ReadValue(ParseFloat(ParseFloatError { kind: Invalid }))
at the line
let obj = open_file("949559.dcm")?;
What can it be?
Hi @Enet4! Thank you for creating such an awesome library!
I have a specific use case: loading big .dcm
files (over 500Mb) in browser. At the moment most of the browsers have a limit for webassembly memory of about 2Gb, and when I run my program, it usually crashes with out of memory error.
Is there a way we can collaborate to support lazy loading?
In particular, I need to iterate over the PixelData items in Multi-frame file
The current data set writing capabilities via the object write_dataset
methods allow the user to choose the transfer syntax of the output data:
let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2.1").unwrap();
obj.write_dataset_with_ts(&mut out, &ts)?;
However, the methods do not check whether the object's pixel data encoding matches the requested output transfer syntax. It merely passes over the pixel data as is, either as native encoded pixel data or as a sequence of fragments. They were designed this way so that this implementation does not have to check the pixel data and other properties to identify how it was encoded. However:
This might only be tackled when the image abstraction becomes available, but it's worth recording this concern for now.
The current file reading implementation requires DICOM files to have a file meta group. The only way to open it would be by specifying the expected transfer syntax (introduced in #84). Although to the best of my knowledge the lack of this group is non-standard, it is known that some implementations are capable of resolving this anyway.
One could start by writing tests for the test files in dicom-test-files
without a file meta group (see example in #48), then working your way into making them pass.
Intra-doc links are already stabilized, have worked in docs.rs for far longer than that, and are easier to write and less error prone than working with HTML links directly. I believe it is time for DICOM-rs to embrace them.
I would be interested in replacing existing links with intra-doc links where applicable.
Let this issue be used to keep track of how the project is currently encoding and providing data dictionaries, as specified in DICOM PS3.6 - Data Dictionary.
dicom-core
dicom-dictionary-std
crate.transfer-syntax-registry
I started playing with this library, as I used Rust in my previous job and now I have to work with DICOM. As I tried the dcmdump
tool on one of my files, I unfortunately ended up with an error:
Error: ReadValue(DateTimeZone)
I looked at the source code but I could not really figure out where this error comes from. However when I looked at my DICOM file with dcmtk/dcmdump
I saw the following:
(0008,0020) DA [19620728] # 8, 1 StudyDate
(0008,0021) DA [19000101] # 8, 1 SeriesDate
(0008,0022) DA [19620728] # 8, 1 AcquisitionDate
(0008,0023) DA [19620728] # 8, 1 ContentDate
(0008,0030) TM [000000.000000] # 14, 1 StudyTime
As you see those dates are before the UNIX epoch. I am not sure if this has anything to do with the error I encountered, but it could be (I don't think these dates are real, these files have been through some anonymization processes, probably multiple times).
I am happy to help debugging the issue, unfortunately I cannot send you any DICOM files, so I can only help executing stuff on your behalf :)
I've been working on SCU/SCP support. I've had to pause because of the missing write support.
With that said, what are you thinking with regards to writing? Do you want to stub out some traits and then I will handle the implementation/tests?
In the context of queries, data elements with the value representations DA, TM, or DT can have two time instances separated by a dash (standard). While no decoded value variant is available for ranges, they can be retrieved from string variants.
I picture being able to do the following:
let val = dicom_value!(Str, "20200313-20200930");
let (start, end) = val.date_range()?;
assert_eq!(start, NaiveDate::from_ymd(2020, 3, 13));
assert_eq!(end, NaiveDate::from_ymd(2020, 9, 30));
Keeping a table and checklist of requirements regarding text encoding in DICOM:
encoding | implemented | validated | Issue / PR |
---|---|---|---|
ISO IR 100 (West Europe) | ✔️ | baseline only | #53 |
ISO IR 101 (Central/East Europe) | ✔️ | baseline only | #53 |
ISO IR 109 (South Europe) | ✔️ | baseline only | #53 |
ISO IR 110 (North Europe) | ✔️ | baseline only | #53 |
ISO IR 144 (Cyrillic) | ✔️ | baseline only | #53 |
ISO IR 192 (UTF-8) | ✔️ | baseline only | |
GB18030 (Simplified Chinese) | ✔️ | baseline only | #51 #52 |
ISO IR 13 (Japanese Katakana) | |||
ISO IR 87 (Japanese Kanji/Hiragana/Katakana) |
In addition:
Although error types are already statically delimited, errors emerging from the library currently contain little to no context about where they happened. This is an example of an error value which may emerge when opening a file: DataSetSyntax(PrematureEnd)
Requesting a backtrace is not useful because the error was raised with the ? operator, without keeping the full backtrace from the origin of the error at the library level. quick-error
has been originally employed as a means to easily create error types, but for a solution of this scale, it becomes harder to work with and does not contribute to good errors.
As declared in the roadmap, it is of my interest to change the existing error definitions to something more informative and organized. I will try to rewrite existing errors with the use of another crate (likely either snafu
or thiserror
), which will provide the much needed quality of these error values.
It was briefly discussed in comments for #22
The file to reproduce (at the end of the comment) pydicom/pydicom#738 (comment)
Stackoverflow answer https://stackoverflow.com/a/20886301
Page in standard http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_A.4.html#table_A.4-1
Hello, thank you for providing DICOM support for Rust.
I was trying to run a minimal example using this data.
ftp://medical.nema.org/medical/dicom/DataSets/WG16/Siemens/Siemens_spectroEnhDICOM/
use dicom_object::open_file;
use dicom_object::Result;
let file_path = "Anonymous.MR._.12.1.2017.06.28.09.41.02.294.59320505.dcm";
let obj = open_file(file_path);
let is_ok = obj.is_ok();
println!("is ok {}", is_ok);
Always print false. I tried with other datasets too.
My rustc version:
rustc --version
rustc 1.38.0 (625451e37 2019-09-23)
How can I make it work?
Thanks.
I have a question about some concerns I have about pre-interpreting data of certain VR types.
For example, the VR::IS
is being interpreted as an [i32]
internally.
Because of this, if we were to load a dicom file, and save it without making any modifications, the saved file would be normalized, in that the VR::IS
would be converted into an array of integers, and then back to a string.
This would also pose a problem with implementing validation checkers. I'd like to develop a command line tool that loads a DICOM file, and validates it's contents. I can't validate any field at all if any one field prevents me from loading the entire DICOM file.
IMO, it is important to keep values that are represented with the Default Character Repertoire
as characters. We could provide utilities to re-interpret the values into their typed representation (date times, date ranges, integer strings, etc) after the DICOM file is loaded.
In the core crate, retrieving a string or sequence of strings from a value may yield unwanted padding whitespace, usually added to keep values with an even length.
While at this time I am not interested in automatically stripping the whitespace out, it would be nice to have some getter methods which provide a "clean" version of the string.
let value = PrimitiveValue::from("1.2.345\0".to_string());
assert_eq!(&value.to_clean_str(), "1.2.345");
let value = dicom_value!(Strs, ["ONE", "TWO", "THREE", "SIX "]);
assert_eq!(&value.to_clean_str(), "ONE\\TWO\\THREE\\SIX");
Apologies for the likely naive question, but is there a way to retrieve the PixelData from a DICOM, such that it could be opened with the image
crate or something similar?
Thanks!
This does not work with python 3.8. When do you think this will support that version?
Take a look at dicom.dic
.
It is carefully curated by the DCMTK team, in coordination with the authors of the DICOM standard. It is also updated routinely with supplements (take a look at git history).
I recommend updating the dictionary build to use it, instead of the xml version of the DICOM standard. You wouldn't need the separate/static list of "META" entries. This change would also invalidate this PR.
Allow the user of dicom_object
to take the element from the object, removing it in the process.
For in-memory objects, this can translate to less data copying.
/// <document please>
pub fn take_element(&mut self, tag: Tag) -> Result<InMemElement<D>> {
todo!()
}
/// <document please>
pub fn take_element_by_name(&mut self, name: &str) -> Result<InMemElement<D>> {
let tag = self.lookup_name(name)?;
self.take_element(tag)
}
I have a case with Tag(0x0002, 0x0001)
, VR: OB
, and Length(0)
. Is it a correct case? I haven't found a corresponding page in the standard so far.
Hi @Enet4 !
Thanks for this awesome library!
I'm now using this library to generate thumbnails for a bunch of JPEG lossless compressed dicoms that I have. I created a simple abstraction here: https://github.com/pevers/dicom-pixel-data, using GDCM bindings to decode pixel data into a DynamicImage
:
Example from the tests:
let obj = open_file(dicom_test_files::path(value).unwrap()).unwrap();
let image = obj.decode_pixel_data().unwrap().to_dynamic_image().unwrap();
image
.save(format!(
"out.png",
))
.unwrap();
Not everything is supported yet, but it can at least decode a bunch of example lossless JPEG dicoms from the Pydicom test set. I created a thin GDCM wrapper because I don't think there is solid support for the wide variety of dicom compression formats in Rust.
My question: I read on roadmap that this is a requested feature. Can I help by creating a PR to this repo? Or is this already in the making? I'm fairly new to Rust so I guess it would require some extensive reviewing but I would be glad to help out a bit.
Thanks again for your work!
Cheers
Sometimes the date in DICOM file is 0000-00-00
. Is it a common case in the ecosystem when the date is empty?
Hi @Enet4 ! I'd like to implement additional transfer syntax in my local fork, in particular
JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14 [Selection Value 1]) 1.2.840.10008.1.2.4.70
Can you give any advice about doint it? Even link to a good doc would be helpful, because at the moment I'm stuck with the question how do transfer syntaxes work together, for example is any of the JPEG transfer syntaxes a big endian or little endian?
I also would be happy to share these changes once you are ready to accept PRs
As per the standard, part 5, section 6.2:
A component that is omitted from the string is termed a null component. Trailing null components of Date Time indicate that the value is not precise to the precision of those components. The YYYY component shall not be null. Non-trailing null components are prohibited. The optional suffix is not considered as a component.
As the variants of a PrimitiveValue
currently decode date and time values into chrono
date/time types, we no longer hold the information of which components were null once decoded from their textual form. The null components are reverted to the "first" values when decoding a date with these: january, day 1, 0 hours, 0 minutes, etc.
Since textual representations are already preserved default (this is done in #9), the DICOM parser will choose to keep DA, TM, and DT as plain text, and thus no information will be lost. Still, it is best to also have some way to store these values in a binary format, one that retains information about null components while still enabling an intuitive conversion to a standard date/time type for subsequent manipulation.
One acceptable way of resolving this would be:
PartialDate
) with multiple variants depending on how many components are defined. They might best be implemented as enums, and would have to have several utility methods in order to be usable..to_partial_date
, .to_partial_time
, .to_partial_datetime
on DICOM value types.While standard group length data elements (xxxx, 0000) are retired, their tags are currently not declared in the data dictionary, and so are perceived as unknown.
Implementing this might involve checking the tag for the 0000 element part of the tag separately, while letting known special group element tags through.
There's a number of different tags possible in file meta information, as described in http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html#table_7.1-1
Is it possible to support additional tags in FileMetaTable, which at the moment has the following check
if elem.tag() != (0x0002, 0x0001) {
return Err(Error::UnexpectedTag(elem.tag()));
}
dicom_object
is missing methods for writing the object's data set to arbitrary data writers.
This is a requirement for connecting this API with the upper layer protocol, in order to create DICOM network services.
Sorry for this stupid question but I tried to do my best for a few hours... better to ask here. Perhaps, this is a problem of my bad skills with Rust.
I have a DicomValue<I, P>
object with SQ tags. I call items
to further iterating thru [I]
However, I
is a simple EmptyObject
structure by default so I have no idea how to retrieve the sequence items.
Can you share a sample code of working with SQ tags?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.