HistogramLUTItemAxisItemwith more than 1000 lines of code copy, Notably ViewBox, ImageItem and ViewBoxMenu are sort of BaseClasses of the pyQTGraph "FrameWork"
GradientEditorItem
ViewBox
ImageItem
ViewBoxMenu
--
You received this message because you are subscribed to the Google Groups "vispy-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vispy-dev+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/vispy-dev/a506dd99-39d2-4a07-9d50-245ee256b2a8%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
I think it worth saying that scientists are not programmers. Basic concepts like object oriented paradigm and even classes may be unknown in some community that make intensive use of superComputers. So you won't be surprise if I say to you that most of scientists do not know anything about ZCA or other framework for component based approach. But I do not want to criticize anyone, each community have its own history and its own goals..
Do you think that ZCA-like paradigm is radically different from OOP? It seems to me that ZCA only add (very useful and very efficient) mechanisms for class interoperability and reuse. Am I wrong? Would you suggest to rewrite vispy using ZCA?
3) Factories are the key to uncouple classes.
Everytime you write code like this:
@property
def data(self):
# Check if data was deleted due to the caching policy
if self._data == None:
logger.info( 'Reload table: %s' % self._params.url )
group_path, object_name, file_name = split_hdf_url(self._params.url)
nav = navigators.get_navigator(file_name)
self._data = retrieve_table(nav, group_path + '/' + object_name)
return self._data
6) Using the ZCA
The usage of the ZCA is simple, just use
'zope.interface'
'zope.component'
'zope.proxy'These packages have no external dependencies.This is interesting (I have definitely found subclassing to be an inconvenient mechanism for user customization).Can you give an example of something you would change in vispy, and how the API would look afterward?
from zope.component import getGlobalSiteManager gsm = getGlobalSiteManager() gsm.registerAdapter(Axis, (IColorBar,), IAxis)
from zope.component import getGlobalSiteManager gsm = getGlobalSiteManager() gsm.registerAdapter(NewAxis, (INewColorBar,), IAxis)
from zope.component import getGlobalSiteManager gsm = getGlobalSiteManager() gsm.registerAdapter(Axis, (IAxisData,), IAxis)
from zope.component import getGlobalSiteManager gsm = getGlobalSiteManager() gsm.registerAdapter(NewAxis, (INewAxisData,), IAxis)
The usage of the ZCA is simple, just use
'zope.interface'
'zope.component'
'zope.proxy'
These packages have no external dependencies.
[ snip ]
# registring the component Axis for the IAxisData componentfrom zope.component import getGlobalSiteManager gsm = getGlobalSiteManager() gsm.registerAdapter(NewAxis, (INewAxisData,), IAxis)
and voila! We have replaced the axis of the ColourBar without even subclassing it.
5) Handling of mass data. My experiance with mass data is that at a certain point you ran out of ram and have to swap data out of the interpreter.
It is sensible to implement such an usecase from the beginning. Since the garbage collector of the interpreter is picky its a very good idea never to store any reference to a numpy data array directly.
The solution for this problem is to wrap numpy arrays into containers.
The parts of pyqtgraph that users most frequently want to customize are (1) AxisItem--text and tick placement, (2) ViewBox--user input handling
In vispy we have already dealt with (2) by allowing different cameras to be attached to a ViewBox. I would be all in favor of doing the same for the axes--use a swappable component that determines the tick locations and text.
<volker....@inqbus.de> wrote:3) Factories are the key to uncouple classes.
Agreed (although I usually just let the class be its own factory). This has been on my list for pyqtgraph as well for a long time. Want to open an issue for this?
The usage of the ZCA is simple, just use
'zope.interface'
'zope.component'
'zope.proxy'
These packages have no external dependencies.
I suspect zope's framework is overkill for us. I'm pretty sure we can solve these issues without adding new dependencies.
[ snip ]
# registring the component Axis for the IAxisData component
from zope.component import getGlobalSiteManager gsm = getGlobalSiteManager() gsm.registerAdapter(NewAxis, (INewAxisData,), IAxis)
and voila! We have replaced the axis of the ColourBar without even subclassing it.
This seems like a rather complicated way of writing
# Swap the axis of one colorbarcolorbar.axis = my_axis
..or perhaps# Create new colorbar with my axiscolorbar = ColorBar(axis=my_axis)
..or even
# change the default axis class for ColorBarColorBar.axis_class = MyAxisClass
It's not clear to me why you suggest using the ZCA for this?
5) Handling of mass data. My experiance with mass data is that at a certain point you ran out of ram and have to swap data out of the interpreter.
It is sensible to implement such an usecase from the beginning. Since the garbage collector of the interpreter is picky its a very good idea never to store any reference to a numpy data array directly.
The solution for this problem is to wrap numpy arrays into containers.
Not sure that this applies in our case. Most of the time, we can upload data to the GPU and discard it on the CPU (this is not always done at present, but should be wherever possible (want to open an issue for this too?). It's definitely worth considering, though, for any visuals that require repeated access to the data on the CPU.
-- ========================================================= inqbus Scientific Computing Dr. Volker Jaenisch Richard-Strauss-Straße 1 +49(08861) 690 474 0 86956 Schongau-West http://www.inqbus.de =========================================================
Hi Luke!
Am 19.06.2015 um 14:56 schrieb Luke Campagnola:
That is from my point of view the right way to do it. The adapter patter is very good for such a thing.The parts of pyqtgraph that users most frequently want to customize are (1) AxisItem--text and tick placement, (2) ViewBox--user input handling
In vispy we have already dealt with (2) by allowing different cameras to be attached to a ViewBox. I would be all in favor of doing the same for the axes--use a swappable component that determines the tick locations and text.
Interludium:
I have not got the whole picture of vispy into my head. So please correct me if I am talking garbage. If I got it right, you are planning to render the axis and other graph and UI components in OpenGL and not only the drawing canvases?
If so a lot of classes have to be programmed to mimic a toolset, eventhandling, user interaction, etc. that has to be maintained beside the Visualisation and OpenGL code. I am asking myself if this is the right approach? May some sort oh hybride approach mixing QT and OpenGL canvases be a better option? Don't get me wrong I do not like to criticise the whole project - but this point looks critical to me. I am sure that I do not understand completly now.
You get me wrong. I have noticed that kinds of workaround in your PyQTgraph code on some locations.
[ snip ]
# registring the component Axis for the IAxisData component
from zope.component import getGlobalSiteManager gsm = getGlobalSiteManager() gsm.registerAdapter(NewAxis, (INewAxisData,), IAxis)
and voila! We have replaced the axis of the ColourBar without even subclassing it.
This seems like a rather complicated way of writing
And it is not the same. For all the three workarounds you present here you have to inherited a custom class from the ColorBar class.
And why should one derive a new class if only a subcomponent of this class has to be changed its class?
This is monkey patching.
# Swap the axis of one colorbarcolorbar.axis = my_axis
But remember I choose this example because of its simplicity - therefore are other simple solutions availabe.
My solution does not change the API, nor the constructor and no derived custom class is needed.
This is also monkey patching...or even
# change the default axis class for ColorBarColorBar.axis_class = MyAxisClass
On Fri, Jun 19, 2015 at 11:38 AM, Dr. Volker Jaenisch <volker....@inqbus.de> wrote:That's correct--everything is rendered in OpenGL, and we already have a complete UI event system that proxies events from various backends (Qt, GLFW, SDL, IPython, etc.). There are currently no plans to implement other GUI tools such as provided by Qt (and for now, I hope we do not venture too far down that path).
You get me wrong. I have noticed that kinds of workaround in your PyQTgraph code on some locations.
[ snip ]
# registring the component Axis for the IAxisData component
from zope.component import getGlobalSiteManager gsm = getGlobalSiteManager() gsm.registerAdapter(NewAxis, (INewAxisData,), IAxis)
and voila! We have replaced the axis of the ColourBar without even subclassing it.
This seems like a rather complicated way of writing
And it is not the same. For all the three workarounds you present here you have to inherited a custom class from the ColorBar class.
And why should one derive a new class if only a subcomponent of this class has to be changed its class?
I'm confused--none of my 3 examples involved making a subclass of ColorBar.
This is monkey patching.
# Swap the axis of one colorbarcolorbar.axis = my_axis
No; in this case `colorbar.axis` would be a property.
Monkey patching is used to:
But remember I choose this example because of its simplicity - therefore are other simple solutions availabe.
Well let's pick one that's complex enough to warrant the addition of a new component framework. So far I do not see the benefits..
I imagine a PlotWidget that includes an image in a ViewBox with axes, plus a colorbar with its own axis. The image, colorbar, and axes can all be swapped out by property assignment. Likewise the ViewBox's camera, or the individual tick-generators for the axes, or the colormap used on the image and the colorbar. There is no combinatorial API explosion here, just a collection of objects that can be swapped in and out.
My solution does not change the API, nor the constructor and no derived custom class is needed.
In that case the ColorBar would have already created its default AxisItem, only to delete it later when the new axis arrives. For any swappable component, I think it makes sense for that component to be specifiable at initialization time (either by making a subclass or with __init__ args).
This is also monkey patching...or even
# change the default axis class for ColorBarColorBar.axis_class = MyAxisClass
If the API docs say "this attribute may be reassigned to change the default axis class", then I don't see a problem with it. Of course the class would be written with this API in mind. (If you wanted to change _prior_ instances of the class, well, then that's a different problem)
Hi Luke!
* I am not your enemy!
* I am using your code. Without pypqgrapth I had never get the code for the LIDAR ready at time. Thank you very much!
* I have no intentions to debase the work you have done (for vispy).
* Sorry for using pyqtgraph code as a "bad" example in the recent discussions. After some probing: The current vispy code is very clean! So I was wrong at my first assumption to warn of bad design, reffering to you! Sorry for that.
I'm not upset--sorry if I came off that way. I am quite convinced that you are correct about the problems with using inheritance as a means of customization. Please DO use pyqtgraph (and vispy where appropriate) as examples of bad code; this is how we learn to make them better.The only thing I am not convinced about is whether the extra complexity of a system like ZCA is balanced by its benefits. I am not trying to fight you on this, I'm just asking you to convince me.
There are some base components: ViewBox, Axis, TickGenerator, Image.
These are used to build up a new component ImagePlot.
The user should do something like:
image = Image(data)
plot = ImagePlot(image)
and is delivered a pretty 2d plot of the image. The plot features a bottom X and a left Y axis as well as the image content.
I have abstracted this into dummy code: At the moment the application code does simply:
class PlotImage( BaseComponent):
"""
Makes a 2D plot for an image
"""
implements(IPlotImage)
def __init__(self, parent, image):
super(PlotImage, self).__init__(parent)
self.image = image
x_axis = IAxis(self)(orientation='bottom', start=0, end=8)
y_axis = IAxis(self)(orientation='left', start=-5, end=2)
self.vb = IViewBox(self, axis_items={'bottom': x_axis, 'left': y_axis })
self.vb.addItem( self.image )
def render(self):
return self.vb.render()
image = Image(None, None)
plot = PlotImage( None, image)
print( plot.render())
Output:
axis: bottom:0 1 2 3 4 5 6 7
axis: left:-5 -4 -3 -2 -1 0 1 2
Image: Rendered image
[ hg clone https://hg.inqbus.de/volker/graph_components
to obtain the python packages.]
As you can see I followed a bit your pyqtgraph approach from the user perspective. But this is not relevant for the underlying concept.
Also this code is far off from a coding style I would propose for building reusable components. One aim of this code is that it is
based on typicall OOP code constructs and therefore it is not easily extended.
I will show how this code can be used (without changing a line of it) to fullfill additional usecases the programmer has never intended by founding this code on component architecture. In fact the code is already base on ZCA as you can see by the constructors prefixed with an "I" but from a user perspective it is the same to write self.x_axis = Axis() or self.x_axis = IAxis(self) - it's just a command to generate an Axis instance.
*The usecases to change this code :* a) The user likes to modify some parameters on the components. e.g. the start value of the X axis shall now be -7. b) The Axis class of all axis used by PlotImage should be swapped to a custom Axis Class "NewAxis". c) The TickGenerator for the Y-Axis should be swapped to a new TickGenerator that is aware of the image type that goes into PlotImage. If the Image exposes the interface IQImage the tickLabes should be prefixed by a "Q". *The over all requirement is :* No changes on the apllication code. No changes on the components code. If the former is not possible the changes to the code should be as non invasive as possible.
All these usecases can be applied using the ZCA (code examples will follow tomorrow). But I have learned already that this additional code that manipulates the classes via ZCA is not straight foreward nor good nor easy to maintain.
You will surely mention at this point how easy it is to e.g. put axis_item as a parameter to the ImagePlot-Class's constructor (and modify some lines of code in it) to solve the usecases. But Imagine that in the ViewBox class the author has written code that gives the axis the tickgenerators by explicitly doing self.axis['bottom'].tick_labels = ITicksLabels(self). In this case you have to swap the ViewBox class as well. I have done lots of such uglyness to achieve class changes in the pyqtgraph code.
So in the end both alternatives
a) Using ZCA to have more points (In fact every I<something>() statement) to set the crank on to tweak the code
nor
b) inheritance with lot of copied code
are making me happy.
So even if the ZCA brings the possibility to change code that is not intended to be changed this is no sole concept a framework should be founded on. Therefore it takes more than just the use of ZCA. Additionally the framework has to follow some coding conventions and some base classes are needed to make the use of the ZCA more comfortable. Also the code changes to make a subcomponent swappable later on has to be minimal invasive (at best search and replace suffices).
Building these conventions and base structures is the work to be done tomorrow. If me and my coworker succeed we come up with structures to build components that are
* as easy to programm as with normal OOP (with slightly more boilerplate)
* powerful methods to manipulate the classes that are used through the whole framework. E.G. swap all Axis classes in several components at once or as specific as :In Component A -> Subcomponent AA -> Subsubcomponent AAA swap the use of Axis class to NewAxis class.
But currently we struggle with the stretch between being easy and being powerful at the same time. Both ends do not fit together.
Please stay tuned what tomorrow brings.
Cheers
Volker
Yes. And this is monkey patching.This is monkey patching.# Swap the axis of one colorbarcolorbar.axis = my_axis
No; in this case `colorbar.axis` would be a property.
"""
Monkey patching is used to:
"""
- Replace methods/attributes/functions at runtime, e.g. to stub out a function during testing;
https://en.wikipedia.org/wiki/Monkey_patch
Here starts the competiton.
What I like to address in my example code is the following:
There are some base components: ViewBox, Axis, TickGenerator, Image.
These are used to build up a new component ImagePlot.
The user should do something like:
image = Image(data)
plot = ImagePlot(image)
and is delivered a pretty 2d plot of the image. The plot features a bottom X and a left Y axis as well as the image content.
[snip]
The usecases is to change this code as a developer:
a) The user likes to modify some parameters on the components. e.g. the start value of the X axis shall now be -5.
b) The Axis class of all axis used by PlotImage should be swapped to a custom Axis Class "NewAxis".
c) The TickGenerator for the Y-Axis should to be swapped to a new TickGenerator that is aware of the image type that goes into PlotImage. If the Image is of Type Q (the former not kown property image.type equals "q") the tickLabes should be prefixed by a "Q".
All these usecases can be applied using the ZCA (code examples will follow tomorrow). But I have learned already that this additional code that manipulates the classes via ZCA is not straight foreward nor good nor easy to maintain.
You will surely mention at this point how easy it is to e.g. put axis_item as a parameter to the ImagePlot-Class's constructor (and modify some lines of code in it) to solve the usecases. But Imagine that in the ViewBox class the author has written code that gives the axis the tickgenerators by explicitly doing self.axis['bottom'].tick_labels = ITicksLabels(self). In this case you have to swap the ViewBox class as well. I have done lots of such uglyness to achieve class changes in the pyqtgraph code.
So in the end both alternatives
a) Using ZCA to have more points (In fact every I<something>() statement) to set the crank on to tweak the code
nor
b) inheritance with lot of copied code
are making me happy.
On Sat, Jun 20, 2015 at 9:02 PM, Dr. Volker Jaenisch <volker....@inqbus.de> wrote:Yes. And this is monkey patching.This is monkey patching.# Swap the axis of one colorbarcolorbar.axis = my_axis
No; in this case `colorbar.axis` would be a property.
"""
Monkey patching is used to:
"""
- Replace methods/attributes/functions at runtime, e.g. to stub out a function during testing;
https://en.wikipedia.org/wiki/Monkey_patch
Please read up on properties: https://docs.python.org/2/library/functions.html#property
Assigning a value to a property is equivalent to calling a `set_property()` method.
[snip]
The usecases is to change this code as a developer:
a) The user likes to modify some parameters on the components. e.g. the start value of the X axis shall now be -5.
plot.view.camera.rect = (-5, ...)
b) The Axis class of all axis used by PlotImage should be swapped to a custom Axis Class "NewAxis".
plot = ImagePlot(image, axis_class=MyAxis)
ORplot = ImagePlot(image, axes=[my_x_axis, my_y_axis])ORplot.x_axis = my_x_axisplot.y_axis = my_y_axis
c) The TickGenerator for the Y-Axis should to be swapped to a new TickGenerator that is aware of the image type that goes into PlotImage. If the Image is of Type Q (the former not kown property image.type equals "q") the tickLabes should be prefixed by a "Q".
plot.y_axis.ticker = my_ticker