Showing posts sorted by relevance for query plugin development. Sort by date Show all posts
Showing posts sorted by relevance for query plugin development. Sort by date Show all posts

Thursday, April 09, 2009

GNUmed plugin development - part 6

So I joined the GNUmed mailing list and immediately received feedback. The data storage aspect will be tough to tackle but we will get to that later.

The goal for today is to get our mockup GUI displayed within GNUmed. There will be no database connection but at least it will display (hopefully).

I selected /home/user/sources/gnumed/gnumed/client/wxGladeWidgets/wxgCardiacDevicePluginPnl.py and pressed 'generate code'.

This code cannot be simply run inside GNUmed so I need to find out how GNUmed makes use of the code inside its plugins. There is a slight chance that this is documented in the development section of the GNUmed wiki. There seems to be no relevant information so I will try to piece it togehter and document it.

There are example plugins. Let's take a look and learn from there. The names of the plugins to load are stored in the database.



I reason since I got some code from existing plugins I should look there. The file is called gmShowMedDocs.py. I copied that file to gmCardiacDevicePlugin.py.

Now it is time to run a local instance of GNUmed and a local database. You should really install it now. Do not try to cut corners by not bootstraping a local database. We need that to work on our plugin.

I recommend you run GNUmed via the script gm-from-cvs.sh in the CVS source tree and bootstrap a database from the CVS tree as well. Change to the server/bootstrap directory and run bootstrap-latest.sh. Extensive information is available here and here.

Be aware that the source code in a CVS tree is a moving target. You might want to consider getting the source for the stable version 0.4.2 and server v10. All files mentioned before like the wxg files and the renamed plugin file should be created in that cvs directory then. The benefit is that your plugin will be developed against the latest stable version.

However if your plugin requires database changes there is no way around using the latest development code since changes to the database made against the stable version might not work with the latest development code.

I did not run into trouble but if you do don't hesitate to contact gnumed-devel@gnu.org.

I started GNUmed (from CVS) with the local database (v11) and added my new plugin (still containing the SOAP stuff) to the database. GNUmed evidently scans the directory foo/gnumed/client/wxpython/gui for available plugins. If the file is there it will show up for activiation. I activated it from within GNUmed via 'Office' > 'manage master data' > 'Workplace profiles'.

GNUmed does not show it immediately. A restart of GNUmed is neccessary. The plugin does does not show up and throws an error in the log file.

Traceback (most recent call last):
File "/home/basti/sources/gnumed/gnumed/Gnumed/wxpython/gmHorstSpace.py", line 121, in __load_plugins
plugin = gmPlugin.instantiate_plugin('gui', curr_plugin)
File "/home/basti/sources/gnumed/gnumed/Gnumed/wxpython/gmPlugin.py", line 333, in instantiate_plugin
plugin_class = module_from_package.__dict__[plugin_name]
KeyError: u'gmCardiacDevicePlugin'

This is due to the fact that the class name and the file name (gmCardiacDevicePlugin) due not match. Open the file and change the class name.
class gmCardiacDevicePlugin(gmPlugin.cNotebookPlugin):
"""Plugin to encapsulate document tree."""

tab_name = _("Documents")

def name (self):
return gmCardiacDevicePlugin.tab_name

The section 'Menu Info' is responsible for the name of the plugin in the GNUmed menu. I changed it like this and while we are at it the notebook tab name as well.
class gmCardiacDevicePlugin(gmPlugin.cNotebookPlugin):
"""Plugin to encapsulate document tree."""

tab_name = _("Cardiac Device")

def name (self):
return gmCardiacDevicePlugin.tab_name
#--------------------------------------------------------
def GetWidget (self, parent):
self._widget = gmMedDocWidgets.cSelectablySortedDocTreePnl(parent, -1)
return self._widget
#--------------------------------------------------------
def MenuInfo (self):
return ('tools', _('Show &cardiac devices'))

I restarted GNUmed and saw the new Cardiac Device plugin. Next step is to load my GUI instead of the Document archive stuff. The line starting self._widget = ' ' needs to be changed.

But this will be the next part of our journey towards a GNUmed plugin. So far it wasn't that hard and I learned quite a bit where files are located in the source tree , what files are involved in creating a plugin and how to add a plugin to the workplace in a database.

Tuesday, April 20, 2010

GNUmed plugin development - part 1 reloaded

If you are just getting started with GNUmed, python and plugins I recommend you read over the series GNUmed plugin development for the basics.

GNUmed plugin develeopment reloaded

Back in the days I was about to develop a GNUmed plugin for pacemaker clinics. While doing this I came across a publication describing a dataset for cardiac echos so I started to develop a plugin for cardiac echocardiography.

A GNUmed plugin consists of three files. When all files are present it is picked up by GNUmed and can be activated/added for a specific workplace in the GNUmed database so it is loaded at startup. Part 4, 5 and 6 of my older series covered that aspect. This time around I am going to create the files from scratch.

Those three files are a foo.wxg file, a foo.py file that is generated from the wxg file and a wrapper file to tell GNUmed about the plugin. Optionally a fourth file can be used to seperate the widget from the rest of the code. This not neccessary however.

The four files can be downloaded here as one zip file. The directory layout of the zipped content is identical to where the files should go (e.g. 'client\wxg', 'client\wxpython').

To activate the plugin open GNUmed and go to 'GNUmed'>'Master data'>'Workplace profiles'. Select 'GNUmed Default' or whatever workplace you want to configure and press 'edit' Select 'all plugins you want' and save your selection.

Restart GNUmed and go to the 'Cardiac echo plugin'. Up to now it is little more then a user interface. It does not do much. Next step is to read all the input fields and write it to an XML file. This XML file will then be stored in the database.






Thursday, July 01, 2010

GNUmed plugin development - easy testing of plugin

Developing a plugin is no magic. We have received a report by a university student that he was able to get a plugin developed and running from the documentation we provide. He intentionally does not ask any questions but tries to dig through the documentation (blog, wiki, source code comments) to find out how everything plays together.

In one of the last articles it was demonstrated how to display a plugin. While it is nice to see your plugin in GNUmed often you don't want to start up all of GNUmed just to test your plugin.

Python comes with batteries included. Each client plugin can be started standalone through a helper called pywidgettester. This is implemented in the ExamplePlugin aleready.

Just start the plugin like this
cd ./client/wxpython/gui
python gmExamplePlugin.py
It will ask for the server it should connect to, the database it should connect to, the username and the password. Once it has connected it lets you search for a patient. Then it will display this:



These lines in a plugin make this possible
#================================================================
# MAIN
#----------------------------------------------------------------
if __name__ == '__main__':

        # GNUmed
        from Gnumed.business import gmPerson
        from Gnumed.wxpython import gmPatSearchWidgets

        _log.info("starting template plugin...")

        try:
                # obtain patient
                patient = gmPerson.ask_for_patient()
                if patient is None:
                        print "None patient. Exiting gracefully..."
                        sys.exit(0)
                gmPatSearchWidgets.set_active_patient(patient=patient)

                # display the plugin standalone
                application = wx.wx.PyWidgetTester(size = (800,600))
                widgets = gmExamplePluginWidgets.cExamplePluginPnl(application.frame, -1)

                application.frame.Show(True)
                application.MainLoop()

Happy coding

Friday, July 17, 2009

GNUmed plugin development - part 13

Be aware that some things have changed. The file GNUmedDeviceParserElementTree.py has been moved from test-area/shilbert/ to business/gmDevices.py

There are now four files involved in this plugin.
- gmDevices.py in /business - responsible for the XML data extraction part
- gmDeviceWidgets.py in /wxpython - responsible for calling gmDevices.py and compiling the ui widgets
- gmCardiacDevicePluginPnl.py in /wxGladeWidgets - is the python code representation of the ui widgets
- gmCardiacDevicePluginPnl.wxg in /wxg - is the xml representation of the ui widgets

If I need to change the plugin's appearance I need to touch first gmCardiacDevicePluginPnl.wxg with wxGlade and transfer it into the python code representation gmCardiacDevicePluginPnl.py. This files gets imported and the controls filled with data in gmDeviceWidgets.py. The business logic of data compilation from and to screen is handled in gmDevices.py

Next up is getting the device data from the database instead of an XML file. For the time being it was agreed that the xml file is simply stored as yet another document. To store it GNUmed was started and the plugin 'Document import' was used. I was asked for an associated episode. 'Routine postop cardiac device checkup' was entered. 'Device check up' was chosen as document type. This was entered via the menu item 'GNUmed>Manage master data>document types'.

A quick chat with Karsten revealed the basic code:

pat = gmPerson.gmCurrentPatient()
doc_folder = pat.get_document_folder()
docs = doc_folder.get_documents()
This returns a list of cMedDoc objects which can be parsed like that:
for doc in docs:
    doc['comment'] = ... etc.
doc_folder.get_documents() returns elements ordered by clin_when so the first element is the newest one I am looking for.
for doc in docs:
             if doc['type'] == 'cardiac device checkup report':
                         selected_docs.append(doc)
if not len(selected_docs) == 0:
             # since get_documents() is sorted I simply get the first one as the most recent one
             # for now assume that the xml file provide the cardiac device context.
             # that pretty much means logical connection of leads and generator is provided in the xml
             xml = selected_docs[0].get_parts()[0].export_to_file()
             tree = etree.parse(xml)
             DevicesDisplayed = gmDevices.device_status_as_text(tree)
             for line in DevicesDisplayed:
                        text = text + line
else:
             _log.info('There are no device checkup reports in the database')
             text = _('There are no device checkup reports in the database')
To construct the line
xml = selected_docs[0].get_parts()[0].export_to_file()
one has to take a good look at the file gmMedDoc.py in /business. Using that code it is now possible to get the XML from the database and display the current device status.

Currently the XML is entered via the default the 'attach document' plugin. There is a nifty solution to a problem. How to check for documents of a type that might not yet exist in the database ?

When the cardiac device plugin is initiated the document type is automatically added to the document types in the database. The file gmDeviceWidgets holds the following code for that.
def __init__(self, *args, **kwargs):
         # check if report types exist in db, if not create them
         dtype = gmMedDoc.create_document_type('cardiac device checkup report')
         dtype.set_translation(_('cardiac device checkup report'))
The next step is to get rid of saving to a file and reading from that. Another issue is to get refresh going when entering new data.

Tuesday, April 07, 2009

GNUmed plugin development - part 1

Hi all,

I decided I want to add some features to GNUmed. I could ask on the mailing list but there is really only one or two people who do the coding. I decided to learn it myself.

Part one will describe what I would like to achieve and what I need. All later stories will hopefully show my progress or lack thereof.

