4. Complete Interface: Extending Base Classes and Default Implementations
5. Class Hierarchy and Documentation
PyDVT is a Python toolkit for data displaying applications designed by BLISS group to be a tool for ESRF new developments in on-line acquisition and off-line analysis of scientific data. As Python was defined the standard language for new development in the group, and several applications require similar features for data displaying, the idea of having a set of common base classes showed up to be a way to standardize graphics functionality. In addiction, the package should offer a very easy to use interface that allows fast development of customized beamline software, and make it easy to translate older generic graphical applications into Python.
Therefore, the requirements of PyDVT project were:
PyDVT contais a set of virtual classes to define the standard way of interconnecting generic data representation classes and data displaying widgets:
Base class for data storage, provides methods for adding and removing data (represented by NumPy arrays), information about data (represented by Python dictionaries) and for communicating with other classes. Applications can work with Data base class by retrieving NumPy arrays by themselves, or work with a derived class implemented for convenience. Derived classes implement easy access to a specific data source (a file format, shared memory, data server, …). PyDVT package contains Data derived classes for the most usual data sources at the ESRF, but new ones can be coded if needed.
View is a virtual class that represents a generic widget for data displaying. The base class provides the interface for communication with linked objects, and the application. Derived classes implement the drawing functionality.
Filters are cascaded objects placed between Data and View objects. The base class implements the interface for connection to an Input (Data or another Filter) and an Output (View or another Filter). Derived classes code any operation on data as a selection on the input data, numeric transformation or coordinate translation.
DataSelection is a Filter that have a Data object as input, and implement a selection over the data, typically by generating an n-dimension array, coherent with the display widget.
Class with Filter-like interface that supports multiple Inputs.
This class implements a standard way to create graphical selections over View objects through mouse and keyboard events. ViewSelect objects can be connected to multiple views, and generate events once a selection has be defined, in order to the application to retrieve the selected data.
The interaction between these objects and the application can be summarized in the following data flow model:
This diagram represents one application that in a particular moment contains:
Through this diagram we can also enumerate some concepts of PyDVT:
The GUI Binding implements the GUI toolkit specific behavior through a defined interface. It's accessed at application level through Binding module. There's two ways to resolve which binding is going to be loaded.
import PyDVT
PyDVT. SetBinding("my_binding")
Where my_binding is the name of an installed binding (as QwtBinding) or a locally defined one.
See DemoQwtBinding.py for an example on loading QwtBinding (QwtBinding is the same as QtBinding but defines a 1D plotting widget based on PyQwt, while QtBinding's 1D widget is based on PLplot).
The Simplified Interface allows using PyDVT package without minding its data model (Data/DataSelection/Filter objects). It is oriented to the View object itself, by working directly with NumPy arrays. To do so, view derived classes define a filter that encapsulate in itself its own Data/DataSelection/Filter objects, hiding them from the application.
If working in this way, the only references the developer needs are (see table below):
The demo files for each View object is an example of using the Simplified Interface:
View Class |
Simplified Interface Filter |
Demo File |
These demo files for View classes can be executed either using PyQt or Tkinter toolkits. By default the demos use PyQt. Tkinter is used instead if the parameter Tk is added as a command line argument.
GraphView defines other two Simplified Interface Filters:
Examples of their use can be seen in DemoHistogramQt.py and DemoPolarQt.py.
When coding this way, the only interfaces the developer has to mind is the View object itself, and the Simplified Interface Filter. Optionally, ViewSelect objects can also be connected to the View objects, and the Simplified Interface Filter can be also cascaded to other filters.
For the standard data sources, however, it is probably easier to use the standard Data objects and the Complete Interface, and for new data sources it would probably be better to code a new Data object for it, that can be reused easily. The same about any data transformation, if it is implemented through a Filter it can be easily reused.
It is very easy to work with the Simplified Interface (and it can be a good choice for simpler cases), but lots of functionality is lost:
4. Complete Interface: Extending Base Classes and Standard Implementations
The following picture shows the relation between PyDis base classes objects, their interface methods, and the virtual methods to be coded by derived classes.
Legend:
All PyDVT objects have an EventHandler property (eh) used internally for communication with the other connected objects. There's no need to see these EventHandler at application level, since all classes objects provide interface to report events, either by overriding methods, or by callback functions. The EventHandler objects aren’t seen in the scope of derived classes neither; its functionality is encapsulated in the base classes. However, if the application (or a derived class) wants to keep track of the internal events of the objects, it can access the "eh" property and register events. See the class documentation to see which are the events sent through its EventHandler.
View and ViewSelect are virtual classes, but Filter, DataSelection and Data base classes can be instantiated:
"Standard implementation" (or class) is how it is called a View, Data, Filter or ViewSelect derived class present in PyDVT package.
The following picture presents the standard View derived classes, and their interface methods in addiction to View's methods:
View class contains a generic interface for data displaying widgets, independent from the GUI toolkit. It is derived from Container class, and creates a Drawable object, through which makes the drawing. Container and Drawable classes are defined in PyDVT.Binding and hence they are GUI toolkit dependent.
The base class is abstract, and derived classes should code some abstract methods to add functionality:
Derived classes should always call View's constructor from its own. Some initialization options are considered by View base class: "ZoomMode", "ScrollMode", "AddOnLine", "AddLockPosition", "AddRefresh", "DataChangedCallback" (see View.__init__).
Plots 2d data as an image. ImageView expects from its source a python string containing the RGBX representation of the image, the size, the depth, and, in case the depth is 8, a colormap table. ColormapFilter implements a filter that transforms a 2d python array into the format expected by ImageView. ColormapFilter provides methods to change the colormap generation parameters. ImageFilter is used to interface data read from an image file (jpg,bmp,gif), already an RGB representation, into the correct output string to ImageView. As ImageView considers just one source Filter, SetSource should be called with one element, otherwise only the first one is considered for drawing. Scroll management: The option "ScrollMode" in constructor parameters sets whether the scrollbars are absent, always present, or just shown if image contents are bigger than displayable area. Zoom management: If option "ZoomMode" in constructor parameters is set to "ON", zoom-related entries are added to View's menu. If set to "FIT_TO_SCREEN", the image is always resized to fit the size of the widget. View's LockPosition method control behavior about positioning of the window after a change in its data. Zoom can be set by code through a call to SetZoom. The method ZoomTo creates a ViewSelect object to define graphically a region to zoom to. This ViewSelect object can be cleared by calling ClearZoomSelect. Cursor management: the cursor is the ViewSelect object that keeps track of the position of the mouse. It can be set by SetCursorType to "Crosshairs" or "None" (default, creates an invisible ViewSelect). Event menagement: (in addition to View class) EventPosition overridable method reports the cursor position event (receives the cursor object, an ViewSelect object. The position can be retrieved with cursor.GetSelection()["Position"]. Changing Colormap: not done in ImageView itself, but on the ColormapFilter. As filters can be cascaded in free order, application can retrieve the ColormapFilter object linked to the ImageView object by calling GetColormapFilter. Retrieving Pixel Data Values: events are reported in Data and Image coordinates. Depending on the situation, the application should use one or another. To work in Image coordinates (to retrieve pixel values by calling GetImagePositionValue) the option "StoreImageData" in constructor parameters should be set to one. In this way the image data is buffered in addiction to the image colormap transformed string. By default image data is not buffered and GetDataPositionValue should be used instead. In most cases there's no difference in using one or another, since both coordinated are passed in events) and the default behavior (just working with data coordinates) is better because saves memory and allow multiple views to work based on the same coordinate system. Sometimes, however, "StoreImageData" should be set, when displaying transformed data by a filter or an operator. If a filter just changes the coordinate system (and not the data itself) both approaches can be used, but to use data coordinates, this filter should code DataCoord2SelectionCoord and SelectionCoord2DataCoord methods. Another way to deal with retrieving data from transformed filter would be not setting "StoreImageData", and accessing the data by its filter (or DataSelection) itself. If using ExtendedImageView with "UseImageValues" " in constructor parameters set, "StoreImageData" is forced to one. |
|
Extends ImageView functionality: Includes status bar displaying mouse position and pixel data value (this can be removed by setting "AddStatus" constructor parameter to zero). Easy colormap menagement (includes methods and menu options to configure linked ColormapFilter object). By default includes menu option to open a dialog for colormap editing. Can be removed by setting "AddColormap" constructor parameter to zero. If "UseColormapWidget" constructor parameter is set to zero, controls the colormap through the popup menu instead of the dialog. If "AddReduc" is set to one adds a menu entry to set a reduction in the ColormapFilter. ExtendedImageView also includes SetColormap, SetScale and HistColormapWidget methods for convenience (the last one opens a histogram dialog to select the limits of the colormap on it). Easy management of a single ViewSelect object through CreateViewSelect, GetViewSelect and ClearViewSelect methods. EventSelection method can be overridden to receive the selection event, or a callback method can be configured by the "SelectionCallback" constructor parameter. By default adds standard selections in the popup menu. To avoid it set "AddSelection" constructor parameter to zero. If "AddCursorSelect" constructor parameter is set to one, adds menu entry to change cursor type If "UseImageValues" constructor parameter is set to one forces "StoreImageData" parameter and displays pixel data values based on Image coordinates. Set this option if the ExtendedImageView object uses the default status bar and displays data transformed by a Filter or an Operator. If the application controls the displaying of values by itself (the status bar is removed by setting "AddStatus" constructor parameter to zero) it is not needed to set this option, since the application can read the output from the DataSelection of Filter itself. |
|
Plots 1d arrays on x-y graphics. Supports polar coordinates, logarithmic scale for x and y axis, a second referencing axis (y2) and more. Each source set with SetSource is plotted as a function. Expects "data" and "xdata" keys from its source as two 1d arrays defining y and x coordinates for each point. GraphFilter is defined to format the data to the input expected by GraphView. GraphFilter allows to set the pen, symbol and to which y axis the function is related to (these parameters can be set by calls to GraphView methods too). Scroll management: The option "ScrollMode" in constructor parameters sets whether the scrollbars are absent, always present, or just shown if graph is zoomed. Zoom management: If option "ZoomMode" in constructor parameters is set to "ON", zoom controlling entries are added to View's menu. View's LockPosition method control behavior for the positioning of the window after a change in its data. Zoom can be set by code with SetZoom and ResetZoom. The method ZoomTo creates a ViewSelect object to define graphically a region to zoom to (this is called from menu's Zoom To entry). Calling ClearZoomSelect clears this ViewSelect object. SetZoomStyle changes the pen and brush of zoom's ViewSelect object. Cursor management: the cursor is the ViewSelect object that keeps track of the position of the mouse. SetCursorType method can set it to "Crosshairs", "Vertical" or "None" (default, creates an invisible ViewSelect). If "AddCursorSelect" constructor parameter is set to one, adds menu entry to change cursor type Style management: SetStyle method allows choosing the plot style: "Line", "Points", "PointsLine" or "Bars". These apply to all displayed functions. To have control of them individually use SetPen, SetSymbol or set in GraphFilter's initialization parameters. If "AddStyleSelect" in constructor parameters is set, entries to configure style are added to the popup menu. SetXScaleLinear, SetXScaleLog , SetYScaleLinear and SetYScaleLog methods choose linear or logarithmic scales for x and y axis. By default the minimum and maximum values of the x, y and y2 axis are automatically defined based on the data. Each one can be independently fixed with the methods SetXAxis, SetYAxis and SetY2Axis. GetXAxis, GetYAxis and GetY2Axis return the minimum and maximum values for each axis. SetLabels method allows to set the title of the plot and labels for x,y and y2 axis. SetPen, SetSymbol and SetFunctionYScale methods change the color, symbol and the y referencing axis of a given function. For these methods the target function is identified by the parameter "name" of the corresponding GraphFilter object. In the case a Graph object is used instead, or any other filter as a source for GraphView, the name parameter shall be set to: str(filter_object). To use Polar coordinates SetPolarEnv method must be called. For the events report coordinates in a zero to one linear system, these coordinates are converted into polar with GetPolarCoords method. Two methods help derived classes (or the application) to add dinamic behavior to the widget. GetPositionValues returns a tuple containing the closest points in all functions for a given position. GetClosestFunction returns the closest function to a given position (if there's any). These are useful to implement answers to the mouse events that change the properties of functions. If option "AddStatus" in constructor parameters is set a status bar is added showing mouse position (for "None" or "Crosshairs" cursor) or function values(for "Vertical" cursor). If option " AutoHideStatus" is set the status bar is hidden when "None" cursor is selected. |
|
GraphView derived class, made by Nicolas Pascal to be used in Artemis application. Adds to GraphView ROI and Peak menagement, graphical selection and configuration of functions, including the concept of active function. |
|
GraphView derived class, it adds functionality to plot efficiently scans containing large number of points. GraphView replots the whole graphics for each update events. ScanGraphView plots just the new points added, hence it is much more appropriate if the plotted array is big and acquisition time small. To do so it receives from its source a key called "nopts" containing the number of valid points of x and y arrays. ScanGraphView just plots the new points added since last update. The SetAutoRescale method sets automatic rescaling when a scan point is added outside the boundaries of the graphic. If the scan is read from Spec's shared memory, the module SPSScanGraphFilter implements an appropriate filter to connect to ScanGraphView: SPSScanGraphFilter. It requires an SPSData as data object. For a plotted array "arr" in Spec, it should also be defined in Spec an array of environment variables "arr_ENV", created with SPS_CreateEnv, containing the variables "nopts, and, optionaly, "xmin" and "xmax" that, if present, set the x axis in the graph. See DemoScanQt.py. SPSScanGraphFilter also defines SPSCircularBufferFilter, a filter to linearize data read from a circular buffer, where a scan is being made. It can then be connected to an SPSScanGraphFilter. See DemoScanCircularBufferQt.py as an example and DemoScanCircularBufferQt.mac for a spec macro to generate a plotable scan to this demo. |
|
Plots 2d data arrays as a mesh plot. This widget can be connected to any filter that provides a "data" key in its output containing a 2d array (z). For convenience it is defined MeshFilter that allows including other optional information to the plotting as setitng x and y values and the color of the plot (the color can be set by MeshView's SetColor method too). The x and y values can be also received from the DataSelection (or other cascaded filter). If x and y data are not defined, they are set to the range from zero to the size on each dimension of "data". MeshView includes vertical and horizontal scrollbars to set the altitude and azimuth angles of the observer. Drawing style is set with SetStyle method. The available styles are "3dSurface" (plots a surface mesh showing just the top side), "SurfaceMesh" (plots a surface mesh showing both top and bottom side) and "ShadedSurface" (plots a 3d shaded surface plot). For "3dSurface" and "SurfaceMesh" styles the method SetMeshPlottingMode defines if the mesh is drawn as z=f(x) for each y, z=f(y) for each x or both. SetScaleLinear and SetScaleLog methods choose linear or logarithmic scale for z axis. By default the minimum and maximum values of the x, y and z axis are automatically defined based on the data. Each one can be independently fixed with the methods SetXAxis, SetYAxis, SetZAxis. SetLabels method allows to set the title of the plot and labels for x, y and z axis. SetPen changes the color of a given plot. Each plot is identified by the parameter name of the corresponding MeshFilter object. In case a Mesh object is used instead, or any filter other than MeshFilter, the name parameter in SetPen call should be set to str(filter_object). SetTrackScrollbars method chooses between executing a full redrawing for each scrollbar position (good for small arrays or fast machines), or just when mouse is released. By default MeshView adds entries in the popup menu to control style, scaling and scrollbars tracking. To customize the menu override CreateMenu method. |
|
Plots 2d data arrays as a contour and/or shading plot. This widget can be connected to any filter that provides an "data" key in its output containing a 2d array (z). For convenience it is defined ContourFilter that allows including other optional information to the plotting as seting x and y values (that can be also received from the DataSelection or other cascaded filter). As ContourView considers just one source Filter, SetSource should be called with just one element, otherwise just the first element is considered for drawing. This limitation exists because of the possibility of drawing shading plot, but DrawableContour class supports multiple contour drawings, so a View derived class can be written to support multiple superposed contour drawings (but not shading) using the same DrawableContour class. SetStyle selects the plot style: shade, contour or both. Contour plot properties are set by SetContourPen, ShowContourLabels and SetContourLevels methods. SetShadeLevels and SetShadeColormap methods set shade plot properties. SetScaleLinear and SetScaleLog methods choose linear or logarithmic scale for data. SetLabels method set the title of the plot and labels for x and y axis. Gridlines can be added end removed to the plot by ToggleGridlines method. By default ContourView adds entries in the popup menu to control style and scale. To customize the menu override CreateMenu method. |
|
Fills a table with 1d or 2d data arrays. This widget can be connected to any filter that provides an "data" key in its output containing an 1d (y) or 2d array (z). For convenience it is defined a TableFilter that allows including other optional information to the plotting as setting x and y values, which can be also received from the DataSelection (or other cascaded filter). Considers just one source Filter. 1d data is displayed in the first line. If xdata is present it is displayed in the horizontal header. 2d data is displayed as a table. If xdata is present it is displayed in the horizontal header. If ydata is present it is displayed in the vertical header. |
|
This widget displays information about the data object it's linked to. SetSource accepts as a source either a Filter or a Data object directly. Displays Data's global info dictionary and each Page's info dictionary. DataInfoView is implemented for debug purposes, but can be useful to some applications. For customized displaying of Data object information, the application can access these directly with Data's GetPageInfo and GetInfo methods. |
In order to work with Data base class, or to implement derived classes, we need to use Data interface methods (see Data).
Data objects contain a set of Page objects. A Page object contains a 1 to 3-dimension NumPy array and a dictionary with information about this array. Data objects contain also a global info dictionary. Application and derived class don't work with Page objects themselves, but through methods of Data interface that allow access to a specific page. There's two ways of indexing a page for the Data interface methods:
There are two kinds of method in Data class. The generic page oriented methods and a virtual interface based on three functions (SetSource, GetSourceInfo, LoadSource) that is a proposed way to derived classes to get data from a particular data source. It is appropriate, if possible, to keep this interface in order to have a standard way to access different kind of data, but derived classes can add new interface methods if they need to.
All standard Data derived classes use the same interface, the virtual interface proposed in Data class composed of the following methods:
Dinamic updating in Data derived classes is done by overriding RefreshPage method, and setting "refresh_interval" parameter in the constructor. It specifies, in milisseconds, the interval in which RefreshPage is going to be called. As an example see SPSData implementation.
Data extension to access EDF files. SetSource has the name of an EDF file as parameter. Since there's no standardization in the header parameters to identify a particular image, arrays in the EDF file are indexed by the position in file. The key for an array is an integer representing its position. LoadSource gets an index, array of indexes, or "ALL" as parameter (meaning loading the whole file). GetSourceInfo returns a list with the available indexes. A sub-array can be loaded by defining position and size of the slice in LoadSource. See DemoEdfQt.py for a simple example of use. |
|
Data extension to access image files (jpg, png, bmp, gif). SetSource has the name of an image file as parameter. LoadSource has no parameter. Image data is stored in a long array 2d containing the RGBX value for each pixel. To be displayed in ImageView, use ImageFilter. See DemoImageFileQt.py for a simple example of use. |
|
Data extension to access Spec shared memory. SetSource gets the name of the spec version. GetSourceInfo returns a list with all the available names of shared memory arrays. LoadSource is called with the name of array, a list of names, or "ALL" (loads all available arrays). A single row or column can be loaded instead of the full array. See DemoSPSQt.py for a simple example of use. |
|
Data extension to access Spec files. SetSource has the name of a Spec file as parameter. The key is a string, in one of the formats:
The first is for loading scan data, the last two a particular mca. GetSourceInfo returns a list of the strings, the available keys. LoadSource is called with one key string, a list of them, or "ALL" (all scan data is loaded). A single row or column can be read from scan data instead of the full array. To index a row or column in LoadSource we can use either its position or name. See DemoSpecFileQt.py for a simple example of use. |
|
Data extention to interface Scisoft's hierarchical data model (SciDXP). SetSource gets a node as parameter (usually the root). A key is a tuple of strings, containing the name of each group node up to the dataset node (from which information is going to be read from). GetSourceInfo returs a list of all key tuples available in "KeyList". LoadSource is called with a key tuple, a list of them, or "ALL" as parameter. See DemoSciDXPQt.py for a simple example of use. |
4.3. DataSelection Classes
Data objects have free and heterogeneous architecture, so DataSelection class is required to get a slice (or set of slices) producing plotable data. DataSelection definition is quite open itself, but a DataSelection is supposed to:
The default DataSelection implements orthogonal selections on the Data object. It is defined by the following parameters in the constructor:
This is enough for most of the cases, but this class doesn't define coordinates conversion, and as its syntax not practical for simpler cases, some standard derived classes are define to make it easy to use in particular situations (and to implement coordinates conversion):
2d selection on data. In this implementation, coordinates conversion works just for data read from a single page. To retrieve data read from multiple pages, use "StoreImageData" initialization option and GetImagePositionValue method on ImageView or "UseImageValues" initialization option on ExtendedImageView. |
|
RectSelection derived. It adds "xdata" and "ydata" keys in its output, generated based in position and size of the selection. This allows displaying widgets as MeshView or ContourView to plot automatically x and y axis. |
|
This RectSelection derived class allows ImageView objects to draw simultaneously on views displaying the same position on multiple pages. |
|
Implements an orthogonal line selection (either vertical or horizontal). |
|
OrthoLineSelection derived, specifies in addiction an 1d array to be used as xdata. |
|
A freely defined line selection inside a page. |
|
Single position selection. |
The Reconfig method allows changing position, size and index_list properties. Coordinate conversion is done overriding DataCoord2SelectionCoord and SelectionCoord2DataCoord methods. The action output is generating overriding GetOutput method. GetInfo method returns the relevant positioning properties, and can be overriden.
New DataSelection derived classes can be defined to manage more complex selection (other than orthogonal). Note DataSelection extends Filter. The difference in behavior is that DataSelection has a Data as input. As any filter, DataSelection provides a dictionary as output, when GetOutput is called. All standard classes use the same key names in the dictionary:
It is a good policy for derived classes to keep this standard to be able to be cascaded to the existing filters and Views.
4.4. Filter and Operator Classes
Filter objects are used to do any operation on that as:
Filters can be cascaded. A filter can have another Filter or DataSelection as input. A Filter can be the input to another Filter or View object.
The basic behavior of a filter is implemented overriding GetOutput method. At most cases this is the only method to be overridden. In this method input data is read, and the output is generated. The input is read with a call to GetInput, which returns a dictionary as received by the source. The way this is done depends on weather the filter is initialized with buffer_input option set or not. The default behavior (buffer_input=0) is GetInput to call directly the source's GetOutput method. Otherwise, data is buffered and it just calls the source if data has changed (a data change event has been received). SetSynchronized method defines if the filter answers or not to the source's data change events. Refresh makes the filter to generate a data change event, giving connected View/Filter objects opportunity to update.
In a data change event, the Filter calls by default Refresh to send the data change notification through. Derived classes can inspect source/input data by overriding DataChanged in order to decide if they should or not pass the event through.
With GetSource, GetData and GetDataSelection we can access the source, the Data object and the DataSelection object connected to.
If the Filter makes changes in coordinate system, and want the other objects and application to have a reference to the correspondent Data coordinates, it should override DataCoord2SelectionCoord and SelectionCoord 2DataCoord.
Operator class has an interface very similar to filters, and they behave the same, with the difference they have a list of sources instead of a single one (and therefore they can execute operations on them). Some of its methods also have an extra parameter to index a single source.
See DemoFilterQt.py and DemoFilterQt.py for simple examples of creating new Filter and Operator objects.
4.5. ViewSelect Classes
ViewSelect base class (virtual) defines a framework to implement graphical selections (mouse and keyboard driven) on a View object. ViewSelect objects are initialized with the parameters:
ViewSelect objects are connected/disconnected to View instances through ConnectView/DisconnectView methods. The connection can be done in INPUT_OUTPUT or OUTPUT mode, meaning, for each connected View, if the ViewSelect is going to answer events or just draw itself to.
A ViewSelect object can be connected to multiple Views, as a View can be connected to multiple ViewSelect at a time. Usually it is not appropriate having multiple ViewSelect objects on a View responding events simultaneously, so Disable/Enable/DisableKeyboard/EnableKeyboard methods control if each ViewSelect object answers to events at a time.
To create a ViewSelect derived class, the mouse/keyboard event methods should be overriden (KeyPress,Motion, PressMotion, Press, DoubleClick, Release). The standard implementations are not good examples of how to implement a derived class, for they access, for simplicity, internal properties, and inherit from ViewSelectDefault, which has some limitations. See DemoPolygonViewSelectQt.py instead for an example.
SetPen and SetBrush methods set the default pen and brush for drawings. Update/Erase/RemoveSelection control the state of the object. The application can call SetSelection to set the selection by code. Once a selection event has taken place, GetSelection returns a dictionary with the selection info (defined by each derived class, but "BoundingRect" key is used in all standard implementations).
5. Class Hierarchy and Documentation
PyDVT was developed at the ESRF by Alexandre Gobbo with valuable collaboration of Nicolas Pascal, Emmanuel Papillon, Gilles Berruyer, Jorg Klora, Armando Sole, Vicente Rey and Olof Svensson.
Several tools and libraries are used underneath:
And the home-made: