Skip to content
Commits on Source (10)
patreon: bastula
*.pyc
.pc/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
\ No newline at end of file
syntax: glob
*.pyc
*~
.*.swp
.DS_Store
.AppleDouble
._*
dist
build
dicompyler
============
<img src='https://raw.githubusercontent.com/wiki/bastula/dicompyler/images/0.3/2dview_mac_thumb.png' align='right' height='240' width='287' alt="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](https://github.com/pydicom/pydicom), [wxPython](http://www.wxpython.org), [Pillow](http://python-pillow.org/), and [matplotlib](http://matplotlib.org) and runs on Windows, Mac OS X and Linux.
dicompyler is released under a BSD [license](dicompyler/license.txt).
Take a tour of dicompyler by checking out some [screenshots](https://github.com/bastula/dicompyler/wiki/Screenshots) or download a copy today.
![alt text](https://img.shields.io/pypi/v/dicompyler.svg "pypi version") ![alt text](https://img.shields.io/pypi/dm/dicompyler.svg "pypi version")
---
Downloads:
----------
Downloads are available through Google Drive:<br>
<br>
Version 0.4.2: [Windows](https://bit.ly/dicompylerwindows) | [Mac](https://bit.ly/dicompylermac) | [Source](https://pypi.python.org/packages/source/d/dicompyler/dicompyler-0.4.2.tar.gz#md5=adbfa47b07f983f17fdba26a1442fce0) | [Test Data](https://bit.ly/dicompylertestdata) - Released July 15th, 2014 - [Release Notes](https://github.com/bastula/dicompyler/wiki/ReleaseNotes)
Features:
---------
* Import CT/MR/PET Images, DICOM RT structure set, RT dose and RT plan files
* Extensible plugin system with included plugins:
* 2D image viewer with dose and structure overlay
* Dose volume histogram viewer with the ability to analyze DVH parameters
* DICOM data tree viewer
* Patient anonymizer
* 3rd-party plugins can be found at [https://github.com/dicompyler/dicompyler-plugins](https://github.com/dicompyler/dicompyler-plugins)
* Custom plugins can be written by following the [Plugin development guide](https://github.com/bastula/dicompyler/wiki/PluginDevelopmentGuide)
For upcoming features, see the [project roadmap](https://github.com/bastula/dicompyler/wiki/Roadmap).
System Requirements:
--------------------
* Windows XP/Vista/7/8/10
* Mac OS X 10.5 - 10.13 (Intel) - Must bypass [Gatekeeper](https://support.apple.com/en-us/HT202491)
* Linux - via a package from [PyPI](https://pypi.python.org/pypi/dicompyler) or a [Debian package](https://packages.debian.org/sid/dicompyler) (courtesy of debian-med)
If you are interested in building from source, please check out the [build instructions](https://github.com/bastula/dicompyler/wiki/BuildRequirements).
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](https://github.com/bastula/dicompyler/wiki/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](https://github.com/bastula/dicompyler/wiki), dicompyler has a [discussion forum](https://groups.google.com/group/dicompyler) 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](http://dx.doi.org/10.1118/1.3468652)
dicompyler (0.4.2.0+git20200106.2643e0e-1) UNRELEASED; urgency=medium
* Fetch Git head from upstream to get changes for Python3 soon
* debhelper-compat 12 (routine-update)
* Standards-Version: 4.5.0
* Build using Python3
Closes: #936395, #943017
* (Build-)Depends: python3-wxgtk4.0, python3-pydicom
TODO: dicompylercore
https://github.com/dicompyler/dicompyler-core/
-- Andreas Tille <tille@debian.org> Wed, 22 Jan 2020 14:14:10 +0100
dicompyler (0.4.2.0-2) unstable; urgency=medium
* Point Vcs fields to salsa.debian.org
......
......@@ -4,11 +4,13 @@ Uploaders: Andreas Tille <tille@debian.org>,
Vojtěch Kulvait <kulvait@gmail.com>
Section: science
Priority: optional
Build-Depends: debhelper (>= 12~),
python,
Build-Depends: debhelper-compat (= 12),
python3,
dh-python,
python-setuptools
Standards-Version: 4.3.0
python3-setuptools,
python3-wxgtk4.0 <!nocheck>,
python3-pydicom <!nocheck>,
Standards-Version: 4.5.0
Vcs-Browser: https://salsa.debian.org/med-team/dicompyler
Vcs-Git: https://salsa.debian.org/med-team/dicompyler.git
Homepage: http://www.dicompyler.com/
......@@ -16,13 +18,13 @@ Homepage: http://www.dicompyler.com/
Package: dicompyler
Architecture: all
Depends: ${misc:Depends},
${python:Depends},
python-wxgtk3.0,
python-pil (>=3.0.0),
python-numpy,
python-matplotlib,
python-dicom,
python-tornado
${python3:Depends},
python3-wxgtk4.0,
python3-pil (>=3.0.0),
python3-numpy,
python3-matplotlib,
python3-pydicom,
python3-tornado
Description: radiation therapy research platform
Dicompyler is an extensible, fully open source radiation therapy
research platform based on the DICOM standard. It also functions as a
......
......@@ -3,60 +3,8 @@ Last-Update: Mon Sep 18 10:00:00 CEST 2017
Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=854837
Description: wx.lib.pubsub fix according to https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
Index: dicompyler/dicompyler/baseplugins/2dview.py
===================================================================
--- dicompyler.orig/dicompyler/baseplugins/2dview.py
+++ dicompyler/dicompyler/baseplugins/2dview.py
@@ -10,9 +10,8 @@
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
-import wx.lib.pubsub.setuparg1
-import wx.lib.pubsub.core
-pub = wx.lib.pubsub.core.Publisher()
+from wx.lib.pubsub import setuparg1 #see https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
+from wx.lib.pubsub import pub
from matplotlib import _cntr as cntr
from matplotlib import __version__ as mplversion
import numpy as np
@@ -194,18 +193,18 @@ class plugin2DView(wx.Panel):
self.Unbind(wx.EVT_RIGHT_DOWN)
self.Unbind(wx.EVT_RIGHT_UP)
self.Unbind(wx.EVT_MOTION)
- pub.unsubscribe(self.OnKeyDown)
- pub.unsubscribe(self.OnMouseWheel)
- pub.unsubscribe(self.OnRefresh)
+ pub.unsubscribe(self.OnKeyDown, 'main.key_down')
+ pub.unsubscribe(self.OnMouseWheel, 'main.mousewheel')
+ pub.unsubscribe(self.OnRefresh, '2dview.refresh')
def OnDestroy(self, evt):
"""Unbind to all events before the plugin is destroyed."""
- pub.unsubscribe(self.OnUpdatePatient)
- pub.unsubscribe(self.OnStructureCheck)
- pub.unsubscribe(self.OnIsodoseCheck)
- pub.unsubscribe(self.OnDrawingPrefsChange)
- pub.unsubscribe(self.OnPluginLoaded)
+ pub.unsubscribe(self.OnUpdatePatient, 'patient.updated.parsed_data')
+ pub.unsubscribe(self.OnStructureCheck, 'structures.checked')
+ pub.unsubscribe(self.OnIsodoseCheck, 'isodoses.checked')
+ pub.unsubscribe(self.OnDrawingPrefsChange, '2dview.drawingprefs')
+ pub.unsubscribe(self.OnPluginLoaded, 'plugin.loaded.2dview')
self.OnUnfocus()
def OnStructureCheck(self, msg):
@@ -725,4 +724,4 @@ class plugin2DView(wx.Panel):
menu.Append(id, "No tools found")
menu.Enable(id, False)
self.PopupMenu(menu)
- menu.Destroy()
\ No newline at end of file
+ menu.Destroy()
Index: dicompyler/dicompyler/baseplugins/anonymize.py
===================================================================
--- dicompyler.orig/dicompyler/baseplugins/anonymize.py
+++ dicompyler/dicompyler/baseplugins/anonymize.py
--- a/dicompyler/baseplugins/anonymize.py
+++ b/dicompyler/baseplugins/anonymize.py
@@ -10,9 +10,8 @@
import wx
......@@ -69,169 +17,9 @@ Index: dicompyler/dicompyler/baseplugins/anonymize.py
import os, threading
from dicompyler import guiutil, util
@@ -333,4 +332,9 @@ class AnonymizeDialog(wx.Dialog):
else:
self.privatetags = False
- self.EndModal(wx.ID_OK)
\ No newline at end of file
+ self.EndModal(wx.ID_OK)
+
+ def OnDestroy(self, evt):
+ """Unbind to all events before the plugin is destroyed."""
+ pub.unsubscribe(self.OnImportPrefsChange, 'general.dicom.import_location')
+ pub.unsubscribe(self.OnUpdatePatient, 'patient.updated.raw_data')
Index: dicompyler/dicompyler/baseplugins/dvh.py
===================================================================
--- dicompyler.orig/dicompyler/baseplugins/dvh.py
+++ dicompyler/dicompyler/baseplugins/dvh.py
@@ -12,9 +12,8 @@
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
-import wx.lib.pubsub.setuparg1
-import wx.lib.pubsub.core
-pub = wx.lib.pubsub.core.Publisher()
+from wx.lib.pubsub import setuparg1 #see https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
+from wx.lib.pubsub import pub
from dicompyler import guiutil, util
from dicompyler import dvhdata, guidvh
from dicompyler import wxmpl
@@ -134,9 +133,9 @@ class pluginDVH(wx.Panel):
def OnDestroy(self, evt):
"""Unbind to all events before the plugin is destroyed."""
- pub.unsubscribe(self.OnUpdatePatient)
- pub.unsubscribe(self.OnStructureCheck)
- pub.unsubscribe(self.OnStructureSelect)
+ pub.unsubscribe(self.OnUpdatePatient, 'patient.updated.parsed_data')
+ pub.unsubscribe(self.OnStructureCheck, 'structures.checked')
+ pub.unsubscribe(self.OnStructureSelect, 'structure.selected')
def OnStructureCheck(self, msg):
"""When a structure changes, update the interface and plot."""
Index: dicompyler/dicompyler/baseplugins/quickopen.py
===================================================================
--- dicompyler.orig/dicompyler/baseplugins/quickopen.py
+++ dicompyler/dicompyler/baseplugins/quickopen.py
@@ -11,9 +11,8 @@
import logging
logger = logging.getLogger('dicompyler.quickimport')
import wx
-import wx.lib.pubsub.setuparg1
-import wx.lib.pubsub.core
-pub = wx.lib.pubsub.core.Publisher()
+from wx.lib.pubsub import setuparg1 #see https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
+from wx.lib.pubsub import pub
from dicompyler import dicomparser, util
import dicom
Index: dicompyler/dicompyler/baseplugins/treeview.py
===================================================================
--- dicompyler.orig/dicompyler/baseplugins/treeview.py
+++ dicompyler/dicompyler/baseplugins/treeview.py
@@ -13,9 +13,8 @@ logger = logging.getLogger('dicompyler.t
import threading, Queue
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
-import wx.lib.pubsub.setuparg1
-import wx.lib.pubsub.core
-pub = wx.lib.pubsub.core.Publisher()
+from wx.lib.pubsub import setuparg1 #see https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
+from wx.lib.pubsub import pub
from wx.gizmos import TreeListCtrl as tlc
from dicompyler import guiutil, util
import dicom
@@ -99,7 +98,7 @@ class pluginTreeView(wx.Panel):
def OnDestroy(self, evt):
"""Unbind to all events before the plugin is destroyed."""
- pub.unsubscribe(self.OnUpdatePatient)
+ pub.unsubscribe(self.OnUpdatePatient, 'patient.updated.raw_data')
def OnLoadTree(self, event):
"""Update and load the DICOM tree."""
Index: dicompyler/dicompyler/dicomgui.py
===================================================================
--- dicompyler.orig/dicompyler/dicomgui.py
+++ dicompyler/dicompyler/dicomgui.py
@@ -15,9 +15,8 @@ logger = logging.getLogger('dicompyler.d
import hashlib, os, threading
import wx
from wx.xrc import *
-import wx.lib.pubsub.setuparg1
-import wx.lib.pubsub.core
-pub = wx.lib.pubsub.core.Publisher()
+from wx.lib.pubsub import setuparg1 #see https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
+from wx.lib.pubsub import pub
import numpy as np
from dicompyler import dicomparser, dvhdoses, guiutil, util
Index: dicompyler/dicompyler/guiutil.py
===================================================================
--- dicompyler.orig/dicompyler/guiutil.py
+++ dicompyler/dicompyler/guiutil.py
@@ -10,9 +10,8 @@
import util
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
-import wx.lib.pubsub.setuparg1
-import wx.lib.pubsub.core
-pub = wx.lib.pubsub.core.Publisher()
+from wx.lib.pubsub import setuparg1 #see https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
+from wx.lib.pubsub import pub
def IsMSWindows():
"""Are we running on Windows?
Index: dicompyler/dicompyler/main.py
===================================================================
--- dicompyler.orig/dicompyler/main.py
+++ dicompyler/dicompyler/main.py
@@ -20,9 +20,8 @@ from wx.xrc import *
import wx.lib.dialogs, webbrowser
# Uncomment line to setup pubsub for frozen targets on wxPython 2.8.11 and above
# from wx.lib.pubsub import setupv1
-import wx.lib.pubsub.setuparg1
-import wx.lib.pubsub.core
-pub = wx.lib.pubsub.core.Publisher()
+from wx.lib.pubsub import setuparg1 #see https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
+from wx.lib.pubsub import pub
from dicompyler import guiutil, util
from dicompyler import dicomgui, dvhdata, dvhdoses, dvhcalc
from dicompyler.dicomparser import DicomParser as dp
Index: dicompyler/dicompyler/plugin.py
===================================================================
--- dicompyler.orig/dicompyler/plugin.py
+++ dicompyler/dicompyler/plugin.py
@@ -12,9 +12,8 @@ logger = logging.getLogger('dicompyler.p
import imp, os
import wx
from wx.xrc import *
-import wx.lib.pubsub.setuparg1
-import wx.lib.pubsub.core
-pub = wx.lib.pubsub.core.Publisher()
+from wx.lib.pubsub import setuparg1 #see https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
+from wx.lib.pubsub import pub
from dicompyler import guiutil, util
def import_plugins(userpath=None):
Index: dicompyler/dicompyler/preferences.py
===================================================================
--- dicompyler.orig/dicompyler/preferences.py
+++ dicompyler/dicompyler/preferences.py
@@ -10,9 +10,8 @@
import os
import wx
from wx.xrc import *
-import wx.lib.pubsub.setuparg1
-import wx.lib.pubsub.core
-pub = wx.lib.pubsub.core.Publisher()
+from wx.lib.pubsub import setuparg1 #see https://wxpython.org/Phoenix/docs/html/wx.lib.pubsub.setuparg1.html
+from wx.lib.pubsub import pub
from dicompyler import guiutil, util
try:
@@ -376,9 +375,8 @@ def main():
--- a/dicompyler/preferences.py
+++ b/dicompyler/preferences.py
@@ -377,9 +377,8 @@ def main():
import tempfile, os
import wx
......@@ -243,3 +31,12 @@ Index: dicompyler/dicompyler/preferences.py
app = wx.App(False)
--- a/dicompyler/baseplugins/2dview.py
+++ b/dicompyler/baseplugins/2dview.py
@@ -712,4 +712,4 @@ class plugin2DView(wx.Panel):
menu.Append(id, "No tools found")
menu.Enable(id, False)
self.PopupMenu(menu)
- menu.Destroy()
\ No newline at end of file
+ menu.Destroy()
......@@ -3,10 +3,8 @@ Last-Update: Mon Sep 18 23:15:00 CEST 2017
Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=854837
Description: wxmpl file was outdated, so it was updated to github.com/NOAA-ORR-ERD/wxmpl version "2.1.0dev1" including patch matplotlib.1069221.n5.nabble.com/wxMPL-patch-td34955.html
Index: dicompyler/dicompyler/wxmpl.py
===================================================================
--- dicompyler.orig/dicompyler/wxmpl.py
+++ dicompyler/dicompyler/wxmpl.py
--- a/dicompyler/wxmpl.py
+++ b/dicompyler/wxmpl.py
@@ -1,6 +1,5 @@
# Purpose: painless matplotlib embedding for wxPython
# Author: Ken McIvor <mcivor@iit.edu>
......@@ -25,10 +23,9 @@ Index: dicompyler/dicompyler/wxmpl.py
import matplotlib
matplotlib.use('WXAgg')
-import numpy as np
-from matplotlib.axes import _process_plot_var_args
-from matplotlib.backends.backend_agg import RendererAgg
+import numpy as NumPy
+from matplotlib.axes._base import _process_plot_var_args
from matplotlib.axes._base import _process_plot_var_args
-from matplotlib.backends.backend_agg import RendererAgg
+from matplotlib.backend_bases import FigureCanvasBase
+from matplotlib.backends.backend_agg import FigureCanvasAgg, RendererAgg
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
......@@ -42,17 +39,7 @@ Index: dicompyler/dicompyler/wxmpl.py
__all__ = ['PlotPanel', 'PlotFrame', 'PlotApp', 'StripCharter', 'Channel',
'FigurePrinter', 'PointEvent', 'EVT_POINT', 'SelectionEvent',
@@ -1122,7 +1124,8 @@ class PlotPanel(FigureCanvasWxAgg):
# find the toplevel parent window and register an activation event
# handler that is keyed to the id of this PlotPanel
topwin = toplevel_parent_of_window(self)
- topwin.Connect(-1, self.GetId(), wx.wxEVT_ACTIVATE, self.OnActivate)
+ #Patch of wxMPL to make it work with wxPython 2.9 due to http://matplotlib.1069221.n5.nabble.com/wxMPL-patch-td34955.html
+ topwin.Connect(self.GetId(), wx.ID_ANY, wx.wxEVT_ACTIVATE, self.OnActivate)
wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
wx.EVT_WINDOW_DESTROY(self, self.OnDestroy)
@@ -1131,8 +1134,6 @@ class PlotPanel(FigureCanvasWxAgg):
@@ -1131,8 +1133,6 @@ class PlotPanel(FigureCanvasWxAgg):
"""
Handles the wxPython window activation event.
"""
......@@ -61,7 +48,7 @@ Index: dicompyler/dicompyler/wxmpl.py
if not evt.GetActive():
self.cursor.setNormal()
self.location.clear()
@@ -1422,7 +1423,7 @@ class PlotFrame(wx.Frame):
@@ -1421,7 +1421,7 @@ class PlotFrame(wx.Frame):
fileName = wx.FileSelector('Save Plot', default_extension='png',
wildcard=('Portable Network Graphics (*.png)|*.png|'
+ 'Encapsulated Postscript (*.eps)|*.eps|All files (*.*)|*.*'),
......@@ -70,7 +57,7 @@ Index: dicompyler/dicompyler/wxmpl.py
if not fileName:
return
@@ -1649,7 +1650,7 @@ class VectorBuffer:
@@ -1648,7 +1648,7 @@ class VectorBuffer:
accomodate new entries.
"""
def __init__(self):
......@@ -79,7 +66,7 @@ Index: dicompyler/dicompyler/wxmpl.py
self.nextRow = 0
def clear(self):
@@ -1663,7 +1664,7 @@ class VectorBuffer:
@@ -1662,7 +1662,7 @@ class VectorBuffer:
"""
Zero and reset this buffer, releasing the underlying array.
"""
......@@ -88,7 +75,7 @@ Index: dicompyler/dicompyler/wxmpl.py
self.nextRow = 0
def append(self, point):
@@ -1675,11 +1676,11 @@ class VectorBuffer:
@@ -1674,11 +1674,11 @@ class VectorBuffer:
resize = False
if nextRow == data.shape[0]:
......@@ -102,7 +89,7 @@ Index: dicompyler/dicompyler/wxmpl.py
self.data[0:data.shape[0]] = data
self.data[nextRow] = point
@@ -1701,7 +1702,7 @@ class MatrixBuffer:
@@ -1700,7 +1700,7 @@ class MatrixBuffer:
accomodate new rows of entries.
"""
def __init__(self):
......@@ -111,7 +98,7 @@ Index: dicompyler/dicompyler/wxmpl.py
self.nextRow = 0
def clear(self):
@@ -1715,14 +1716,14 @@ class MatrixBuffer:
@@ -1714,14 +1714,14 @@ class MatrixBuffer:
"""
Zero and reset this buffer, releasing the underlying array.
"""
......@@ -128,7 +115,7 @@ Index: dicompyler/dicompyler/wxmpl.py
nextRow = self.nextRow
data = self.data
nPts = row.shape[0]
@@ -1733,7 +1734,7 @@ class MatrixBuffer:
@@ -1732,7 +1732,7 @@ class MatrixBuffer:
resize = True
if nextRow == data.shape[0]:
nC = data.shape[1]
......@@ -137,7 +124,7 @@ Index: dicompyler/dicompyler/wxmpl.py
if nC < nPts:
nC = nPts
elif data.shape[1] < nPts:
@@ -1743,7 +1744,7 @@ class MatrixBuffer:
@@ -1742,7 +1742,7 @@ class MatrixBuffer:
resize = False
if resize:
......@@ -146,7 +133,7 @@ Index: dicompyler/dicompyler/wxmpl.py
rowEnd, colEnd = data.shape
self.data[0:rowEnd, 0:colEnd] = data
@@ -1935,7 +1936,7 @@ class StripCharter:
@@ -1934,7 +1934,7 @@ class StripCharter:
xys = axes._get_verts_in_data_coords(
line.get_transform(), zip(x, y))
else:
......
Author: Andreas Tille <tille@debian.org>
Last-Update: Wed, 10 Sep 2014 23:21:42 +0200
Description: python-imaging is providing only pillow egg-info
Since pil egg-info is missing this patch is needed. See also
https://lists.debian.org/debian-python/2014/09/msg00070.html
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@ import sys
requires = [
'matplotlib>=0.99, <=1.1.0',
'numpy>=1.2.1',
- 'pil>=1.1.7',
+ 'pillow>=2.5.1',
'pydicom>=0.9.5, <=0.9.9']
if sys.version_info[0] == 2 and sys.version_info[1] < 6:
Author: Andreas Tille <tille@debian.org>
Last-Update: Wed, 10 Sep 2014 23:21:42 +0200
Description: Hopefully dicompyler can also cope with the current
version of python-dicom. This needs to be clarified with upstream
but for the moment we need the patch to let it start at all.
--- dicompyler-0.4.2.orig/setup.py
+++ dicompyler-0.4.2/setup.py
@@ -17,7 +17,7 @@
'matplotlib>=0.99, <=1.1.0',
'numpy>=1.2.1',
'pil>=1.1.7',
- 'pydicom>=0.9.5, <0.9.7']
+ 'pydicom>=0.9.5, <=0.9.9']
if sys.version_info[0] == 2 and sys.version_info[1] < 6:
requires.append('simplejson')
@@ -91,4 +91,4 @@
- PIL 1.1.7 or any version of Pillow
- pydicom 0.9.5 or 0.9.6
- simplejson (only for Python 2.5, Python 2.6+ includes JSON support)""",
-)
\ No newline at end of file
+)
do_not_download_distribute.patch
wxpy30.patch
wxpy30-more.patch
enable_pydicom_0.9.8.patch
allow_pillow_instead_of_pil.patch
enable_later_versions_of_matplotlib.patch
fix-xrc-errors.patch
fix_DicomImporterDialog.patch
# enable_later_versions_of_matplotlib.patch
# fix-xrc-errors.patch
# fix_DicomImporterDialog.patch
092017A_fixpubsub.patch
092017B_fiximportasterixs.patch
092017C_fixwx30.patch
# 092017B_fiximportasterixs.patch
# 092017C_fixwx30.patch
092017E_newwxmpl.patch
092017F_pillowtostringtobytes.patch
092017G_fixdicomparser.patch
# 092017F_pillowtostringtobytes.patch
# 092017G_fixdicomparser.patch
......@@ -10,131 +10,23 @@ Description: Further patches not applied by wx-migration-tool from
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
-from wx.lib.pubsub import Publisher as pub
-from wx.lib.pubsub import pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
import os, threading
from dicompyler import guiutil, util
--- a/dicompyler/baseplugins/treeview.py
+++ b/dicompyler/baseplugins/treeview.py
@@ -13,7 +13,9 @@ logger = logging.getLogger('dicompyler.t
import threading, Queue
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
-from wx.lib.pubsub import Publisher as pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
from wx.gizmos import TreeListCtrl as tlc
from dicompyler import guiutil, util
import dicom
--- a/dicompyler/plugin.py
+++ b/dicompyler/plugin.py
@@ -12,7 +12,9 @@ logger = logging.getLogger('dicompyler.p
import imp, os
import wx
from wx.xrc import *
-from wx.lib.pubsub import Publisher as pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
from dicompyler import guiutil, util
@@ -329,4 +331,4 @@ class AnonymizeDialog(wx.Dialog):
else:
self.privatetags = False
def import_plugins(userpath=None):
--- a/dicompyler/baseplugins/quickopen.py
+++ b/dicompyler/baseplugins/quickopen.py
@@ -11,7 +11,9 @@
import logging
logger = logging.getLogger('dicompyler.quickimport')
import wx
-from wx.lib.pubsub import Publisher as pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
from dicompyler import dicomparser, util
import dicom
--- a/dicompyler/baseplugins/2dview.py
+++ b/dicompyler/baseplugins/2dview.py
@@ -10,7 +10,9 @@
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
-from wx.lib.pubsub import Publisher as pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
from matplotlib import _cntr as cntr
from matplotlib import __version__ as mplversion
import numpy as np
--- a/dicompyler/baseplugins/dvh.py
+++ b/dicompyler/baseplugins/dvh.py
@@ -12,7 +12,9 @@
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
-from wx.lib.pubsub import Publisher as pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
from dicompyler import guiutil, util
from dicompyler import dvhdata, guidvh
from dicompyler import wxmpl
--- a/dicompyler/dicomgui.py
+++ b/dicompyler/dicomgui.py
@@ -15,7 +15,9 @@ logger = logging.getLogger('dicompyler.d
import hashlib, os, threading
import wx
from wx.xrc import *
-from wx.lib.pubsub import Publisher as pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
import numpy as np
from dicompyler import dicomparser, dvhdoses, guiutil, util
--- a/dicompyler/guiutil.py
+++ b/dicompyler/guiutil.py
@@ -10,7 +10,9 @@
import util
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
-from wx.lib.pubsub import Publisher as pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
def IsMSWindows():
"""Are we running on Windows?
--- a/dicompyler/main.py
+++ b/dicompyler/main.py
@@ -20,7 +20,9 @@ from wx.xrc import *
import wx.lib.dialogs, webbrowser
# Uncomment line to setup pubsub for frozen targets on wxPython 2.8.11 and above
# from wx.lib.pubsub import setupv1
-from wx.lib.pubsub import Publisher as pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
from dicompyler import guiutil, util
from dicompyler import dicomgui, dvhdata, dvhdoses, dvhcalc
from dicompyler.dicomparser import DicomParser as dp
- self.EndModal(wx.ID_OK)
\ No newline at end of file
+ self.EndModal(wx.ID_OK)
--- a/dicompyler/preferences.py
+++ b/dicompyler/preferences.py
@@ -10,7 +10,9 @@
import os
import wx
from wx.xrc import *
-from wx.lib.pubsub import Publisher as pub
+import wx.lib.pubsub.setuparg1
+import wx.lib.pubsub.core
+pub = wx.lib.pubsub.core.Publisher()
from dicompyler import guiutil, util
try:
@@ -374,7 +374,9 @@ def main():
@@ -377,7 +377,9 @@ def main():
import tempfile, os
import wx
......
......@@ -4,20 +4,9 @@ Bug-Debian: http://bugs.debian.org/759056
Description: Apply wx-migration-tool which was developed by Olly Betts
http://anonscm.debian.org/cgit/collab-maint/wx-migration-tools.git
--- a/dicompyler/main.py
+++ b/dicompyler/main.py
@@ -925,7 +925,7 @@ class MainFrame(wx.Frame):
class dicompyler(wx.App):
def OnInit(self):
- wx.InitAllImageHandlers()
+ # no-op in wxPython2.8 and later: wx.InitAllImageHandlers()
wx.GetApp().SetAppName("dicompyler")
# Load the XRC file for our gui resources
--- a/dicompyler/wxmpl.py
+++ b/dicompyler/wxmpl.py
@@ -1422,7 +1422,7 @@ class PlotFrame(wx.Frame):
@@ -1421,7 +1421,7 @@ class PlotFrame(wx.Frame):
fileName = wx.FileSelector('Save Plot', default_extension='png',
wildcard=('Portable Network Graphics (*.png)|*.png|'
+ 'Encapsulated Postscript (*.eps)|*.eps|All files (*.*)|*.*'),
......
......@@ -9,7 +9,7 @@ export PYBUILD_INSTALL_ARGS=--install-lib=/usr/share/$(DEB_SOURCE)/
export PYBUILD_AFTER_INSTALL=mv {destdir}/usr/bin/$(DEB_SOURCE) {destdir}/usr/share/$(DEB_SOURCE)/run
%:
dh $@ --with python2 --buildsystem=pybuild
dh $@ --with python3 --buildsystem=pybuild
override_dh_fixperms:
dh_fixperms
......
version=3
https://github.com/bastula/dicompyler/releases .*/archive/release-(\d[\d.-]+)\.(?:tar(?:\.gz|\.bz2)?|tgz)
version=4
opts="mode=git,pretty=0.4.2.0+git%cd.%h" \
https://github.com/bastula/dicompyler.git HEAD
# https://github.com/bastula/dicompyler/releases .*/archive/release-(\d[\d.-]+)\.(?:tar(?:\.gz|\.bz2)?|tgz)
......@@ -2,12 +2,18 @@
# -*- coding: utf-8 -*-
# __init__.py
"""Package initialization for dicompyler."""
# Copyright (c) 2009-2011 Aditya Panchal
# Copyright (c) 2009-2017 Aditya Panchal
# This file is part of dicompyler, relased under a BSD license.
# See the file license.txt included with this distribution, also
# available at http://code.google.com/p/dicompyler/
# available at https://github.com/bastula/dicompyler/
from main import start, __version__
__author__ = 'Aditya Panchal'
__email__ = 'apanchal@bastula.org'
__version__ = '0.5.0'
__version_info__ = (0, 5, 0)
from dicompyler.main import start
if __name__ == '__main__':
import dicompyler.main
......
#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-
# -*- coding: utf-8 -*-
# 2dview.py
"""dicompyler plugin that displays images, structures and dose in 2D planes."""
# Copyright (c) 2009-2012 Aditya Panchal
# Copyright (c) 2009-2017 Aditya Panchal
# This file is part of dicompyler, released under a BSD license.
# See the file license.txt included with this distribution, also
# available at http://code.google.com/p/dicompyler/
# available at https://github.com/bastula/dicompyler/
#
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
from wx.lib.pubsub import Publisher as pub
from wx.lib.pubsub import pub
from matplotlib import _cntr as cntr
from matplotlib import __version__ as mplversion
import numpy as np
......@@ -23,7 +23,7 @@ def pluginProperties():
props['name'] = '2D View'
props['description'] = "Display image, structure and dose data in 2D"
props['author'] = 'Aditya Panchal'
props['version'] = "0.4.2"
props['version'] = "0.5.0"
props['plugin_type'] = 'main'
props['plugin_version'] = 1
props['min_dicom'] = ['images']
......@@ -46,9 +46,7 @@ class plugin2DView(wx.Panel):
"""Plugin to display DICOM image, RT Structure, RT Dose in 2D."""
def __init__(self):
pre = wx.PrePanel()
# the Create step is done by XRC.
self.PostCreate(pre)
wx.Panel.__init__(self)
def Init(self, res):
"""Method called after the panel has been initialized."""
......@@ -124,7 +122,7 @@ class plugin2DView(wx.Panel):
pub.subscribe(self.OnRefresh, '2dview.refresh')
pub.subscribe(self.OnDrawingPrefsChange, '2dview.drawingprefs')
pub.subscribe(self.OnPluginLoaded, 'plugin.loaded.2dview')
pub.sendMessage('preferences.requested.values', '2dview.drawingprefs')
pub.sendMessage('preferences.requested.values', msg='2dview.drawingprefs')
def OnUpdatePatient(self, msg):
"""Update and load the patient data."""
......@@ -132,21 +130,21 @@ class plugin2DView(wx.Panel):
self.z = 0
self.structurepixlut = ([], [])
self.dosepixlut = ([], [])
if msg.data.has_key('images'):
self.images = msg.data['images']
if 'images' in msg:
self.images = msg['images']
self.imagenum = 1
# If more than one image, set first image to middle of the series
if (len(self.images) > 1):
self.imagenum = len(self.images)/2
self.imagenum = int(len(self.images)/2)
image = self.images[self.imagenum-1]
self.structurepixlut = image.GetPatientToPixelLUT()
# Determine the default window and level of the series
self.window, self.level = image.GetDefaultImageWindowLevel()
# Dose display depends on whether we have images loaded or not
self.isodoses = {}
if (msg.data.has_key('dose') and \
("PixelData" in msg.data['dose'].ds)):
self.dose = msg.data['dose']
if ('dose' in msg and \
("PixelData" in msg['dose'].ds)):
self.dose = msg['dose']
self.dosedata = self.dose.GetDoseData()
# First get the dose grid LUT
doselut = self.dose.GetPatientToPixelLUT()
......@@ -154,8 +152,8 @@ class plugin2DView(wx.Panel):
self.dosepixlut = self.GetDoseGridPixelData(self.structurepixlut, doselut)
else:
self.dose = []
if msg.data.has_key('plan'):
self.rxdose = msg.data['plan']['rxdose']
if 'plan' in msg:
self.rxdose = msg['plan']['rxdose']
else:
self.rxdose = 0
else:
......@@ -164,6 +162,7 @@ class plugin2DView(wx.Panel):
self.SetBackgroundColour(wx.Colour(0, 0, 0))
# Set the focus to this panel so we can capture key events
self.SetFocus()
self.OnFocus() #Workaround on Windows. ST
self.Refresh()
def OnFocus(self):
......@@ -178,8 +177,9 @@ class plugin2DView(wx.Panel):
self.Bind(wx.EVT_RIGHT_UP, self.OnMouseUp)
self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
pub.subscribe(self.OnKeyDown, 'main.key_down')
pub.subscribe(self.OnMouseWheel, 'main.mousewheel')
if guiutil.IsMSWindows():
pub.subscribe(self.OnKeyDown, 'main.key_down')
pub.subscribe(self.OnMouseWheel, 'main.mousewheel')
def OnUnfocus(self):
"""Unbind to certain events when the plugin is unfocused."""
......@@ -192,52 +192,53 @@ class plugin2DView(wx.Panel):
self.Unbind(wx.EVT_RIGHT_DOWN)
self.Unbind(wx.EVT_RIGHT_UP)
self.Unbind(wx.EVT_MOTION)
pub.unsubscribe(self.OnKeyDown)
pub.unsubscribe(self.OnMouseWheel)
pub.unsubscribe(self.OnRefresh)
if guiutil.IsMSWindows():
pub.unsubscribe(self.OnKeyDown, 'main.key_down')
pub.unsubscribe(self.OnMouseWheel, 'main.mousewheel')
pub.unsubscribe(self.OnRefresh, '2dview.refresh')
def OnDestroy(self, evt):
"""Unbind to all events before the plugin is destroyed."""
pub.unsubscribe(self.OnUpdatePatient)
pub.unsubscribe(self.OnStructureCheck)
pub.unsubscribe(self.OnIsodoseCheck)
pub.unsubscribe(self.OnDrawingPrefsChange)
pub.unsubscribe(self.OnPluginLoaded)
self.OnUnfocus()
pub.unsubscribe(self.OnUpdatePatient, 'patient.updated.parsed_data')
pub.unsubscribe(self.OnStructureCheck, 'structures.checked')
pub.unsubscribe(self.OnIsodoseCheck, 'isodoses.checked')
pub.unsubscribe(self.OnDrawingPrefsChange, '2dview.drawingprefs')
pub.unsubscribe(self.OnPluginLoaded, 'plugin.loaded.2dview')
# self.OnUnfocus()
def OnStructureCheck(self, msg):
"""When the structure list changes, update the panel."""
self.structures = msg.data
self.structures = msg
self.SetFocus()
self.Refresh()
def OnIsodoseCheck(self, msg):
"""When the isodose list changes, update the panel."""
self.isodoses = msg.data
self.isodoses = msg
self.SetFocus()
self.Refresh()
def OnDrawingPrefsChange(self, msg):
def OnDrawingPrefsChange(self, topic, msg):
"""When the drawing preferences change, update the drawing styles."""
if (msg.topic[2] == 'isodose_line_style'):
self.isodose_line_style = msg.data
elif (msg.topic[2] == 'isodose_fill_opacity'):
self.isodose_fill_opacity = msg.data
elif (msg.topic[2] == 'structure_line_style'):
self.structure_line_style = msg.data
elif (msg.topic[2] == 'structure_fill_opacity'):
self.structure_fill_opacity = msg.data
topic = topic.split('.')
if (topic[1] == 'isodose_line_style'):
self.isodose_line_style = msg
elif (topic[1] == 'isodose_fill_opacity'):
self.isodose_fill_opacity = msg
elif (topic[1] == 'structure_line_style'):
self.structure_line_style = msg
elif (topic[1] == 'structure_fill_opacity'):
self.structure_fill_opacity = msg
self.Refresh()
def OnPluginLoaded(self, msg):
"""When a 2D View-dependent plugin is loaded, initialize the plugin."""
name = msg.data.pluginProperties()['name']
self.plugins[name] = msg.data.plugin(self)
name = msg.pluginProperties()['name']
self.plugins[name] = msg.plugin(self)
def DrawStructure(self, structure, gc, position, prone, feetfirst):
"""Draw the given structure on the panel."""
......@@ -246,7 +247,7 @@ class plugin2DView(wx.Panel):
# to compare with the image z position
if not "zarray" in structure:
structure['zarray'] = np.array(
structure['planes'].keys(), dtype=np.float32)
list(structure['planes'].keys()), dtype=np.float32)
structure['zkeys'] = structure['planes'].keys()
# Return if there are no z positions in the structure data
......@@ -264,7 +265,7 @@ class plugin2DView(wx.Panel):
color = wx.Colour(structure['color'][0], structure['color'][1],
structure['color'][2], int(self.structure_fill_opacity*255/100))
# Set fill (brush) color, transparent for external contour
if (('RTROIType' in structure) and (structure['RTROIType'].lower() == 'external')):
if (('type' in structure) and (structure['type'].lower() == 'external')):
gc.SetBrush(wx.Brush(color, style=wx.TRANSPARENT))
else:
gc.SetBrush(wx.Brush(color))
......@@ -272,11 +273,11 @@ class plugin2DView(wx.Panel):
style=self.GetLineDrawingStyle(self.structure_line_style)))
# Create the path for the contour
path = gc.CreatePath()
for contour in structure['planes'][structure['zkeys'][index]]:
if (contour['geometricType'] == u"CLOSED_PLANAR"):
for contour in structure['planes'][list(structure['zkeys'])[index]]:
if (contour['type'] == u"CLOSED_PLANAR"):
# Convert the structure data to pixel data
pixeldata = self.GetContourPixelData(
self.structurepixlut, contour['contourData'], prone, feetfirst)
self.structurepixlut, contour['data'], prone, feetfirst)
# Move the origin to the last point of the contour
point = pixeldata[-1]
......@@ -415,7 +416,7 @@ class plugin2DView(wx.Panel):
image = guiutil.convert_pil_to_wx(
self.images[self.imagenum-1].GetImage(self.window, self.level))
bmp = wx.BitmapFromImage(image)
bmp = wx.Bitmap(image)
self.bwidth, self.bheight = image.GetSize()
# Center the image
......@@ -440,7 +441,7 @@ class plugin2DView(wx.Panel):
feetfirst = True
else:
feetfirst = False
for id, structure in self.structures.iteritems():
for id, structure in self.structures.items():
self.DrawStructure(structure, gc, self.z, prone, feetfirst)
# Draw the isodoses if present
......@@ -451,7 +452,7 @@ class plugin2DView(wx.Panel):
np.arange(grid.shape[1]), np.arange(grid.shape[0]))
# Instantiate the isodose generator for this slice
isodosegen = cntr.Cntr(x, y, grid)
for id, isodose in iter(sorted(self.isodoses.iteritems())):
for id, isodose in iter(sorted(self.isodoses.items())):
self.DrawIsodose(isodose, gc, isodosegen)
# Restore the translation and scaling
......@@ -486,7 +487,7 @@ class plugin2DView(wx.Panel):
# Send message with the current image number and various properties
pub.sendMessage('2dview.updated.image',
{'number':self.imagenum, # slice number
msg={'number':self.imagenum, # slice number
'z':self.z, # slice location
'window':self.window, # current window value
'level':self.level, # curent level value
......@@ -535,24 +536,27 @@ class plugin2DView(wx.Panel):
# Set an empty text placeholder if the coordinates are not within range
text = ""
value = ""
# Skip processing if images are not loaded
if not len(self.images):
pub.sendMessage('main.update_statusbar', msg={1:text, 2:value})
# Only display if the mouse coordinates are within the image size range
if ((0 <= xpos < len(self.structurepixlut[0])) and
(0 <= ypos < len(self.structurepixlut[1])) and self.mouse_in_window):
text = "X: " + unicode('%.2f' % self.structurepixlut[0][xpos]) + \
" mm Y: " + unicode('%.2f' % self.structurepixlut[1][ypos]) + \
" mm / X: " + unicode(xpos) + \
" px Y:" + unicode(ypos) + " px"
text = "X: " + str('%.2f' % self.structurepixlut[0][xpos]) + \
" mm Y: " + str('%.2f' % self.structurepixlut[1][ypos]) + \
" mm / X: " + str(xpos) + \
" px Y:" + str(ypos) + " px"
# Lookup the current image and find the value of the current pixel
image = self.images[self.imagenum-1]
# Rescale the slope and intercept of the image if present
if (image.ds.has_key('RescaleIntercept') and
image.ds.has_key('RescaleSlope')):
if ('RescaleIntercept' in image.ds and
'RescaleSlope' in image.ds):
pixel_array = image.ds.pixel_array*image.ds.RescaleSlope + \
image.ds.RescaleIntercept
else:
pixel_array = image.ds.pixel_array
value = "Value: " + unicode(pixel_array[ypos, xpos])
value = "Value: " + str(pixel_array[ypos, xpos])
# Lookup the current dose plane and find the value of the current
# pixel, if the dose has been loaded
......@@ -564,10 +568,10 @@ class plugin2DView(wx.Panel):
dose = dosegrid[ydpos, xdpos] * \
self.dosedata['dosegridscaling']
value = value + " / Dose: " + \
unicode('%.4g' % dose) + " Gy / " + \
unicode('%.4g' % float(dose*10000/self.rxdose)) + " %"
str('%.4g' % dose) + " Gy / " + \
str('%.4g' % float(dose*10000/self.rxdose)) + " %"
# Send a message with the text to the 2nd and 3rd statusbar sections
pub.sendMessage('main.update_statusbar', {1:text, 2:value})
pub.sendMessage('main.update_statusbar', msg={1:text, 2:value})
def OnZoomIn(self, evt):
"""Zoom the view in."""
......@@ -585,13 +589,6 @@ class plugin2DView(wx.Panel):
def OnKeyDown(self, evt):
"""Change the image when the user presses the appropriate keys."""
# Needed to work around a bug in Windows. See main.py for more details.
if guiutil.IsMSWindows():
try:
evt = evt.data
except AttributeError:
keyname = evt.GetKeyCode()
if len(self.images):
keyname = evt.GetKeyCode()
prevkey = [wx.WXK_UP, wx.WXK_PAGEUP]
......@@ -620,14 +617,6 @@ class plugin2DView(wx.Panel):
def OnMouseWheel(self, evt):
"""Change the image when the user scrolls the mouse wheel."""
# Needed to work around a bug in Windows. See main.py for more details.
if guiutil.IsMSWindows():
try:
evt = evt.data
except AttributeError:
delta = evt.GetWheelDelta()
rot = evt.GetWheelRotation()
if len(self.images):
delta = evt.GetWheelDelta()
rot = evt.GetWheelRotation()
......@@ -652,7 +641,7 @@ class plugin2DView(wx.Panel):
(self.mouse_in_window) and
(evt.LeftDown())):
pub.sendMessage('2dview.mousedown',
{'x':self.xpos,
msg={'x':self.xpos,
'y':self.ypos,
'xmm':self.structurepixlut[0][self.xpos],
'ymm':self.structurepixlut[1][self.ypos]})
......@@ -660,7 +649,7 @@ class plugin2DView(wx.Panel):
def OnMouseUp(self, evt):
"""Reset the cursor when the mouse is released."""
self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
self.SetCursor(wx.Cursor(wx.CURSOR_DEFAULT))
def OnMouseEnter(self, evt):
"""Set a flag when the cursor enters the window."""
......@@ -679,7 +668,7 @@ class plugin2DView(wx.Panel):
if evt.LeftIsDown():
self.OnLeftIsDown(evt)
self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
self.SetCursor(wx.Cursor(wx.CURSOR_SIZING))
elif evt.RightIsDown():
self.OnRightIsDown(evt)
# Custom cursors with > 2 colors only works on Windows currently
......@@ -714,7 +703,7 @@ class plugin2DView(wx.Panel):
menu = wx.Menu()
if len(self.plugins):
for name, p in self.plugins.iteritems():
for name, p in self.plugins.items():
id = wx.NewId()
self.Bind(wx.EVT_MENU, p.pluginMenu, id=id)
menu.Append(id, name)
......
#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-
# -*- coding: utf-8 -*-
# anonymize.py
"""dicompyler plugin that anonymizes DICOM / DICOM RT data."""
# Copyright (c) 2010-2012 Aditya Panchal
# Copyright (c) 2010-2017 Aditya Panchal
# This file is part of dicompyler, released under a BSD license.
# See the file license.txt included with this distribution, also
# available at http://code.google.com/p/dicompyler/
# available at https://github.com/bastula/dicompyler/
#
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
from wx.lib.pubsub import Publisher as pub
from wx.lib.pubsub import pub
import os, threading
from dicompyler import guiutil, util
......@@ -22,7 +22,7 @@ def pluginProperties():
props['menuname'] = "as Anonymized DICOM"
props['description'] = "Anonymizes DICOM / DICOM RT data"
props['author'] = 'Aditya Panchal'
props['version'] = "0.4.2"
props['version'] = "0.5.0"
props['plugin_type'] = 'export'
props['plugin_version'] = 1
props['min_dicom'] = []
......@@ -45,7 +45,7 @@ class plugin:
def OnUpdatePatient(self, msg):
"""Update and load the patient data."""
self.data = msg.data
self.data = msg
def pluginMenu(self, evt):
"""Anonymize DICOM / DICOM RT data."""
......@@ -87,13 +87,13 @@ class plugin:
length = 0
for key in ['rtss', 'rtplan', 'rtdose']:
if data.has_key(key):
if key in data:
length = length + 1
if data.has_key('images'):
if 'images' in data:
length = length + len(data['images'])
i = 1
if data.has_key('rtss'):
if 'rtss' in data:
rtss = data['rtss']
wx.CallAfter(progressFunc, i, length,
'Anonymizing file ' + str(i) + ' of ' + str(length))
......@@ -101,12 +101,12 @@ class plugin:
self.updateElement(rtss, 'SeriesDescription', 'RT Structure Set')
self.updateElement(rtss, 'StructureSetDate', '19010101')
self.updateElement(rtss, 'StructureSetTime', '000000')
if rtss.has_key('RTROIObservations'):
if 'RTROIObservations' in rtss:
for item in rtss.RTROIObservations:
self.updateElement(item, 'ROIInterpreter', 'anonymous')
rtss.save_as(os.path.join(path, 'rtss.dcm'))
i = i + 1
if data.has_key('rtplan'):
if 'rtplan' in data:
rtplan = data['rtplan']
wx.CallAfter(progressFunc, i, length,
'Anonymizing file ' + str(i) + ' of ' + str(length))
......@@ -115,10 +115,10 @@ class plugin:
self.updateElement(rtplan, 'RTPlanName', 'plan')
self.updateElement(rtplan, 'RTPlanDate', '19010101')
self.updateElement(rtplan, 'RTPlanTime', '000000')
if rtplan.has_key('ToleranceTables'):
if 'ToleranceTables' in rtplan:
for item in rtplan.ToleranceTables:
self.updateElement(item, 'ToleranceTableLabel', 'tolerance')
if rtplan.has_key('Beams'):
if 'Beams' in rtplan:
for item in rtplan.Beams:
self.updateElement(item, 'Manufacturer', 'manufacturer')
self.updateElement(item, 'InstitutionName', 'institution')
......@@ -126,7 +126,7 @@ class plugin:
self.updateElement(item, 'InstitutionalDepartmentName', 'department')
self.updateElement(item, 'ManufacturersModelName', 'model')
self.updateElement(item, 'TreatmentMachineName', 'txmachine')
if rtplan.has_key('TreatmentMachines'):
if 'TreatmentMachines' in rtplan:
for item in rtplan.TreatmentMachines:
self.updateElement(item, 'Manufacturer', 'manufacturer')
self.updateElement(item, 'InstitutionName', 'vendor')
......@@ -135,13 +135,13 @@ class plugin:
self.updateElement(item, 'ManufacturersModelName', 'model')
self.updateElement(item, 'DeviceSerialNumber', '0')
self.updateElement(item, 'TreatmentMachineName', 'txmachine')
if rtplan.has_key('Sources'):
if 'Sources' in rtplan:
for item in rtplan.Sources:
self.updateElement(item, 'SourceManufacturer', 'manufacturer')
self.updateElement(item, 'SourceIsotopeName', 'isotope')
rtplan.save_as(os.path.join(path, 'rtplan.dcm'))
i = i + 1
if data.has_key('rtdose'):
if 'rtdose' in data:
rtdose = data['rtdose']
wx.CallAfter(progressFunc, i, length,
'Anonymizing file ' + str(i) + ' of ' + str(length))
......@@ -149,7 +149,7 @@ class plugin:
self.updateElement(rtdose, 'SeriesDescription', 'RT Dose')
rtdose.save_as(os.path.join(path, 'rtdose.dcm'))
i = i + 1
if data.has_key('images'):
if 'images' in data:
images = data['images']
for n, image in enumerate(images):
wx.CallAfter(progressFunc, i, length,
......@@ -217,9 +217,7 @@ class AnonymizeDialog(wx.Dialog):
"""Dialog that shows the options to anonymize DICOM / DICOM RT data."""
def __init__(self):
pre = wx.PreDialog()
# the Create step is done by XRC.
self.PostCreate(pre)
wx.Dialog.__init__(self)
def Init(self):
"""Method called after the dialog has been initialized."""
......@@ -253,7 +251,7 @@ class AnonymizeDialog(wx.Dialog):
# Initialize the import location via pubsub
pub.subscribe(self.OnImportPrefsChange, 'general.dicom.import_location')
pub.sendMessage('preferences.requested.value', 'general.dicom.import_location')
pub.sendMessage('preferences.requested.value', msg='general.dicom.import_location')
# Pre-select the text on the text controls due to a Mac OS X bug
self.txtFirstName.SetSelection(-1, -1)
......@@ -271,7 +269,7 @@ class AnonymizeDialog(wx.Dialog):
def OnImportPrefsChange(self, msg):
"""When the import preferences change, update the values."""
self.path = unicode(msg.data)
self.path = str(msg)
self.txtDICOMFolder.SetValue(self.path)
def OnFolderBrowse(self, evt):
......
#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-
# -*- coding: utf-8 -*-
# dvh.py
"""dicompyler plugin that displays a dose volume histogram (DVH)
with adjustable constraints via wxPython and matplotlib."""
# Copyright (c) 2009-2012 Aditya Panchal
# Copyright (c) 2009-2017 Aditya Panchal
# This file is part of dicompyler, released under a BSD license.
# See the file license.txt included with this distribution, also
# available at http://code.google.com/p/dicompyler/
# available at https://github.com/bastula/dicompyler/
#
# It is assumed that the reference (prescription) dose is in cGy.
import wx
from wx.xrc import XmlResource, XRCCTRL, XRCID
from wx.lib.pubsub import Publisher as pub
from wx.lib.pubsub import pub
from dicompyler import guiutil, util
from dicompyler import dvhdata, guidvh
from dicompyler import wxmpl
from dicompyler import guidvh
import numpy as np
def pluginProperties():
......@@ -25,7 +24,7 @@ def pluginProperties():
props['name'] = 'DVH'
props['description'] = "Display and evaluate dose volume histogram (DVH) data"
props['author'] = 'Aditya Panchal'
props['version'] = "0.4.2"
props['version'] = "0.5.0"
props['plugin_type'] = 'main'
props['plugin_version'] = 1
props['min_dicom'] = ['rtss', 'rtdose']
......@@ -48,10 +47,8 @@ class pluginDVH(wx.Panel):
"""Plugin to display DVH data with adjustable constraints."""
def __init__(self):
pre = wx.PrePanel()
# the Create step is done by XRC.
self.PostCreate(pre)
wx.Panel.__init__(self)
def Init(self, res):
"""Method called after the panel has been initialized."""
......@@ -85,11 +82,8 @@ class pluginDVH(wx.Panel):
controls.append(self.lblType.GetContainingSizer().GetStaticBox())
if guiutil.IsMac():
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
font.SetPointSize(10)
for control in controls:
control.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
control.SetFont(font)
# Adjust the control size for the result value labels
te = self.lblType.GetTextExtent('0')
......@@ -98,17 +92,22 @@ class pluginDVH(wx.Panel):
self.Layout()
# Bind ui events to the proper methods
wx.EVT_CHOICE(self, XRCID('choiceConstraint'), self.OnToggleConstraints)
wx.EVT_SPINCTRL(self, XRCID('txtConstraint'), self.OnChangeConstraint)
wx.EVT_COMMAND_SCROLL_THUMBTRACK(self, XRCID('sliderConstraint'), self.OnChangeConstraint)
wx.EVT_COMMAND_SCROLL_CHANGED(self, XRCID('sliderConstraint'), self.OnChangeConstraint)
self.Bind(
wx.EVT_CHOICE, self.OnToggleConstraints, id=XRCID('choiceConstraint'))
self.Bind(
wx.EVT_SPINCTRL, self.OnChangeConstraint, id=XRCID('txtConstraint'))
self.Bind(
wx.EVT_COMMAND_SCROLL_THUMBTRACK,
self.OnChangeConstraint, id=XRCID('sliderConstraint'))
self.Bind(
wx.EVT_COMMAND_SCROLL_CHANGED,
self.OnChangeConstraint, id=XRCID('sliderConstraint'))
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
# Initialize variables
self.structures = {} # structures from initial DICOM data
self.checkedstructures = {} # structures that need to be shown
self.dvhs = {} # raw dvhs from initial DICOM data
self.dvhdata = {} # dict of dvh constraint functions
self.dvharray = {} # dict of dvh data processed from dvhdata
self.dvhscaling = {} # dict of dvh scaling data
self.plan = {} # used for rx dose
......@@ -122,9 +121,9 @@ class pluginDVH(wx.Panel):
def OnUpdatePatient(self, msg):
"""Update and load the patient data."""
self.structures = msg.data['structures']
self.dvhs = msg.data['dvhs']
self.plan = msg.data['plan']
self.structures = msg['structures']
self.dvhs = msg['dvhs']
self.plan = msg['plan']
# show an empty plot when (re)loading a patient
self.guiDVH.Replot()
self.EnableConstraints(False)
......@@ -132,30 +131,27 @@ class pluginDVH(wx.Panel):
def OnDestroy(self, evt):
"""Unbind to all events before the plugin is destroyed."""
pub.unsubscribe(self.OnUpdatePatient)
pub.unsubscribe(self.OnStructureCheck)
pub.unsubscribe(self.OnStructureSelect)
pub.unsubscribe(self.OnUpdatePatient, 'patient.updated.parsed_data')
pub.unsubscribe(self.OnStructureCheck, 'structures.checked')
pub.unsubscribe(self.OnStructureSelect, 'structure.selected')
def OnStructureCheck(self, msg):
"""When a structure changes, update the interface and plot."""
# Make sure that the volume has been calculated for each structure
# before setting it
self.checkedstructures = msg.data
for id, structure in self.checkedstructures.iteritems():
if not self.structures[id].has_key('volume'):
self.checkedstructures = msg
for id, structure in self.checkedstructures.items():
if not 'volume' in self.structures[id]:
self.structures[id]['volume'] = structure['volume']
# make sure that the dvh has been calculated for each structure
# before setting it
if self.dvhs.has_key(id):
if id in self.dvhs:
self.EnableConstraints(True)
# Create an instance of the dvhdata class to can access its functions
self.dvhdata[id] = dvhdata.DVH(self.dvhs[id])
# Create an instance of the dvh arrays so that guidvh can plot it
self.dvharray[id] = dvhdata.DVH(self.dvhs[id]).dvh
self.dvharray[id] = self.dvhs[id].relative_volume.counts
# Create an instance of the dvh scaling data for guidvh
self.dvhscaling[id] = self.dvhs[id]['scaling']
self.dvhscaling[id] = 1 # self.dvhs[id]['scaling']
# 'Toggle' the choice box to refresh the dose data
self.OnToggleConstraints(None)
if not len(self.checkedstructures):
......@@ -166,15 +162,13 @@ class pluginDVH(wx.Panel):
def OnStructureSelect(self, msg):
"""Load the constraints for the currently selected structure."""
if (msg.data['id'] == None):
if (msg['id'] == None):
self.EnableConstraints(False)
else:
self.structureid = msg.data['id']
if self.dvhs.has_key(self.structureid):
# Create an instance of the dvhdata class to can access its functions
self.dvhdata[self.structureid] = dvhdata.DVH(self.dvhs[self.structureid])
self.structureid = msg['id']
if self.structureid in self.dvhs:
# Create an instance of the dvh scaling data for guidvh
self.dvhscaling[self.structureid] = self.dvhs[self.structureid]['scaling']
self.dvhscaling[self.structureid] = 1 # self.dvhs[self.structureid]['scaling']
# 'Toggle' the choice box to refresh the dose data
self.OnToggleConstraints(None)
else:
......@@ -198,7 +192,7 @@ class pluginDVH(wx.Panel):
# Replot the remaining structures and disable the constraints
# if a structure that has no DVH calculated is selected
if not self.dvhs.has_key(self.structureid):
if not self.structureid in self.dvhs:
self.guiDVH.Replot([self.dvharray], [self.dvhscaling], self.checkedstructures)
self.EnableConstraints(False)
return
......@@ -218,34 +212,25 @@ class pluginDVH(wx.Panel):
self.lblConstraintType.SetLabel(' Dose:')
self.lblConstraintTypeUnits.SetLabel('% ')
self.lblResultType.SetLabel('Volume:')
rxDose = float(self.plan['rxdose'])
dvhdata = (len(dvh['data'])-1)*dvh['scaling']
constraintrange = int(dvhdata*100/rxDose)
# never go over the max dose as data does not exist
if (constraintrange > int(dvh['max'])):
constraintrange = int(dvh['max'])
constraintrange = dvh.relative_dose().max
# Volume constraint in Gy
elif (constrainttype == 1):
self.lblConstraintType.SetLabel(' Dose:')
self.lblConstraintTypeUnits.SetLabel('Gy ')
self.lblResultType.SetLabel('Volume:')
constraintrange = self.plan['rxdose']/100
maxdose = int(dvh['max']*self.plan['rxdose']/10000)
# never go over the max dose as data does not exist
if (constraintrange*100 > maxdose):
constraintrange = maxdose
constraintrange = round(dvh.max)
# Dose constraint
elif (constrainttype == 2):
self.lblConstraintType.SetLabel('Volume:')
self.lblConstraintTypeUnits.SetLabel(u'% ')
self.lblConstraintTypeUnits.SetLabel('% ')
self.lblResultType.SetLabel(' Dose:')
constraintrange = 100
# Dose constraint in cc
elif (constrainttype == 3):
self.lblConstraintType.SetLabel('Volume:')
self.lblConstraintTypeUnits.SetLabel(u'cm')
self.lblConstraintTypeUnits.SetLabel('cm\u00B3')
self.lblResultType.SetLabel(' Dose:')
constraintrange = int(self.structures[self.structureid]['volume'])
constraintrange = dvh.volume
self.sliderConstraint.SetRange(0, constraintrange)
self.sliderConstraint.SetValue(constraintrange)
......@@ -265,47 +250,50 @@ class pluginDVH(wx.Panel):
self.txtConstraint.SetValue(slidervalue)
self.sliderConstraint.SetValue(slidervalue)
rxDose = self.plan['rxdose']
id = self.structureid
dvh = self.dvhs[self.structureid]
constrainttype = self.choiceConstraint.GetSelection()
# Volume constraint
if (constrainttype == 0):
absDose = rxDose * slidervalue / 100
volume = self.structures[id]['volume']
cc = self.dvhdata[id].GetVolumeConstraintCC(absDose, volume)
constraint = self.dvhdata[id].GetVolumeConstraint(absDose)
absDose = dvh.rx_dose * slidervalue
cc = dvh.volume_constraint(slidervalue)
constraint = dvh.relative_volume.volume_constraint(slidervalue)
self.lblConstraintUnits.SetLabel("%.1f" % cc + u' cm')
self.lblConstraintPercent.SetLabel("%.1f" % constraint + " %")
self.lblConstraintUnits.SetLabel(str(cc))
self.lblConstraintPercent.SetLabel(str(constraint))
self.guiDVH.Replot([self.dvharray], [self.dvhscaling],
self.checkedstructures, ([absDose], [constraint]), id)
self.checkedstructures, ([absDose], [constraint.value]), id)
# Volume constraint in Gy
elif (constrainttype == 1):
absDose = slidervalue*100
volume = self.structures[id]['volume']
cc = self.dvhdata[id].GetVolumeConstraintCC(absDose, volume)
constraint = self.dvhdata[id].GetVolumeConstraint(absDose)
cc = dvh.volume_constraint(slidervalue, dvh.dose_units)
constraint = dvh.relative_volume.volume_constraint(
slidervalue, dvh.dose_units)
self.lblConstraintUnits.SetLabel("%.1f" % cc + u' cm')
self.lblConstraintPercent.SetLabel("%.1f" % constraint + " %")
self.lblConstraintUnits.SetLabel(str(cc))
self.lblConstraintPercent.SetLabel(str(constraint))
self.guiDVH.Replot([self.dvharray], [self.dvhscaling],
self.checkedstructures, ([absDose], [constraint]), id)
self.checkedstructures, ([absDose], [constraint.value]), id)
# Dose constraint
elif (constrainttype == 2):
dose = self.dvhdata[id].GetDoseConstraint(slidervalue)
dose = dvh.dose_constraint(slidervalue)
relative_dose = dvh.relative_dose().dose_constraint(slidervalue)
self.lblConstraintUnits.SetLabel("%.1f" % dose + u' cGy')
self.lblConstraintPercent.SetLabel("%.1f" % (dose*100/rxDose) + " %")
self.lblConstraintUnits.SetLabel(str(dose))
self.lblConstraintPercent.SetLabel(str(relative_dose))
self.guiDVH.Replot([self.dvharray], [self.dvhscaling],
self.checkedstructures, ([dose], [slidervalue]), id)
self.checkedstructures,
([dose.value * 100], [slidervalue]), id)
# Dose constraint in cc
elif (constrainttype == 3):
volumepercent = slidervalue*100/self.structures[id]['volume']
dose = dvh.dose_constraint(slidervalue, dvh.volume_units)
relative_dose = dvh.relative_dose().dose_constraint(
slidervalue, dvh.volume_units)
dose = self.dvhdata[id].GetDoseConstraint(volumepercent)
self.lblConstraintUnits.SetLabel("%.1f" % dose + u' cGy')
self.lblConstraintPercent.SetLabel("%.1f" % (dose*100/rxDose) + " %")
self.lblConstraintUnits.SetLabel(str(dose))
self.lblConstraintPercent.SetLabel(str(relative_dose))
self.guiDVH.Replot([self.dvharray], [self.dvhscaling],
self.checkedstructures, ([dose], [volumepercent]), id)
self.checkedstructures,
([dose.value * 100], [volumepercent]), id)