I am working in a large heart center. I am resident in cardiology. My tasks include pacemaker and defibrillator interrogation. Various software packages exist but rarely are they available in cardiologist's offices. GNUmed has the potential to change that.

While pacemakers are manageable defibrillators are another story. Inconsistent programming is potentially lethal. Sharing of interrogation data hardly happens.

Today we are going to change that.

We need to get skilled in python. We need to find out about the plugin stuff in GNUmed and have someone adapt the database. Karsten Hilbert offered to change the GNUmed middleware. For that to happen we need to tell him exactly what we need.

Here is what we will do.

0.) document what we would like to achieve
1.) develop a mockup GUI on paper
2.) whip up the mockup GUI in wxglade
3.) join and tell the GNUmed mailing list about it
4.) discuss and change the GUI mockup so it will be a *first usable version*
5.) check out the GNUmed source code
6.) take a good look at the source of the plugins already in GNUmed
7.) start hacking on the plugin to get the plugin displayed in GNUmed and basics like patient name
8.) announce the code and share the code with the GNUmed mailing list
9.) start documentation for our plugin in the GNUmed manual

From here on out a discussion will take place which will lead to

10.) changes to the plugin source code and GUI
11.) getting CVS commit rights and checking in the source into CVS

If you did not notice you are by now a GNUmed developer.

Wednesday, April 29, 2009

GNUmed plugin development - part 10

It has taken some time to tackle part 10 of my way to a GNUmed plugin. A new GNUmed version has been released. Release dude duties have taken up most of my freetime.

Anyway I am back. The widget user interface loads in GNUmed but does not do anything useful yet. The time for clicking UI elements is over. The hard labor to code in python is about to begin. Here is where most idealists have left off and left GNUmed.

I was fortunate enough to have a face to face meeting with Karsten to ask all my newbie questions.

1.) How to proceed ?
2.) How to extend the database tables to house my data ?

His answer wasn't straight forward. First he wanted to know exactly what I intended to do. I told him my vision but visions don't amount to code. I had to break the vision down into manageable pieces.

Turns out there are many ways to store the input gathered via the the plugin user interface. There is the option to extend the database structure which is more work for him and involves a lot of new code (potentially buggy that is) or store the input as a XML document like any other document.

I did not understand the concept of storing the input as a document at first so he had to explain the concept to me. It comes down to this. Instead of storing any value gotten from the user interface in a table/field in the database one stores the values in an xml file and stores the xml file in the database. The xml file is a datbase more or less which gets stored in the the PostgreSQL database.

This means I can reuse the document plugin's classes and functions and do the content storage via python's XML routines.

I first constructed an XML file to work with. The editor used was EditiX - a cross-platform and multi-purpose XML editor, XSLT debugger and Visual Schema Editor. It provides editing support for XML, DTD, XHTML, XSLT, XSD, XML RelaxNG, ...

Here is the manually crafted XML file.

<?xml version="1.0" encoding="UTF-8"?>

<!-- New document created with EditiX at Wed Apr 29 15:37:23 CEST 2009 -->

<CardicacDevices>
        <DevicePart type="generator">
                <class>AID</class>
                <type>generator</type>
                <isactive>yes</isactive>
                <dateofimplant></dateofimplant>
                <dateofexplant></dateofexplant>
                <isexplanted>no</isexplanted>
                <comment></comment>
                <implantsite>subpectoral</implantsite>
                <implantregion>right subclavian</implantregion>
                        <GeneratorPart type="CPU" >
                                <manufacturer>SJM</manufacturer>
                                <model></model>
                                <serial></serial>
                        </GeneratorPart>
                        <GeneratorPart type="Battery">
                        <manufacturer></manufacturer>
                        <serial></serial>
                </GeneratorPart>
        </DevicePart>

        <DevicePart type="lead">
                <manufacturer>          </manufacturer>
                <model></model>
                <polarity></polarity>
                <serial></serial>
                <isactive>yes</isactive>
                <hasYconnector>yes</hasYconnector>
                <dateofimplant></dateofimplant>
                <dateofexplant></dateofexplant>
                <isexplanted>   no</isexplanted>
                <leadposition>RV</leadposition>
                <leadslot>RA</leadslot>
                <comment></comment>
                        <connectedtodevice>aid</connectedtodevice>
                        <connectedtodevice>RVlead</connectedtodevice>
        </DevicePart>

</CardicacDevices>
This is a simplified version. It lacks the actual measurements. Next step is to learn how to transfer that XML to a human readable form so I can display it in the left upper section of the plugin.

Thursday, April 22, 2010

GNUmed plugin development - how to share your work

In the last article I shared my work by posting a few screenshots and a tarball for everyone to grab. However the ultimate goal is to get this included onto GNUmed's main code repository.

A while back the GNUmed project announced it made the switch from CVS to GIT. That means that it moved from a centralized repository (CVS head) to a distributed repository architecture. In short. There is no longer an 'officical' or 'central' code repository to submit our plugin to.

However not all is lost. The most active coders share their so called GIT trees with the world so that is now the de facto standard. This is not carved into stone but since Karsten Hilbert has been making released based on his GIT tree this tree is the main tree for the time being.

What we need to do is ask him to merge our code into his tree so it will be part of the next releases. Same procedure is implemented for the Linux kernel where Linus Torvalds merges code from other developers and finally releases a new kernel.

There is one problem. I did not consider this when working on the plugin so I need to get my stuff straight and my hands dirty with GIT.

First I get a local copy of the 'main tree'. It is hosted at gitorious for everyone to grab. I grab a copy by cloning it. For that I create a subdirectory 'Sources' in my home directory and run the following command.
git clone git://gitorious.org/gnumed/gnumed.git
Now I have a copy of the main tree which can be kept up to date by running the following commands.
git checkout master
git fetch
This gives us the latest code in the master branch. For our plugin it makes sense to create our own branch. This is done by the following command.
git branch add-echo-xml
To list all available branches use:
git branch
It will output:
*  add-echo-xml                                                                                                                                                             
master   
The branch with the asterisk is the currently active one.  To switch to the master tree (e.g. for updates) use:
git checkout master
To activate and develop in our own branch use this:
git checkout add-echo-xml
It will do some magic and track all change we make to the existing and new files. It will find out that we change existing files, delete files or add files. Now is the time to add our previously created files from the tarball I talked about in the last article. Just copy them into the corresponding directories. We have to tell git about them so it can track the changes to them. This is done by adding the file once and commiting after a change. In the wxGladeWidgets directory we run:
git add wxgCardiacEchoPluginPnl.py
In the wxg direcotory we run:
git add wxgCardiacEchoPnl.wxg
In the wxpython directory we run:
git add gmEchoWidgets.py
In the wxpython/gui directory we run:
git add gmCardiacEchoPlugin.py
Finally we commit all this by:
git commit
This will ask for a commit message which is a short message to keep track of your changes. Once done it will output:                                                                                                                                                                           
[add-echo-xml f0b2d35] - added new files for echo plugin                                                                                                                   
 4 files changed, 1196 insertions(+), 0 deletions(-)                                                                                                                       
 create mode 100755 gnumed/gnumed/client/wxGladeWidgets/wxgCardiacEchoPluginPnl.py                                                                                         
 create mode 100644 gnumed/gnumed/client/wxg/wxgCardiacEchoPnl.wxg
 create mode 100644 gnumed/gnumed/client/wxpython/gmEchoWidgets.py
 create mode 100644 gnumed/gnumed/client/wxpython/gui/gmCardiacEchoPlugin.py
Whenever we change files during developement we need to go through this (git branch add-echo-xml, git add, git commit) again. Next step will be to figure out how to offer these local changes for merging by the person doing releases.

Tuesday, April 07, 2009

GNUmed plugin development - part 2

Hi all,

Here is what I decided to do.

0.) document what we would like to achieve
1.) develop a mockup GUI on paper
2.) whip up the mockup GUI in wxglade
3.) join and tell the GNUmed mailing list about it
4.) discuss and change the GUI mockup so it will be a *first usable version*
5.) check out the GNUmed source code
6.) take a good look at the source of the plugins already in GNUmed
7.) start hacking on the plugin to get the plugin displayed in GNUmed and basics like patient name
8.) announce the code and share the code with the GNUmed mailing list
9.) start documentation for our plugin in the GNUmed manual

This part is about documenting what I would like to achieve. It is most helpful to document this in the GNUmed wiki. I started a new GNUmed Enhancement Proposal (GEP). The proposal is at GNUmed_{01}_CardiacDevicePlugin.

Thursday, April 09, 2009

GNUmed plugin development - part 7

Now on to the real stuff. Looking at

class gmCardiacDevicePlugin(gmPlugin.cNotebookPlugin):

        def GetWidget (self, parent):
                self._widget = gmMedDocWidgets.cSelectablySortedDocTreePnl(parent, -1)
                return self._widget
in gmCardiacDevicePlugin.py tells me that in the case of the Document archive plugin we borrowed from the class cSelectablySortedDocTreePnl is located in the file gmMedDocWidgets. The name implies that this is a collection of classes of medical document widgets. The device parameters are essentially measurements.

There is a file called gmMeasurementWidgets available and I am going to extend that one. I added a class cCardiacDeviceMeasurementsPnl to the file gmMeasurementWidgets.py just before the __main__ statement.

#================================================================
class cCardiacDeviceMeasurementsPnl(wxgCardiacDevicePluginPnl.wxgCardiacDevicePluginPnl, gmRegetMixin.cRegetOnPaintMixin):

        """Panel holding a number of widgets to manage implanted cardiac devices. Used as notebook page."""

        def __init__(self, *args, **kwargs):

                wxgCardiacDevicePluginPnl.wxgCardiacDevicePluginPnl.__init__(self, *args, **kwargs)
                gmRegetMixin.cRegetOnPaintMixin.__init__(self)
                self.__init_ui()
                self.__register_interests()
        #--------------------------------------------------------
        # event handling
        #--------------------------------------------------------

An extended import is needed as well.

from Gnumed.wxGladeWidgets import wxgMeasurementsPnl, wxgMeasurementsReviewDlg
from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
from Gnumed.wxGladeWidgets import wxgCardiacDevicePluginPnl

Now back to gmCardiacDevicePlugin.py to reflect the changes.

class gmCardiacDevicePlugin(gmPlugin.cNotebookPlugin):

        def GetWidget (self, parent):
                self._widget = gmMeasurementWidgets.cCardiacDeviceMeasurementsPnl(parent, -1)
                return self._widget

A change in the import statements is needed here too.
from Gnumed.wxpython import gmMeasurementWidgets, gmPlugin
instead of

