Code Monkey home page Code Monkey logo

dicompyler-core's Introduction

dicompyler

NOTE: dicompyler has been archived and is no longer supported. The developers of dicompyler and DVH Analytics have teamed up to form ONCurate.

dicompyler screenshot

dicompyler is an extensible open source radiation therapy research platform based on the DICOM standard. It also functions as a cross-platform DICOM RT viewer.

dicompyler is written in Python and is built on a number of technologies including: pydicom, wxPython, Pillow, and matplotlib and runs on Windows, Mac OS X and Linux.

dicompyler is released under a BSD license.

Take a tour of dicompyler by checking out some screenshots or download a copy today.

alt text alt text

Downloads:

Downloads are available through Google Drive:

Version 0.4.2: Windows | Mac | Source | Test Data - Released July 15th, 2014 - Release Notes

Features:

  • Import CT/MR/PET Images, DICOM RT structure set, RT dose and RT plan files
  • Extensible plugin system with included plugins:

For upcoming features, see the project roadmap.

System Requirements:

  • Windows XP/Vista/7/8/10
  • Mac OS X 10.5 - 10.13 (Intel) - Must bypass Gatekeeper
  • Linux - via a package from PyPI or a Debian package (courtesy of debian-med)

If you are interested in building from source, please check out the build instructions.

Getting Started:

  • How to run dicompyler:
    • If you have downloaded dicompyler as an application for Windows or Mac, please follow the normal process for running any other application on your system.

    • If you are running from a Python package, a script called "dicompyler" will now be present on your path, which you can run from your command line or terminal.

    • If you are running from a source checkout, there is a script in the main folder called "dicompyler_app.py" which can be executed via your Python interpreter.

dicompyler will read properly formatted DICOM and DICOM-RT files. To get started, run dicompyler and click "Open Patient" to bring up a dialog box that will show the DICOM files in the last selected directory. You may click "Browse..." to navigate to other folders that contain DICOM data.

In the current version of dicompyler, you can import any DICOM CT, PET, or MRI image series, DICOM RT structure set, RT dose and RT plan files. dicompyler will automatically highlight the most dependent item for the patient. All related items (up the tree) will be automatically imported as well.

Alternatively, you can selectively import data. For example, If you only want to import CT images and an RT structure set just highlight the RT structure set. If you are importing an RT dose file and the corresponding plan does not contain a prescription dose, enter one in the box first. To import the data, click "Select" and dicompyler will process the information.

Once the DICOM data has been loaded, the main window will show the patient and plan information. Additionally it will show a list of structures and isodoses that are associated with the plan.

Getting Help:

  • As a starting point, please read the FAQ as it answers the most commonly asked questions about dicompyler.
  • If you are unable to find the answer in the FAQ or in the wiki, dicompyler has a discussion forum hosted on Google Groups.

Citing dicompyler:

  • If you need to cite dicompyler as a reference in your publication, please use the following citation:
    • A Panchal and R Keyes. "SU-GG-T-260: dicompyler: An Open Source Radiation Therapy Research Platform with a Plugin Architecture" Med. Phys. 37, 3245, 2010
    • The reference in Medical Physics can be accessed via http://dx.doi.org/10.1118/1.3468652

dicompyler-core's People

Contributors

a-detiste avatar bastula avatar cutright avatar cvelten avatar darcymason avatar davidchall avatar dependabot[bot] avatar gacou54 avatar gertsikkema avatar github-actions[bot] avatar inamoto85 avatar lgtm-migrator avatar nicocrm avatar pyup-bot avatar smichi23 avatar wkt84 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dicompyler-core's Issues

Incorrect DVHs for FFS, HFP patient orientations (non-HFS?)

Some users of my software (which uses dicompyler-core) have noticed an issue with incorrect DVHs. This appears to be verified when opening FFS and HFP patients in the dicompyler GUI; the structures do not properly overlay with the CT data set.

Original issue posted here: cutright/DVH-Analytics#62

Multiple users have identified a bug where DVHs are incorrectly calculated for Foot-First-Supine patient orientations. This appears to be an issue with dicompyler-core and can be observed by independently loading CT and RT-Structure data in the dicompyler GUI/program, the structures to do not line up with the CT.

Thanks to Mahesh and Mike for catching this.

Are there any other users out that that have observed similar behavior for other patient orientations? It would be easy to check, simply include a Patient Orientation filter in your query. The DVHs will almost certainly not monotonically decrease.

The resolution is unclear for the moment, but at a minimum I'll flag FFS plans in DVHA on import in v0.7.5 when it's released.

Memory efficient use

I wish to load up to 30 plans for simultaneous analysis, since each plan is approx. 46 Mb this may be a problem for my PC (30*46 = 1.4 Gb). Is there a more memory efficient way to work with a large number of plans?
Cheers

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/38300136-memory-efficient-use?utm_campaign=plugin&utm_content=tracker%2F30933069&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F30933069&utm_medium=issues&utm_source=github).

Problem import of dose module

Hi,
I have 'ImportError' when I try to import dose module in my code. I use python 3.7.2. While dicomparser, dvh and dvhcalc module work fine. Any idea?
Thanks a lot

Install via setup.py

Hi Adit
I previously installed dicompyler-core using pip and it worked OK.
I have now forked dicompyler-core and attempted to install my version using setup.py, but it fails and I receive the message below. Is there a fault in setup.py or my usage of it? Is it possible to install my fork 'over' the version I have already? Please note I am on a hospital computer without admin rights, I am not sure if this could be part of the problem, but I assume not since previous install via pip was successful.
Cheers
Robin

`C:\Users......\Desktop\GitHub\Useful-python-for-medical-physics\My
Forks\dicompyler-core>python setup.py install

running install
running bdist_egg
running egg_info
writing dependency_links to dicompyler_core.egg-info\dependency_links.txt
writing top-level names to dicompyler_core.egg-info\top_level.txt
writing requirements to dicompyler_core.egg-info\requires.txt
writing dicompyler_core.egg-info\PKG-INFO
reading manifest file 'dicompyler_core.egg-info\SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no previously-included files matching 'pycache' found under directory ''
warning: no previously-included files matching '
.py[co]' found under directory''
warning: no previously-included files matching '
.DS_Store' found under directory '*'
writing manifest file 'dicompyler_core.egg-info\SOURCES.txt'
installing library code to build\bdist.win32\egg
running install_lib
running build_py
copying dicompylercore\dvh.py -> build\lib\dicompylercore
creating build\bdist.win32\egg
creating build\bdist.win32\egg\dicompylercore
copying build\lib\dicompylercore\config.py -> build\bdist.win32\egg\dicompylercore
copying build\lib\dicompylercore\dicomparser.py -> build\bdist.win32\egg\dicompylercore
copying build\lib\dicompylercore\dvh.py -> build\bdist.win32\egg\dicompylercore
copying build\lib\dicompylercore\dvhcalc.py -> build\bdist.win32\egg\dicompylercore
copying build\lib\dicompylercore\util.py -> build\bdist.win32\egg\dicompylercore

copying build\lib\dicompylercore__init__.py -> build\bdist.win32\egg\dicompylercore
byte-compiling build\bdist.win32\egg\dicompylercore\config.py to config.cpython-35.pyc
byte-compiling build\bdist.win32\egg\dicompylercore\dicomparser.py to dicomparser.cpython-35.pyc
byte-compiling build\bdist.win32\egg\dicompylercore\dvh.py to dvh.cpython-35.pyc

Sorry: TabError: inconsistent use of tabs and spaces in indentation (dvh.py, line 290)
byte-compiling build\bdist.win32\egg\dicompylercore\dvhcalc.py to dvhcalc.cpython-35.pyc
byte-compiling build\bdist.win32\egg\dicompylercore\util.py to util.cpython-35.pyc
byte-compiling build\bdist.win32\egg\dicompylercore__init__.py to init.cpython-35.pyc
creating build\bdist.win32\egg\EGG-INFO
copying dicompyler_core.egg-info\PKG-INFO -> build\bdist.win32\egg\EGG-INFO
copying dicompyler_core.egg-info\SOURCES.txt -> build\bdist.win32\egg\EGG-INFO
copying dicompyler_core.egg-info\dependency_links.txt -> build\bdist.win32\egg\EGG-INFO
copying dicompyler_core.egg-info\not-zip-safe -> build\bdist.win32\egg\EGG-INFO
copying dicompyler_core.egg-info\requires.txt -> build\bdist.win32\egg\EGG-INFO
copying dicompyler_core.egg-info\top_level.txt -> build\bdist.win32\egg\EGG-INFO

creating 'dist\dicompyler_core-0.5.2-py3.5.egg' and adding 'build\bdist.win32\egg' to it
removing 'build\bdist.win32\egg' (and everything under it)
Processing dicompyler_core-0.5.2-py3.5.egg
creating c:\users\rcole02.royalsurrey\appdata\local\continuum\anaconda3\lib\site
-packages\dicompyler_core-0.5.2-py3.5.egg

error: could not create 'c:\users.....\appdata\local\continuum\an
aconda3\lib\site-packages\dicompyler_core-0.5.2-py3.5.egg': Access is denied

Shouldn't the range be calculated using max-min rather than max+min?

First of all, great work on Dicomplyer - it's a really nice piece of software!

I just noticed in this line of code that the window is calculated by adding the max+min values, whereas it looks like it should be max-min?

window = int(abs(wmax) + abs(wmin))

The existing code window=abs(max)-abs(min) will work in most cases because, for example (min=0,max=700) will give window=700 and (min=-1000,max=6000) will give window=7000. However, (min=100,max=700) would give window=800 instead of window 600 as expected. However, using window=max-min looks like it would work in all cases.

Dicomparser can't parse dicom files

Running into an odd issue. Did a fresh install of dicompyler-core today and tried to load in a few dicom files, except the dicomparser can't read them.

dp = dicomparser.DicomParser(datafolder+structloc+structfile)

produces this:


AttributeError Traceback (most recent call last)
in
----> 1 dp = dicomparser.DicomParser(datafolder+structloc+structfile)

~/anaconda3/envs/sandbox/lib/python3.6/site-packages/dicompylercore/dicomparser.py in init(self, dataset)
42 try:
43 self.ds =
---> 44 read_file(dataset, defer_size='100', force=True)
45 except:
46 # Raise the error for the calling method to handle

~/anaconda3/envs/sandbox/lib/python3.6/site-packages/pydicom/filereader.py in dcmread(fp, defer_size, stop_before_pixels, force, specific_tags)
878 try:
879 dataset = read_partial(fp, stop_when, defer_size=defer_size,
--> 880 force=force, specific_tags=specific_tags)
881 finally:
882 if not caller_owns_file:

~/anaconda3/envs/sandbox/lib/python3.6/site-packages/pydicom/filereader.py in read_partial(fileobj, stop_when, defer_size, force, specific_tags)
769 dataset_class = FileDataset
770 new_dataset = dataset_class(fileobj, dataset, preamble, file_meta_dataset,
--> 771 is_implicit_VR, is_little_endian)
772 # save the originally read transfer syntax properties in the dataset
773 new_dataset.set_original_encoding(is_implicit_VR, is_little_endian,

~/anaconda3/envs/sandbox/lib/python3.6/site-packages/pydicom/dataset.py in init(self, filename_or_obj, dataset, preamble, file_meta, is_implicit_VR, is_little_endian)
1362 big-endian.
1363 """
-> 1364 Dataset.init(self, dataset)
1365 self.preamble = preamble
1366 self.file_meta = file_meta

~/anaconda3/envs/sandbox/lib/python3.6/site-packages/pydicom/dataset.py in init(self, *args, **kwargs)
175 """Create a new Dataset instance."""
176 self._parent_encoding = kwargs.get('parent_encoding', default_encoding)
--> 177 dict.init(self, *args)
178 self.is_decompressed = False
179

~/anaconda3/envs/sandbox/lib/python3.6/site-packages/pydicom/dataset.py in getitem(self, key)
595 from pydicom.filereader import read_deferred_data_element
596 data_elem = read_deferred_data_element(
--> 597 self.fileobj_type, self.filename, self.timestamp,
598 data_elem)
599

~/anaconda3/envs/sandbox/lib/python3.6/site-packages/pydicom/dataset.py in getattr(self, name)
518 if tag is None: # name isn't a DICOM element keyword
519 # Try the base class attribute getter (fix for issue 332)
--> 520 return super(Dataset, self).getattribute(name)
521 tag = Tag(tag)
522 if tag not in self: # DICOM DataElement not in the Dataset

AttributeError: 'Dataset' object has no attribute 'fileobj_type'

PixelData = None causes crash

I came across an issue with DICOM files originating from QARC (which I believe uses CERR). For some reason the RTPlan files contained a PixelData attribute. Because it was set to None (rather than deleting it), the following line crashes:

self.pixel_array = self.ds.pixel_array

with the error:

TypeError: object of type 'NoneType' has no len()

I'll submit a PR shortly.

Change bin dimension

The script calculate the dose in cGy and thus the bin dimension is of 0.01. I would like to change bin dimension in 0.1 or 0.5 ... How I can obtain this? Thank you very well!

Dose Grid in y or x directions?

Is there an easy way to get the dose grid in x or y? the GetDoseGrid command only works in the z axis but in my specific application I am looking for the sagittal and coronal plane dose grids (interpolated) as well.

How to get DVH metrics for entire dose grid (without structures)

Hi all,

Is there a way to get some DVH metrics for the overall dose grid?
Something like V100% and V200%, with answer being in cc's? I presume this can be done without a (body/external contour) structure.

Any thoughts on how this can be done? Or do I need to write some code?

Thanks,
Marc

dvh.max gives IndexError

Hello and thank you for the project.

I am using dicompyler-core 0.5.4 to extract statistics and I have been running into an exception with certain DCM files. When calculating dvh.max, the following error is thrown:

 File "/home/nico/.local/share/virtualenvs/dcm-extractor-8Y6EYVZw/lib/python3.7/site-packages/dicompylercore/dvh.py", line 269, in max
    return diff.bins[1:][diff.counts > 0][-1]
IndexError: boolean index did not match indexed array along dimension 0; dimension is 0 but corresponding boolean dimension is 1

This seems to happen whenever dvh.counts is either empty, or has all same values (such that dvh.diff.counts is all 0s).

Please let me know if I can assist in troubleshooting.

Inputting cGy units into dvhcalc.get_dvh

First off, thanks for all the work on dicompyler-core.

I am trying to analyze DVHs calculated from RT Dose and RT Structure DICOM files, and am currently using the dvhcalc.get_dvh to obtain the doses at particular dose thresholds, e.g. as below.

dvhcalc.get_dvh.V50Gy

I can only ever input integers into the V(...)Gy field, and dvhcalc.get_dvh.V**cGy is not recognized. Is there any way I can input non-integer dose values into this function?

For context, I am attempting to analyze DVHs at certain dose thresholds/constraints that may not necessarily be integers when converted to Gy units. Another thing my current project entails is subtracting one contour from another (specifically, subtracting the contour of bladder contents from the whole bladder contour to obtain a bladder wall approximate), and I am attempting to recreate the dose bins/DVH for the bladder wall proxy for every cGy dose bin.

Thanks and regards.

How would I extract the 0.1cc value from a DVH?

Apologies if this has been answered somewhere but I was unable to easily find the syntax in the docs. Just based on example it seems that it's easy enough to get the 2cc value of a dvh by just typing in dvh.D2cc, however how would I do this for values that are not integers? e.g. 0.1cc?

typing in dvh.D0.1cc of course doesn't work as the . will confuse python.

I have also tried

dvh.statistic(dvh,'D0.1cc')
which returns a type error as well as

dvh.dose_constraint(dvh,0.1,volume_units=cc)
which returns 'cc' is not defined.

Is it possible to compute the DVH of joint structures?

I don't have access to an RT-struct file, I just have the outputs. In it I have 2 structs for left and right parotid glands, but I would like a single struct which is a combination of the two. Is it possible to compute the DVH to these two structs as a merged entity? e.g. a single DVH for both parotid glands instead of 2 DVHs for each.

Thanks

Structure volume calculation dependent on RTDose

This would be an issue for planning systems which export a limited dose grid (i.e. Oncentra Masterplan, brachytherapy). Structure volume should be calculated straight from RTSS dicom. It seems to be calculating volume by counting voxels of structure which overlap within dose grid.

This is probably an issue when using dvhcalc only. This may be an issue when trying to convert to relative volume.

Issues with float pixel spacing

Hi all!

In my work, I had to create RTDOSE files to store Monte Carlo data. When creating my DICOMs, I had pixel spacings of 0.3710000000000002325/0.37109999999931. When I tried to calculate the DVHs with dicompyler-core, I got this error:

File "/dvh_from_dosegrid/venv/lib/python3.8/site-packages/dicompylercore/dvhcalc.py", line 88, in get_dvh
calcdvh = _calculate_dvh(s, rtdose, limit, calculate_full_volume,
File "/dvh_from_dosegrid/venv/lib/python3.8/site-packages/dicompylercore/dvhcalc.py", line 218, in _calculate_dvh
planedata[z] = calculate_plane_histogram(plane, doseplane,
File "/dvh_from_dosegrid/venv/lib/python3.8/site-packages/dicompylercore/dvhcalc.py", line 283, in calculate_plane_histogram
hist, vol = calculate_contour_dvh(grid, doseplane, maxdose, dd, id,
File "/dvh_from_dosegrid/venv/lib/python3.8/site-packages/dicompylercore/dvhcalc.py", line 316, in calculate_contour_dvh
mask = ma.array(doseplane * dd['dosegridscaling'] * 100, mask=~mask)
File "/dvh_from_dosegrid/venv/lib/python3.8/site-packages/numpy/ma/core.py", line 6610, in array
return MaskedArray(data, mask=mask, dtype=dtype, copy=copy,
File "/dvh_from_dosegrid/venv/lib/python3.8/site-packages/numpy/ma/core.py", line 2906, in new
raise MaskError(msg % (nd, nm))
numpy.ma.core.MaskError: Mask and data not compatible: data size is 452188, mask size is 451242.

This error came from an int conversion of 477.9999999 to 477 in the intrapolated shapes. To solve that issue I simply rounded the lut new shapes.

Here is what I changed in file dvhcalc.py line 502 and 503:

col_samples = num_cols * min_pixel_spacing[1] / new_pixel_spacing[1]
row_samples = num_rows * min_pixel_spacing[0] / new_pixel_spacing[0]

to

col_samples = round(num_cols * min_pixel_spacing[1] / new_pixel_spacing[1])
row_samples = round(num_rows * min_pixel_spacing[0] / new_pixel_spacing[0])

Best,

Sam

RTSTRUCT dicom files from Oncentra Prostate software produce empty DVH

Hi, first of all thank you for this nice library!

I encounter a problem when calculating DVH with RTSTRUCT dicom files from Oncentra Prostate v4 software. The resulting DVH is always empty. This is because the dataset in those RTSTRUCT files does not have a ContourImageSequence tag. There is the condition in GetStructureCoordinates method where it appears (in dicomparser.py):

contourimagesequence

This tag does not seems to be used later in the code.

As you can see, this tag is not in RTSTRUCT dicom files from Oncentra Prostate v4:
contourimagenotthere

But in other cases (not Oncentra Prostate) they are:
contourimageisthere

I have solved my problem by simply removing this condition:
master...gacou5:bug/OCP-dicom-file-fail

I was wondering if there was any reason for the presence of this condition that I have not find out. If no I could do a Pull Request to remove this.

Thank you,
Gabriel

Calcdvh function performance when contour and rtdose planes are not the same.

I have a modified contour as a 'set of points' dict and I want to computer the dvh of this contour from a particular rtdose file. However, the slices of the contour are not co-planar. I.e. the slice location of rtdose may not necessarily match the slice location of the structure. Would the _calculate_dvh function still evaluate correctly in this case or would I have to interpolate the contour to match the rtdose planes?

Thanks in advance

dvhcalc interpolation_resolution issue

Hi,

First of all, congrats on this great tool!

I'm trying to use dicmpyler-core to calculate the DVH of some files. I got a result but I wanted to try increasing the resolution as shown in the documentation.
interpscar = dvhcalc.get_dvh("rtss.dcm", "rtdose.dcm", 8,interpolation_resolution= 2.5/16),interpolation_segments_between_planes=2,use_structure_extents=True)
However in my file the original resolution is 2.02265 and no matter what value I use it gives me an error.

AttributeError: New pixel spacing must be a factor of 2.02265/(2^n), where n is an integer.

Even when I use 2.02265!

AttributeError: New pixel spacing must be a factor of 2.02265/(2^n), where n is an integer. Value provided was 2.02265.

Am I doing something wrong? Any help is appreciated. Please let me know if you need some further information or data to reproduce the problem.

Best regards

Pedro

Is it possible to add an extra contour from a 'set of points' dict to a parsed rtss object?

I have a set of points dict, structure_planes, that I have gotten using the command

from dvha.tools.roi_geometry import overlap_volume
from dvha.tools.roi_formatter import dicompyler_roi_coord_to_db_string, get_planes_from_string
structure_planes=get_planes_from_string(dicompyler_roi_coord_to_db_string(parsed_rt_struct.GetStructureCoordinates(structure_id)))

where parsed_rt_struct is the parsed rt_struct file.

I then apply a custom function that performs translation of all the coordinates in structure_planes by simply adding some shift vector to all point triplets. (note: I have ensured that any translation in z is by multiples of the slice thickness, so everything should still be in the same z planes).

translated_structure_planes=Contour_Translate(structure_planes,[0.5,0.5,0])

I now want to add translated_structure_planes, which is a 'sets of points' dict, back into parsed_rt_struct as a new structure. Is this possible? I am wondering this because I want to extract DVHs on the translated struct but the dvh extraction functions usually take in a parsed rtss directly instead of using a "sets of points" dict.

Just FYI my translation function is below, all I do is add a shift vector to all coordinate points in the "sets of points" dict.

def Contour_Translate(contour_planes,shift_vector):
    """
    Applies translational shift to all points in a contour plane

    Parameters
    ----------
    contour_planes : Dict
        a "sets of points" formatted dictionary of input contour.
    shift_vector : List
        A list of size 3, specifying the contour shift vector as [x,y,z]. If this
        is used to compute overlap, then the z value *must* be evenly divisible by 
        slice thickness. i.e. if slice thickness is 3mm then z must be an integer
        multiple of 3. 

    Returns
    -------
    contour_planes_translated : Dict
        Output contour planes after translation, formatted as a 
        "sets of points" dictionary.

    """
    contour_planes_translated={}
    for key,plane in list(contour_planes.items()): #iterate over dictionary, k are keys representing each plane
        key=str(np.round(float(key)+shift_vector[2],2)) #the new key should match the z location
        #print(key)
        plane_copy=copy.deepcopy(plane)
        for point_index in range(0,len(plane[0])): #loop over points in this plane
            plane_copy[0][point_index]=list(np.around(np.array(plane[0][point_index])
                                       +np.array(shift_vector),decimals=2)) #add shift vector to all points    
        contour_planes_translated[key]=plane_copy
    
    return contour_planes_translated

Bad volume using get_dvh

Hello,

I used the version 0.5.6. RTstruct and RTdose are from Oncentra.
A big volume difference occurs with the output of dvhcalc.get_dvh which does not correspond to CalculateStructureVolume. The volume of CalculateStructureVolume seems to be alright by performing a check on another Software. So the DVH output of get_dvh are wrong. The issue seems similar to #26, but i did not understand how to get the right DVH value. How to resolve this point ?

Best regards,

Calculate structure volume

This should be a simple syntax question, but I can't quite figure out how to calculate the structure volume without computing a DVH (which can be time consuming). I can see that a parsed rtss has an attribute called GetStructureVolume which takes in inputs of coords and the thickness, so I thought I can extract the coords with rtss.GetStructureCoordinates(roi_number) and the thickness in one go, but the function CalculatePlaneThickness(self, planesDict): does not make sense to me, what should the input of this function be to calculate the thickness?

I want my final script to extract a volume in one line like

rtss.CalculateStructureVolume(rtss.GetStructureCoordinates(roi_number),rtss.CalculatePlaneThickness(planesDict))

But can't figure out how to generate planesDict for my structure of interest.

Thanks

Use of the from_data function

Hi,

(I tried to post this on the google group, but it didn't seem to show up?)

I am trying to access and use the from_data() function within dicompyler-core.

I am supplying my cumulative DVH data as a list and specifying the binsize = 1 (cGy) as
my_dvh =dvh.DVH.from_data(data=dvh_list,binsize=1)

This is producing a DVH object with the following properties:
DVH(cumulative, 100 bins: [0.0:100.0] Gy, volume: 5325 cm3, name: None, rx_dose: 0 Gy)

Running my_dvh.plot() produced the following:
dvh1

but the cumulative DVH plot should be (based on plotting from the raw data):
dvh2

So I was wondering if I am passing in the data incorrectly or misunderstanding the use of the from_data function. What format should the data that I pass to the function be in?
Also, is there a way to specify the prescription dose (rx_dose)?

Many thanks,

Matt

Structure info

Structure volume is calculated from self.differential.counts.sum(). I am also interested in other structure properties that are available within eclipse, specifically structure mean/SD of HU. Is it possible to extract these?
Cheers
Robin

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/38291925-structure-info?utm_campaign=plugin&utm_content=tracker%2F30933069&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F30933069&utm_medium=issues&utm_source=github).

Dose plane not found for %s. Using %s to calculate contour volume.'

I am trying to apply a translation shift to an rtss prior to extracting the dvh. I.e. translating a 3D contour by some vector [x,y,z] and then computing the dvh using the translated rtss and rtdose. to this end, I have slightly modified the get_dvh function by adding some shift vector to all coordinate triplets in s['planes'] and assigning keys corresponding to the new z values.

I believe that this function is able to handle that, and it seems to work without issue. However, I am getting a warning that "Dose plane not found for %s. Using %s to calculate contour volume.' in the logger, which I don't quite understand. Will this affect accurate calculation of the DVH or any relevant features or can I safely ignore this warning?

My function to translate the dvh is provided in a previous issue: #240

Thanks in advance.

Why is pixel_array not being imported?

When I use DicomParser on my RT-DOSE.dcm file, the resulting parsed dicom does not have a get_pixel_array attribute. This is making it so that when I pass this parsed rt_dose into _calculate_dvh, the (hasattr(dose, 'pixel_array')) boolean is evaluating to false and so the dvh being returned is calcdvh('Empty DVH', np.array([0])).

Strangely, when I try to calculate dvh's by using the rt_struct.dcm and rt-dose.dcm filepaths using the get_dvh function directly, the dvh is calculated without issue. Indeed, when I parse the very same rt-dose dicom file on a another python environment on a different machine (as far as I can tell the environments are identical but I am not 100% sure), it parses without issue and the pixel_array attribute is present and everything works, so it is not an issue with the file itself.

This normally would not be a problem as I can still somehow get the dvh, except for the fact that I am looking to calculate the dvh using a parsed dicom as an input instead of the path itself (the main reason for this is that I can apply spatial translations to the 'planes' in s in the get_dvh function). To this end, I have created a modified version of the get_dvh function that takes parsed dicoms as an input instead of filepaths. Unfortunately, due to the issue described in pargaraph 1, this is evaluating to an empty DVH, and so all metrics of interest are also evaluating to Zero.

Does anyone have any insight as to why this might be happening? This seems to be a fundamental issue of the parsing procedure, but if that were the case I cannot understand why it is environment specific.

My modified get_dvh function is below. I have really only modified it so that it takes the rtss and rtdose as inputs directly, instead of using filepaths. Additionally I have removed several optional arguments and just use _calculate_dvh's defaults instead of specifying them myself.

def get_dvh(rtss,
            rtdose, 
            roi):
    """
    This function extracts a DVH object after application of shift vector to
    rtss. If this is called to generate a nonadaptive dvh, then shift vector
    translation is applied to rtss prior to DVH calculation with dose cube.

    Parameters
    ----------
    rtss : dicomparser.Dicomparser (parsed dicomparser object)
        Parsed RT-STRUCT dicom.
    rtdose : dicomparser.Dicomparser (parsed dicomparser object)
        Parsed RT-DOSE dicom.
    roi : str
        The ROI number used to uniquely identify the structure in the structure
        set.


    Returns
    -------
    dvh.DVH
        An instance of dvh.DVH in cumulative dose. This can be converted to
        different formats using the attributes and properties of the DVH class.

    """
    #get structure and roi 
    structures=rtss.GetStructures()
    roi=get_structure_id(structures, roi_name)
    
    #structure description
    s = structures[roi]
    s['planes']=rtss.GetStructureCoordinates(roi)
    
    #compute thickness, this will be unaffected by any shift vector
    s['thickness'] = rtss.CalculatePlaneThickness(s['planes'])
    
    #compute dvh with calcvdvh
    calcdvh = _calculate_dvh(s, rtdose)

    
    #return dvh out
    return  dvh.DVH(counts=calcdvh.histogram,
                   bins=(np.arange(0, 2) if (calcdvh.histogram.size == 1) else
                         np.arange(0, calcdvh.histogram.size + 1) / 100),
                   dvh_type='differential',
                   dose_units='Gy',
                   notes=calcdvh.notes,
                   name=s['name']).cumulative

Note: I have tried using rtdose = dicomparser.DicomParser(dose, memmap_pixel_array=memmap_rtdose) but this returns an error that init_() got an unexpected keyword argument 'memmap_pixel_array'

Dose Grid Comparison

I have loaded data from a single plan which has been calculated with AAA and again with AXB. Since it is the same plan I expect the dose grid size to be identical. However I have found that the grid shape and scaling is different. How could this be? Is there a way to check the dicom files to see these factors?
Cheers
Robin

image

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/38300141-dose-grid-comparison?utm_campaign=plugin&utm_content=tracker%2F30933069&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F30933069&utm_medium=issues&utm_source=github).

DVH calculation problem for Eclipse 15.6 rtdose

Hey there!
There is a problem with dvh calculation for Eclipse 15.6 rtdose.

When I use get_dvh(... use_structure_extents=True ...)
I get this:
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/skimage/transform/_warps.py:116: RuntimeWarning: invalid value encountered in true_divide np.asarray(output_shape, dtype=float)) Traceback (most recent call last): File "/Users/sergei/GoogleDrive/PlanCompetition/Competition.py", line 532, in <module> result = calc_scoring(IDs[i], data_dict[struct][constr]) File "/Users/sergei/GoogleDrive/PlanCompetition/Competition.py", line 414, in calc_scoring calculate_full_volume=cal_full_vol) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dicompylercore/dvhcalc.py", line 75, in get_dvh callback) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dicompylercore/dvhcalc.py", line 183, in calculate_dvh dose, z, interpolation_resolution, dgindexextents) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dicompylercore/dvhcalc.py", line 458, in get_interpolated_dose preserve_range=True) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/skimage/transform/_warps.py", line 293, in rescale anti_aliasing_sigma=anti_aliasing_sigma) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/skimage/transform/_warps.py", line 169, in resize tform.estimate(src_corners, dst_corners) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/skimage/transform/_geometric.py", line 684, in estimate _, _, V = np.linalg.svd(A) File "<__array_function__ internals>", line 6, in svd File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numpy/linalg/linalg.py", line 1626, in svd u, s, vh = gufunc(a, signature=signature, extobj=extobj) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numpy/linalg/linalg.py", line 106, in _raise_linalgerror_svd_nonconvergence raise LinAlgError("SVD did not converge") numpy.linalg.LinAlgError: SVD did not converge

If I use get_dvh(... use_structure_extents=False ...)
I get the dvh, but it's really strange. The volume of the structure in % can be, for example, 1.2e+06...
image

Basic usage example

Following https://github.com/dicompyler/dicompyler-core#basic-usage
At

dvh = dvh.DVH.from_dicom_dvh(rtdose.ds, 5)

I get error

`/Users/..../python3.5/site-packages/dicompyler_core-0.5.2-py3.5.egg/dicompylercore/dvh.py in statistic(self, name)