from Gnumed.wxpython import gmMedDocWidgets, gmPlugin, images_Archive_plugin
If you followed the above steps you might notice that the plugin throws many errors in the file gnumed.log. This file can be viewed directly from within GNUmed. It does not show up. The reason is simple. I use a complex set of widgets that are scattered across the files gmMeasurementWidgets and gmNarrativeWidgets.

The errors are that widgets found in gmNarrativeWidgets are not found, most likely because I put the class cCardiacDeviceMeasurementsPnl(parent, -1) in gmMeasurementWidgets. It could help to move the class into narrative widgets but that is not what I want. I could as well start another file called gmDeviceWidgets.

Lets see what the mailing list has to say about that.


What I have learned so far. First I need to create a mockup gui, store it in the wxg directory, create python code from it and store it in the directory wxGladeWidgets. Then in the wxpython/gui directory a file called gmPluginName is needed. When created from scratch or copied over from a template make sure to have the class name reflect the filename, the tab name set and the menu entry configured.  Last but not least the GetWidget line needs to be filled to reflect what GUI code to load.

Monday, April 13, 2009

GNUmed plugin development - part 9

Karsten Hilbert and I had a little chat to help me plan the next steps. The current status of the plugin is as follows

a) it loads fine in GNUmed
b) it crashes when trying to load data
c) an incomplete class cCardiacDevicePluginPnl has been partially implemented

Karsten advised me to move the device specific bits to a seperate file called gmDeviceWidgets in /usr/foo/gnumed/gnumed/client/wxpython

I did so. I forgot to add the import of gmDeviceWidgets to the top of gmCardiacDevicePlugin in /usr/foo/gnumed/gnumed/client/wxpython/gui but that was quickly resolved after reading about the 'global gmDeviceWidgets undefined' stuff in the log file.

It now does not crash anymore but does not yet load the SOAP notebook. Apart from missing soap entry which does not need to be a notebook but rather a single soap editor the right column needs to house an ICD data entry widget.

This widget will be produced in the next step in the wxg file. See screenshot below for current state of the plugin.


Thursday, April 09, 2009

GNUmed plugin development - part 5

Now that I have the source code available I can load an existing wxg file in wxglade as a start for my own plugin.

I opened /home/user/source/gnumed/gnumed/client/wxg/wxSoapPluginPnl.wxg and started to changed it to my fit my needs. I then saved it as /home/user/source/gnumed/gnumed/client/wxg/wxCardiacDevicePluginPnl.wxg

The result looks like this:




What you can see here is the complexity we are getting into. There can be more than one device not to mention a combination of active and inactive devices. Active and inactive leads. Why is all this important ? Because the life of a patient may depend on it. Hospital might call in and ask how long the leads have been in to decide whether to cap or extract a lead. There should be a device history. It is important to know which of you patients have which leads and generators when a recall is issued.

Now that we have a GUI we need to produce python code for it. There is a feature in wxglade to produce python code. This will be described in the next part.

Thursday, May 07, 2009

GNUmed plugin development - part 11

Python comes with various XML tools. Fortunately there is a library called ElementTree which makes XML mangling quite a bit easier. The task is to extract the information on each device from the XML file and format a string that can be fed to the display.

I had to tidy up my pyhon skills a lot and started with a standalone python script that reads in an XML file and parses it for a number of known tags such as 'vendor', 'model' and much more. The next step is to format this into the string I would like to display on the screen. The benefit of seperating the data extraction from the data formating part is that many different display options can be catered for.

In short my preferred mode of display it to group by logical device unit. Each device such as a pacemaker and its leads make up a device. Each device has subparts such as the generator consisting of a mainboard and battery. This is not mandatory but makes sense because I would like to have the option to document software updates and battery types. Not every pacemaker of the same model has the same battery model over time.

I admit the demo-XML file changed a number of times and will most likely see some more restructuring until the plugin is complete.

They say it is best to commit often and early to not lose work on one hand and get feedback on the other.

Having said that I am going to check in the XML file and standalone XML-parser into CVS. It is wise to use the folder 'test-area' in the CVS tree for that.

cvs add GNUmed6.xml GNUmedDeviceParserElementTree.py

Enter passphrase for key '/home/basti/.ssh/id_rsa':

cvs add: scheduling file `GNUmed6.xml' for addition

cvs add: scheduling file `GNUmedDeviceParserElementTree.py' for addition

cvs add: use `cvs commit' to add these files permanently
and
cvs commit GNUmed6.xml GNUmedDeviceParserElementTree.py
Enter passphrase for key '/home/basti/.ssh/id_rsa':

/sources/gnumed/gnumed/gnumed/test-area/shilbert/GNUmed6.xml,v  <--  GNUmed6.xml
initial revision: 1.1
/sources/gnumed/gnumed/gnumed/test-area/shilbert/GNUmedDeviceParserElementTree.py,v  <-- GNUmedDeviceParserElementTree.py
initial revision: 1.1
When you update your CVS tree you get my work located in the folder gnumed/gnumed/test-area/shilbert

to try it run:
python GNUmedDeviceParserElementTree.py
Reading the XML file GNUmed6.xml it will output this:
The file has the following devices: <Element CardicacDevice at b7bfb20c>:
The device has the following parts: <Element DevicePart at b7bfb18c>
hey we are dealing with a generator here:-)
('St. Jude Medical', 'Atlas DR', 'active')

Seems like we are getting there. Next up is to finish the parsing. While this is getting done Karsten needs to write the routine to store and retrieve and xml object in the database.

Wednesday, April 08, 2009

GNUmed plugin development - part 3

Computers are great but paper is unbeaten for quick GUI mockups. While putting down a few sketches the ideas develop to a clearer vision.

I want to

a) provide a quick glance overview over the currently active programmed device settings, number of devices, caveats regarding leads, battery
b) provide a quick overview over the previous interrogations and previous , active as well as future problems
c) display the individual encounter/interrogation as well as changes during that session
d) document the current session (device data, programmed settings)
e) document patient's health with regards to the device
f) document EKG findings with and without the device active
g) display reference info such as the information supplied by manufacturers (lead recalls, current scientific studies)

I started GNUmed and took a look if any plugin comes close to what I want. I chose the new progress notes editor and printed a screenshot taken by GNUmed's screenshot feature.

GNUmed plugin development - part 4

Paper is great but we need code to make it appear on screen. At this stage the vision has not yet fully developed. Different from what traditional IT/IS university taught specialists might want to do now I will develop and reshape the code instead of defining detailed specs first.

This is the medical approach. I could try to learn tools like UML to model the plugin with the added benefit of using sophisticated tools to produce code from the model but that is not my preferred approach.

Anyway I downloaded wxglade from https://bitbucket.org/agriggio/wxglade/ as zip file. Unpack it into a directory like /home/user/wxglade and you are good to go. Find the wxglade shell script and change it to make it find wxglade.py in the same directory. Just remove the /usr/lib/foo stuff.

Fire it up and either start a new project or start from one of the wxg in the GNUmed CVS source tree. Oh I don't have the CVS tree copied yet. So lets do that now.

Heading over to wiki.gnumed.de in the Dowload section I am presented with the option to either download a snapshot package or get a copy of the CVS tree. SInce I will probably need to get updates often I will go for the CVS option.

I followed this guide and retrieved a copy of the source for local storage.

Update: GNUmed has swithed to GIT for source code management. New guide is available.

Thursday, July 09, 2009

GNUmed plugin development - part 12

Show me the code. Part 11 talked about the xml handling. I put everything in the CVS to make it available.
That was almost two months ago. Due to time constraints I only recently picked up again. I had to realize that my python skills are very rusty and that I had to learn to read and write at the same time.

In the meantime both the XML files as well as the Parser have gone through 2 rewrites. I threw the code out where I felt it became too complex for me to handle. Check out the latest version now.

now run:
python GNUmedDeviceParserElementTree.py
and observe the following output:
-------------------------------------------------------------
Device(ICD): St. Jude Medical Atlas DR (active)   Battery: St. Jude Medical (St. Jude Medical)  Implanted: 23.03.2002


RA-lead in RA-position: Medtronic Sprint XL (active,no comment on lead) Implanted: 23.04.2009
last check: 23.04.2004 Sensing: 7mV Threshold 1.4mV Impedance: 300 Ohm

RV-lead in RV-position: Medtronic Sprint fidelis (inactive,no comment on lead) Implanted: 23.04.2009
last check: 23.04.2009" Sensing: 7mV Threshold 1.4mV Impedance: 300 Ohm

LV-lead in LV-position: St. Jude QuickSite XL (active,no comment on LV lead) Implanted: 23.04.2009
last check: 23.03.2004 Sensing: 7mV Threshold 1.4mV Impedance: 300 Ohm
Now what you see here has been parsed from the XML. No more fake input. Next step is to first replace all hardcoded English strings with gettext-equivalents so translators can have a go at them. To achieve that we will take a look at other plugins that already have that feature.

Take a look at gmMeasurementsGridPlugin.py. The first few lines look like this.

import logging

from Gnumed.wxpython import gmPlugin, gmMeasurementWidgets
from Gnumed.pycommon import gmI18N

_log = logging.getLogger('gm.ui')
_log.info(__version__)
All of the above but the second and third line were added to the top of the parser. To tell gettext where to replace I marked them like this:
_('this is a string that needs a translation') vs. 'this is a string that needs a translation'
So the _() construct identifies the strings to gettext. The following lines were added to the main section.
from Gnumed.pycommon import gmI18N
gmI18N.activate_locale()
gmI18N.install_domain()
Running the parser now displayes the same result even after all 'I am a string' occurences having been replaced by _('I am a string'). The next part will try to get the parser connected to gmCardiacDevicePlugin and get this plugin displayed in GNUmed. Once this has been accomplished the XML needs to be read from the database instead of a file.

Wednesday, July 07, 2010

GNUmed plugin development - how to access the current patient

Chances are you want to access the data of the currently active patient in your plugin. There is an app for that (TM) . I mean there are some convenience functions for that. This is called middleware. No matter if the database ever changes. The way to access the data is abstracted and will remain stable.
from Gnumed.business import gmPerson

pat = gmPerson.gmCurrentPatient()
text = patient['firstnames']
text = patient['lastnames']
text = patient['dob']
text = patient['gender']
This should be pretty self explainatory. For convenience use patient['dob'].isoformat() for a string representation of the datetime object.

To work with the patient's emr you can access it like this:

emr = pat.get_emr()
Let's say you want to get the current episode and if none create one.

                if episode is None:
                        all_epis = emr.get_episodes()
                        # FIXME: what to do here ? probably create dummy episode
                        if len(all_epis) == 0:
                                episode = emr.add_episode(episode_name = _('Cardiac echo exam'), is_open = False)
                        else:
                                dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg(parent = parent, id = -1, episodes = all_epis)
                                dlg.SetTitle(_('Select the episode under which to file the document ...'))
                                btn_pressed = dlg.ShowModal()
                                episode = dlg.get_selected_item_data(only_one = True)
                                dlg.Destroy()

                                if (btn_pressed == wx.ID_CANCEL) or (episode is None):
                                        if unlock_patient:
                                                pat.locked = False
                                        return None

Monday, April 13, 2009

GNUmed plugin development - part 8

Remember I left off with a problem rather than a solution ? Well I had a little chat face to face with Karsten who happens to be the author of the SOAP plugin I borrowed so much code from.

Turns out he added some special code to the file wxgSoapPluginPnl.py which normally gets autogenerated from the wxg file. Looking at the file I found I had to add

class wxgCardiacDevicePluginPnl(wx.ScrolledWindow):
    def __init__(self, *args, **kwds):

        from Gnumed.wxpython.gmNarrativeWidgets import cSoapNoteInputNotebook
        from Gnumed.wxpython.gmDateTimeInput import cFuzzyTimestampInput
        from Gnumed.wxpython.gmEMRStructWidgets import cEncounterTypePhraseWheel
        from Gnumed.wxpython import gmListWidgets

        # begin wxGlade: wxgCardiacDevicePluginPnl.__init__

The import statements to my wxgCardiacDevicePluginPnl.py. It now actually shows up in GNUmed. Horray. But it crashes when trying to load data. this should be easy to fix.

Here is the error:

gm.gui (/home/basti/sources/gnumed/gnumed/Gnumed/wxpython/gmExceptionHandlingWidgets.py::handle_uncaught_exception_wx() #49): unhandled exception caught:
Traceback (most recent call last):
  File "/home/basti/sources/gnumed/gnumed/Gnumed/wxpython/gmHorstSpace.py", line 231, in _on_notebook_page_changed
    new_page.receive_focus()
  File "/home/basti/sources/gnumed/gnumed/Gnumed/wxpython/gmPlugin.py", line 183, in receive_focus
    self._widget.repopulate_ui()
  File "/home/basti/sources/gnumed/gnumed/Gnumed/wxpython/gmRegetMixin.py", line 128, in repopulate_ui
    self.__repopulate_ui()
  File "/home/basti/sources/gnumed/gnumed/Gnumed/wxpython/gmRegetMixin.py", line 66, in __repopulate_ui
    self._data_stale = not self._populate_with_data()
  File "/home/basti/sources/gnumed/gnumed/Gnumed/wxpython/gmRegetMixin.py", line 79, in _populate_with_data
    raise NotImplementedError, "[%s] _populate_with_data() not implemented" % self.__class__.__name__
NotImplementedError: [cCardiacDeviceMeasurementsPnl] _populate_with_data() not implemented

That is no surprise since I decided to put the class cCardiacDeviceMeasurementsPnl into gmMeasurementWidgets.py and did not copy all stuff from gmSoapWidgets. What I effectively missed is to copy all the needed functions like _populate_with_data().

What this effectively shows is that I need to get a better idea on what to put where. Up till now it has mainly been a process of duplicating content of other files.

One issue is if it makes sense to put more stuff into gmMeasurementWidgets or rather a seperate file along the lines of gmDeviceAssessmentWidgets.

From the feedback received on the mailing list it is recommended to leave it in gmMeasurementWidgets and import the relevant stuff from gmNarrativeWidgets in order to avoid code duplication.

It was further recommended to rename cCardiacDeviceMeasurementsPnl into cCardiacDevicePluginPnl to avoid namespace confusion for the day I really want a class representing the measurement specific bits.

Wednesday, December 14, 2011

GNUmed 1.2 in the works

GNUmed 1.2 is under heavy development. Among many other features it will sport a brand new patient overview plugin.

 

Friday, June 25, 2010

GNUmed web interface - when easy is not so easy after all

There is two sorts of user interfaces for FOSS EMR. There are the fat client applications
and the web client applications.

GNUmed is a fat client application. It uses wxpython and has an interface like many
traditional software applications. That means it needs to be installed on a user's computer.
This has pros and cons. On the positive side developers need to code exactly one interface.
The actual redering is left to the operating system and underlying GUI toolkit. However
providing the underlying toolkit on many operating systems and window managers is no
easy task.

Then the internet era came along and brought the browser. There was one rendering
application (the browser) and one user interface language (html). It turned out that was
not reality. There are dozens of browsers and because html alone is limited along came
CSS and JavaScript. But there was one very important detail that makes the web attractive
for delivering applications. All you need is a browser. You do not need to install the application
on your computer and you don't need to mess with dependencies such as special libraries.
In the ideal world you make your application run on on computer (the server) and the clients
(the browser) will only display the output and collect input to feed back to the server.

Web clients have their own share of problems. Browsers are not fully standard compliant. For
security reasons you cannot access local peripheral devices such as scanners, printers, files
easily. That severly breaks a input-oriented application like an electronic medical record.

People came up with all sorts of clever solutions. Thin client are fat clients whose output is
displayed at a remote location. This does not sound too bad. However the data that needs
to be transfered is too much for today's internet lines and even broadband. VNC is only usable
when run in a highspeed local network of 100MBit/s or more. And people tried to solve that
problem as well. They came up with the NX protocol. Pretty much a heavily optimized remote
display solution. It works and it works well but the web is so prominent that it does not
penetrate the mass market.

NX solves the problems for the user. Well kind of. You still need to install a NX client on your
computer. Then NX married the solution with the browser. They built the nx browser plugin.
This is one solution to the problem. However the nx client needs to be available for every
platform and every device (ARM, x86, your favorite operating system here). It is not. But the
web is. Virtually every device that looks like an electronic device can run a browser. Even the
washing machine and the microwave oven have Android installed. Anywhere there is a browser
there is chance to deliver the application.

Does GNUmed need a webinterface. I don't think so. However people are made to believe that
the personal computer will go away in 2-5 years. Along with it there is a chance that fat clients
will go away. That would make the GNUmed client we have today go away. The doctor does not
care. She just wants it to work not matter what technology. Lets just say GNUmed needs a web
interface. Apart from the fact that then GNUmed team does not yet have anyone with the skills
needed to make this a success it is always a good idea to look at what others have done so
far.

Web interface definition
There is two types of web interfaces. One is the traditional mix of HMTL pages maybe with a bit
of CSS for beatification and some Javascript to make it look. The other one is a so called RIA
(rich internet application). This is what Gmail and friends think a web application should work
and look like. Because the mix of browsers and OSs is such as PITA frameworks have been created.
Those frameworks try to abstract the pitfalls from the developer and try to offer a cross-browser
and cross-platform user interface developement solution. This has been benefical but developers
tend to push the boundries. This lead to countless approaches and frameworks. Many of those
target Javascript developers (extJS, dojo, qooxdoo, YUI, jQuery). Then Google came along and
brought GWT which let developers develop in JAVA instead of pure JavaScript. This however means
you
need to program in JAVA or at least php.

Choosing the right tools
Over the years it became evident that developers seldomly have design skills.
Application design was pushed down on the agenda and a few webpages were created to for
the EMR user interface. Pretty soon everyone noticed that those "applications" are a  nightmare
to develop, maintain, translate and debug. Users broke them all the time. Not because users
are stupid but because they are busy and impatient. The result is what openEMR, Oscar and
Freemed (up to 0.8.4) look like today. Design says little about how well an application works
but users tend to associate the quality of an application with its design.

Road to redesign
Nearly all projects I followed over the years have either done a complete user rewrite or plan to
do so. Freemed has been picked up but even the rewrite is under rewrite. They tried Dojo

but that did not work so they switched to GWT. The web application now works as a mix of
three languages (PHP, Javascript, JAVA). The interface looks promising and I hope they will
pull this off. OpenEMR is currently getting certified in the US. I wonder how long it takes before
the UI will be rewritten.

Back to GNUmed.
While the appearance of openEMR and Freemed might be lacking GNUmed
is not there yet. It has recently been demonstrated that the GNUmed backend and middleware
(connection to the database etc.) can be reused without much effort in a webbrowser. The
only thing missing it the graphical user interface. It is not as easy as it sounds. Careful
planning is indicated to avoid starting over a few months down the road. A python based
web application usually uses a python web framework anlong with some template manager.
For python there is at least pylons, django, turbogears, cherrypy. These frameworks try to
be complete solutions. They bring everything to the table including database access. But we
don't want that. So the question arises do we need a python framework ? Given my little
knowledge I am not sure about that. Next question is on the user interface framework. Do we
need a Javascript framework like extJS , GWT , qooxdoo and friends. I am starting to believe
we could use one of those although I am not sure how to marry this to our middleware. The
point is unless you have a very experienced web application developer in the team chances are
this gets screwed up and wastes time.

Pyjamas to the rescue.
A group of people thought what GWT is to JAVA GWT can be to Python as well. They came up
with pyjamas. The ideas is to develop in python and the python-to-javascript compiler will
translate this to well JavaScript. JavaScript is the only language browsers understand. There
is documentation on this and it is supposedly relatively compatible to GWT. I lack the insight
to judge how well this works. The potential benefit is a closer tie to the python middleware.
A potential drawback is that you need a python coder to do the job. This will leave out all
web developers which are only fluent in JavaScript. And those are the majority I would guess.

Performance considerations
While reading up a lot on web application development I noticed there are a number of ways
to solve the problem. There is no best of practice and depending on what you wanna do
opinions differ. It boils down to where to do the heavy lifting. One end of the rope is creating
all (x)HTML on the server and serving that to the client. The other end is letting the client to all
the HTML,JS,DOM work.  Since I know too little on this here is an article which talks about it.

Long story short
Doing a web interface is easy. Just start and learn along the way. The same attitude is inherent in
some of the FOSS EMR both with web clients and fat clients. While it works it certainly is a pain
if you ever want to hand over the code to another developer.

What are the options.
One could look into pyjamas and try to make the most of it. Or one could just learn JS and go for
extJS and friends. I guess no option would be to use pure GWT since there needs to be a bridge
between python and JAVA (Jython maybe?). One could also just do away with the RIA as a whole
and serve a few static or partly dynamic webpages.

I would really welcome some comments here. As I said I am totally new to this. However if this
is not properly planned it is better to leave this out. And who knows how the internet will develop.
There might just be wxwidgets to javascript mapping appearing.