484 # print(match.groups())
485 if not match or match.groups()[0] is not None:
--> 486 raise AttributeError("'DVH' has no attribute '%s'" % name)
487
488 # Process the regex match

AttributeError: 'DVH' has no attribute 'DVH'`

'FileDataset' object has no attribute 'DVHSequence'

I am trying to do the example in the docs to confirm that the package is working, I have an rt dose file and an rt struct file and my code is as follows


from dicompylercore import dicomparser, dvh, dvhcalc
from dicompylercore import dicomparser, dvh, dvhcalc
dp = dicomparser.DicomParser("RT-STRUCT.dcm")


# i.e. Get a dict of structure information
structures = dp.GetStructures()


rtdose=dicomparser.DicomParser("RT-DOSE.dcm")
#Stomach dvh
stomachdvh=dvh.DVH.from_dicom_dvh(rtdose.ds, 10)

But on the last line I get the following error

Traceback (most recent call last):

  File "<ipython-input-5-34a766ef1447>", line 17, in <module>
    stomachdvh=dvh.DVH.from_dicom_dvh(rtdose.ds, 10)

  File "D:\Users\me\anaconda3\envs\Python38\lib\site-packages\dicompylercore\dvh.py", line 67, in from_dicom_dvh
    for i, d in enumerate(dataset.DVHSequence):

  File "D:\Users\me\anaconda3\envs\Python38\lib\site-packages\pydicom\dataset.py", line 835, in __getattr__
    return object.__getattribute__(self, name)

AttributeError: 'FileDataset' object has no attribute 'DVHSequence

I installed the package from the git on python 3.8 using the command pip install git+git://github.com/dicompyler/dicompyler-core.git

Has anyone else had this problem or have a fix?

Decubitus orientation

I'm hoping for some guidance on DVHs in decubitus situation - I have CT and RTDOSE files with ImageOrientationPatient of [0, -1, 0, 1, 0, 0] and I get empty DVHs returned.

On calling get_dvh with debugging on, I see many lines like:
Dose plane not found for -100.00. Using -140.25 to calculate contour volume.

I see that there have been some issues and PR related to non-HFS orientation (e.g. #183), and in reviewing the code, I don't see any particular limitations for decubitus. I'm using the latest commit from master.

I haven't pursued it in further detail hoping to get some pointers in the right direction for further investigation. For example I haven't tried changing any of the optional parameters to get_dvh.

Thanks

Grid Frame Offset option b

I haven't observed an actual real-world problem with this, but noticed that GetDoseGrid, in using the GridFrameOffsetVector, does not check for the "option b" case defined in the DICOM standard, which could result in dose planes being offset from the correct location.

Unlikely, but possibly related to #160? Most likely the grid would be very far off if option b were used in the DICOM file.

Dose metric disagreement as compared with CERR

I have noticed that some metrics (e.g. 0.5cc) differ from the value calculated by CERR by as much to 10Gy for certain organs in a particular rtdose+rtss file pair when I use dvh calc. It is possible for binning to be the culprit for such a large deviation in these features? Is it possible to adjust the binning within the code to control this if so?

Extract 3D contour mask

Is there some way to extract the 3D mask of a struct? i.e. a binary rank 3 tensor which corresponds to struct outlines? I am looking to computer the overlap between GTVs in two different rt-struct files (I am doing this manually right now by extracting 2 masks and computing the overlap).

I had found the dicompylercore.dvhcalc.get_contour_mask function but it seems like this is only for single slices from the struct at a time?

Thanks in advance

D100 is 0Gy

When calculating D100, it calculates as 0.00Gy.
Technically, 0Gy will always satify D100 for cummulative DVH but I believe the highest dose to satify D100 should be the one stated. I believe the code isn't doing that.

Syntax question on getting isodose points

I'm not quite clear on how the GetIsodosePoints function translates into an actual isodose as we understand it as a function of prescription dose. I see that the default scalings is 0.5 relative to the DoseGridScaling, but what does this mean? If I set this to 0.5 then are we getting the 50% isodose as defined as 50% of the maximum? Or is this 50% of the DoseGridScaling in Gy? I'm a bit confused about how, for example, we would get say the isodose for 95% of the prescription dose.

Pandas multi index

Hi I have written a short script (pyEclipseDVH.py) to parse dvh.txt files exported from Eclipse into Pandas multiindex dataframe objects. I have found this to be a very efficient way to work with dvh data from multiple patients, as required for my study. I mention this since it would be a nice addition to dicompyler-core to provide a convenience function to return dvh data in the multiindex dataframe format for these kinds of study.
Example of its use here https://github.com/robmarkcole/Useful-python-for-medical-physics/blob/master/Experiments%20in%20ipython%20notebooks/pyEclipseDVH/MultiIndex%203-3-17/Demo%20pyEclipseDVH_v2%203-3-2017.ipynb
Cheers


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

dvhcalc.get_interpolated_dose raises IndexError if no dose for z

When there is no dose plane, GetDoseGrid returns an empty array. So an IndexError is raised in dvhcalc.get_interpolated_dose when there is no dose plane:

d = dose.GetDoseGrid(z)
extent_dose = d[extents[1]:extents[3],
extents[0]:extents[2]] if len(extents) else d

There is a check for an empty dose plane, but it occurs after get_interpolated_dose is called.

for z, plane in iteritems(planes):
# Get the dose plane for the current structure plane
if interpolation_resolution or use_structure_extents:
doseplane = get_interpolated_dose(
dose, z, interpolation_resolution, dgindexextents)
else:
doseplane = dose.GetDoseGrid(z)
if doseplane.size:

extract dvh table

Apologies if this is too simple a question but I wasn't able to figure it out based on the documentation. I have a calculated dvh from dvhcalc which I have calculated using ptv_dvh=dvhcalc.get_dvh(struct_path, dose_path, ptvid)

Now I want to extract the relative cumulative DVH as a table, but I can't quite figure out how this is supposed to be done. I have tried ptv_dvh.cumulative but as far as I can tell the object that this returns appears identical to the original ptv_dvh? I have also tried extracting ptv_dvh.bincenters and ptv_dvh.counts but as far as I can tell these are the dose bins for the differential DVH and the the absolute volume reciving the dose in each bin?

I have been able to extract other metrics with ease but have had trouble finding the command in the docs to just extract the relative cumulative DVH as a table of dose vs volume %

Thanks,

DVH scaling Volume, not Dose

(dicompyler-core=0.5.5)

In dvh.py the DVH object is created with the following return

        return cls(counts=data[1::2] * dvh.DVHDoseScaling,
                   bins=data[0::2].cumsum(),

With the counts being volume or "counts" data, then the DVHDoseScaling should rather be applied to the bins.

This is a sample output, the doses and volume are wrong by a factor of 10 (the DVHDoseScaling is 0.01), verified with TPS and other 3rd party software.
image

D100 still returns value of 0

Hi,

I see this has tried to be fixed in #74 but I am still getting a returned value of 0Gy.

D99.99 gives a value of 4.9Gy.
D99.999 gives 0Gy.
D100 gives 0Gy.

This is a brachy plan created in and exported from the Oncentra brachy TPS so now sure if that would make any difference?
For our brachy plans this is an important statistic to be able to extract, so wondered if there might be a workaround in the meantime?

I am using dicompyler-core version 0.5.4.

Thanks,

Matt

D100, D98, D2cc calculation on differential dvh

Hi Bastula,

Thank you for this wonderful package first!
I noticed that when calculating D100, D98, etc with differential DVH, the function does not return correct value from dvh.statistic.
Maybe a wraparound to calculate the statistics from DVH.cumulative could be added in the function?

Improve documentation

Documentation with more examples would be great.

Ideally docstrings need to be completed for the dvhcalc module as well.

IndexError on get_dvh if using interpolation outside of dose grid

Line 492 of dvhcalc.py raises an IndexError if get_interpolated_dose is called on a z with no dose. I injected a print statement to verify that it occurs on z values with no dose plane and that dose.GetDoseGrid(z) returns a 0 length array.

I edited get_interpolated_dose to just return dose.GetDoseGrid(z) in this situation, but that causes further issues.

d = dose.GetDoseGrid(z)
extent_dose = d[extents[1]:extents[3],
extents[0]:extents[2]] if len(extents) else d

from dicompylercore.dvhcalc import get_dvh
dvh = get_dvh(struct, dose, 6, interpolation_resolution=(0.5, 0.5))
Traceback (most recent call last):
  File "<input>", line 4, in <module>
  File "/Users/ninja/PycharmProjects/DVH-Analytics/venv/lib/python3.6/site-packages/dicompylercore/dvhcalc.py", line 90, in get_dvh
    callback)
  File "/Users/ninja/PycharmProjects/DVH-Analytics/venv/lib/python3.6/site-packages/dicompylercore/dvhcalc.py", line 210, in _calculate_dvh
    dose, z, interpolation_resolution, dgindexextents)
  File "/Users/ninja/PycharmProjects/DVH-Analytics/venv/lib/python3.6/site-packages/dicompylercore/dvhcalc.py", line 492, in get_interpolated_dose
    extents[0]:extents[2]] if len(extents) else d
IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed

Initial Update

Hi 👊

This is my first visit to this fine repo, but it seems you have been working hard to keep all dependencies updated so far.

Once you have closed this issue, I'll create seperate pull requests for every update as soon as I find one.

That's it for now!

Happy merging! 🤖

DVH dose calculation

I've been working on a DicomRT project and your dicom parser has been very helpful.

I also been looking at your old dicom rt project at https://github.com/bastula/dicompyler
I noticed that the dvh dose calculation in these two project are quite different. And the result from the old version match your dicompyler app. Also the structure volume from the two version are different and the new version match the app. That confuse me.

and the code from old version:

def get_dvh_min(dvh):
'''Return minimum dose to ROI derived from cDVH.'''

# ROI volume (always receives at least 0% dose)
v1 = dvh[0]

j = 1
jmax = len(dvh) - 1
mindose = 0
while j < jmax:
    if dvh[j] < v1:
        mindose = (2*j - 1)/2.0
        break
    else:
        j += 1

return mindose

seem it gives dose base on a index?..

Hope you can clear my confusion.
Thanks a lot.

p.s. I think we are using the same testing data set here.
qq20160919-0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.