ejeschke / ginga

The Ginga astronomical FITS file viewer
BSD 3-Clause "New" or "Revised" License
121 stars 77 forks source link

How to access raw image data in Ginga? #70

Closed pllim closed 9 years ago

pllim commented 9 years ago

I wish to use Ginga for data visualization, but at the same time, I also need to modify the data when user interacts with the GUI. For example, user clicks on a bad pixel on the image display and select "fix bad pixel", and my program will apply some kind of correction algorithm, and display the corrected data on screen. Once user is satisfied, user can choose to save the modified data to a new file without changing the input image. Is this possible with Ginga architecture? If so, do I have to write my own basic rendering class in a new program (as mentioned in your docs) or can I simply use plugins? Your advise would be greatly appreciated. Thank you.

ejeschke commented 9 years ago

@pllim, this should be quite straightforward in ginga. If you are reasonably happy with the reference viewer then I would just write a plugin--it would not take long at all. Under ginga, data is extracted from the file using either astropy or fitsio as a numpy array and placed in a AstroImage object--this is the model in the MVC architecture of ginga. You can fetch the model of the currently viewed object by a call like

image = self.fitsimage.get_image()
data_np = image.get_data()

(due to a historical reason, most plugins refer to the viewer (of MVC) as self.fitsimage)

You could then either directly modify the data and then call for a redraw in the viewer (more efficient), or create a new AstroImage object and set it into the viewer. You would need to take care of writing out the new file yourself when they press some "save" button or some such.

ejeschke commented 9 years ago

Take a look at a few of the existing plugins and read the relevant part of the manual on readthedocs.org and you will have a good sense of how to proceed.

pllim commented 9 years ago

Thanks. I might have missed it, but it's not clear to me how plugins talk to one another. For example: I have a global plugin that lists the opened images with checkboxes and a "save" button, and I have a local plugin that does the bad pixel correction. When I correct for bad pixels on 2 of the 5 opened images, I want my global listing to have a "modified" status on those 2 images, so I would know which ones to save. Is this possible too? If you already documented it somewhere, you can just give me the link here. Thank you very much for your time.

ejeschke commented 9 years ago

Hi @pllim, The usual way for plugins to find out about each other is via the reference viewer "shell". For historical reasons, this is bound to self.fv in the plugins. So you would probably do something like

# image is modified, notify global plugin 'Listing'
list_plugin_obj = self.fv.gpmon.getPlugin('Listing')
list_plugin_obj.set_modified_status(image)

where set_modified_status() is a method you have defined in your global plugin.

It's a little hacky that you have to access the global plugin manager via the gpmon variable, that's something I might fix later with a special call to fetch it. But there you have it.

pllim commented 9 years ago

@ejeschke , I have another question related to plugins. Your MyGlobalPlugin example works fine, but it stops working the moment I change the class name to something else (I tried ContentsManager and BlahBlah). I changed it in 3 different places in your example:

class BlahBlah(GingaPlugin.GlobalPlugin):
    def __init__(self, fv):
        super(BlahBlah, self).__init__(fv)
        ....
    def __str__(self):
        return 'blahblah'

And I changed my command line to:

ginga --loglevel=20 -log=ginga.log --modules=BlahBlah --toolkit-pyside

This is the log file:

2015-02-02 14:22:34,220 | W | main.py:298 (main) | failed to set WCS package preference: 
2015-02-02 14:22:34,220 | W | main.py:310 (main) | failed to set FITS package preference: 
2015-02-02 14:22:34,263 | E | main.py:339 (main) | Error importing Ginga config file: No module named ginga_config
2015-02-02 14:22:34,264 | E | main.py:340 (main) | Traceback:
  File ".../lib/python2.7/site-packages/ginga-2.1.20141220052723-py2.7.egg/ginga/main.py", line 327, in main
    import ginga_config

2015-02-02 14:22:34,397 | I | ModuleManager.py:40 (loadModule) | Loading module 'Toolbar'...
2015-02-02 14:22:34,404 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Toolbar' loaded.
2015-02-02 14:22:34,485 | I | ModuleManager.py:40 (loadModule) | Loading module 'Pan'...
2015-02-02 14:22:34,492 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Pan' loaded.
2015-02-02 14:22:34,498 | I | ModuleManager.py:40 (loadModule) | Loading module 'Info'...
2015-02-02 14:22:34,505 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Info' loaded.
2015-02-02 14:22:34,510 | I | ModuleManager.py:40 (loadModule) | Loading module 'Header'...
2015-02-02 14:22:34,512 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Header' loaded.
2015-02-02 14:22:34,519 | I | ModuleManager.py:40 (loadModule) | Loading module 'Zoom'...
2015-02-02 14:22:34,525 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Zoom' loaded.
2015-02-02 14:22:34,581 | I | ModuleManager.py:40 (loadModule) | Loading module 'Thumbs'...
2015-02-02 14:22:34,587 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Thumbs' loaded.
2015-02-02 14:22:34,597 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_none'
2015-02-02 14:22:34,598 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_wheel'
2015-02-02 14:22:34,600 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_draw'
2015-02-02 14:22:34,601 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'kp_poly_add'
2015-02-02 14:22:34,603 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'kp_poly_del'
2015-02-02 14:22:34,604 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_cursor'
2015-02-02 14:22:34,605 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'kp_edit_del'
2015-02-02 14:22:34,606 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_edit'
2015-02-02 14:22:34,608 | I | ImageView.py:283 (set_window_size) | widget resized to 200x200
2015-02-02 14:22:34,630 | I | ModuleManager.py:40 (loadModule) | Loading module 'Contents'...
2015-02-02 14:22:34,632 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Contents' loaded.
2015-02-02 14:22:34,645 | I | ModuleManager.py:40 (loadModule) | Loading module 'WBrowser'...
2015-02-02 14:22:34,647 | I | PluginManager.py:67 (loadPlugin) | Plugin 'WBrowser' loaded.
2015-02-02 14:22:34,649 | I | ModuleManager.py:40 (loadModule) | Loading module 'Errors'...
2015-02-02 14:22:34,651 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Errors' loaded.
2015-02-02 14:22:34,670 | I | ModuleManager.py:40 (loadModule) | Loading module 'RC'...
2015-02-02 14:22:34,750 | I | PluginManager.py:67 (loadPlugin) | Plugin 'RC' loaded.
2015-02-02 14:22:34,752 | I | ModuleManager.py:40 (loadModule) | Loading module 'SAMP'...
2015-02-02 14:22:34,859 | I | PluginManager.py:67 (loadPlugin) | Plugin 'SAMP' loaded.
2015-02-02 14:22:34,860 | I | ModuleManager.py:40 (loadModule) | Loading module 'IRAF'...
2015-02-02 14:22:34,872 | I | PluginManager.py:67 (loadPlugin) | Plugin 'IRAF' loaded.
2015-02-02 14:22:34,873 | I | ModuleManager.py:40 (loadModule) | Loading module 'Log'...
2015-02-02 14:22:34,878 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Log' loaded.
2015-02-02 14:22:34,880 | I | ModuleManager.py:40 (loadModule) | Loading module 'Debug'...
2015-02-02 14:22:34,886 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Debug' loaded.
2015-02-02 14:22:34,887 | I | ModuleManager.py:40 (loadModule) | Loading module 'BlahBlah'...
2015-02-02 14:22:34,899 | E | ModuleManager.py:47 (loadModule) | Failed to load module 'BlahBlah': No module named BlahBlah
2015-02-02 14:22:34,901 | E | Control.py:391 (add_global_plugin) | Unable to load global plugin 'BlahBlah': No module named BlahBlah
2015-02-02 14:22:34,987 | I | ModuleManager.py:40 (loadModule) | Loading module 'Pick'...
2015-02-02 14:22:35,406 | I | ModuleManager.py:40 (loadModule) | Loading module 'Ruler'...
2015-02-02 14:22:35,412 | I | ModuleManager.py:40 (loadModule) | Loading module 'MultiDim'...
2015-02-02 14:22:35,419 | I | ModuleManager.py:40 (loadModule) | Loading module 'Cuts'...
2015-02-02 14:22:35,425 | I | ModuleManager.py:40 (loadModule) | Loading module 'Histogram'...
2015-02-02 14:22:35,430 | I | ModuleManager.py:40 (loadModule) | Loading module 'Overlays'...
2015-02-02 14:22:35,436 | I | ModuleManager.py:40 (loadModule) | Loading module 'PixTable'...
2015-02-02 14:22:35,442 | I | ModuleManager.py:40 (loadModule) | Loading module 'Preferences'...
2015-02-02 14:22:35,448 | I | ModuleManager.py:40 (loadModule) | Loading module 'Catalogs'...
2015-02-02 14:22:35,453 | I | ModuleManager.py:40 (loadModule) | Loading module 'Mosaic'...
2015-02-02 14:22:35,462 | I | ModuleManager.py:40 (loadModule) | Loading module 'Drawing'...
2015-02-02 14:22:35,468 | I | ModuleManager.py:40 (loadModule) | Loading module 'FBrowser'...
2015-02-02 14:22:35,701 | W | Control.py:946 (add_channel) | no saved preferences found for channel 'Image': Error opening settings file (.../.ginga/channel_Image.cfg): [Errno 2] No such file or directory: '.../.ginga/channel_Image.cfg'
2015-02-02 14:22:35,709 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_none'
2015-02-02 14:22:35,711 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_wheel'
2015-02-02 14:22:35,713 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_draw'
2015-02-02 14:22:35,714 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'kp_poly_add'
2015-02-02 14:22:35,716 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'kp_poly_del'
2015-02-02 14:22:35,717 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_cursor'
2015-02-02 14:22:35,718 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'kp_edit_del'
2015-02-02 14:22:35,720 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_edit'
2015-02-02 14:22:35,725 | I | ImageView.py:283 (set_window_size) | widget resized to 736x737
2015-02-02 14:22:35,726 | I | ImageView.py:283 (set_window_size) | widget resized to 736x737
2015-02-02 14:22:35,731 | I | ImageView.py:283 (set_window_size) | widget resized to 736x715
2015-02-02 14:22:35,790 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Pick' loaded.
2015-02-02 14:22:35,792 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Catalogs' loaded.
2015-02-02 14:22:35,793 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Preferences' loaded.
2015-02-02 14:22:35,795 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Ruler' loaded.
2015-02-02 14:22:35,796 | I | PluginManager.py:67 (loadPlugin) | Plugin 'MultiDim' loaded.
2015-02-02 14:22:35,798 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Histogram' loaded.
2015-02-02 14:22:35,799 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Cuts' loaded.
2015-02-02 14:22:35,801 | I | PluginManager.py:67 (loadPlugin) | Plugin 'PixTable' loaded.
2015-02-02 14:22:35,804 | I | PluginManager.py:67 (loadPlugin) | Plugin 'FBrowser' loaded.
2015-02-02 14:22:35,806 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Overlays' loaded.
2015-02-02 14:22:35,807 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Drawing' loaded.
2015-02-02 14:22:35,809 | I | PluginManager.py:67 (loadPlugin) | Plugin 'Mosaic' loaded.
2015-02-02 14:22:35,817 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_none'
2015-02-02 14:22:35,818 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_wheel'
2015-02-02 14:22:35,820 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_draw'
2015-02-02 14:22:35,821 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'kp_poly_add'
2015-02-02 14:22:35,822 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'kp_poly_del'
2015-02-02 14:22:35,824 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_cursor'
2015-02-02 14:22:35,825 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'kp_edit_del'
2015-02-02 14:22:35,826 | W | Bindings.py:244 (setup_settings_events) | No method found matching 'ms_edit'
2015-02-02 14:22:35,828 | I | ImageView.py:283 (set_window_size) | widget resized to 300x300
2015-02-02 14:22:35,832 | I | ImageView.py:283 (set_window_size) | widget resized to 315x316
2015-02-02 14:22:35,834 | I | ImageView.py:283 (set_window_size) | widget resized to 315x316
2015-02-02 14:22:36,132 | E | main.py:405 (main) | Error processing Ginga config file: local variable 'ginga_config' referenced before assignment
2015-02-02 14:22:36,134 | E | main.py:406 (main) | Traceback:
  File ".../lib/python2.7/site-packages/ginga-2.1.20141220052723-py2.7.egg/ginga/main.py", line 395, in main
    ginga_config.post_gui_config(ginga)

2015-02-02 14:22:36,136 | I | main.py:428 (main) | Entering mainloop...

Things work again when I change the plugin class name back to MyGlobalPlugin. Does this mean that I can only name my custom plugin MyGlobalPlugin? I kinda want to name it in a more useful way for my users. Am I missing something?

pllim commented 9 years ago

@ejeschke , never mind... Right after I posted above, I found out that MyGlobalPlugin came built-in with Ginga. I did not realize that I can only have one .py file per plugin and it has to have the same name as the plugin class.

ejeschke commented 9 years ago

There can be multiple plugins per file. As you've found the global and local plugins tables, poke around those a bit and you can see that they specify the module and class names for the plugin. However, as you've noted, the default for loading them via the command line is one plugin per module with the same class name, module name, file name.

pllim commented 9 years ago

Is there another way to load custom plugins that is not done from command line? I only see one way to do it from the documentation.

pllim commented 9 years ago

@ejeschke , I have a follow-up question about cross-plugin communications. In your suggestion:

# image is modified, notify global plugin 'Listing'
list_plugin_obj = self.fv.gpmon.getPlugin('Listing')
list_plugin_obj.set_modified_status(image)

I actually need

list_plugin_obj.set_modified_status(chname, image)

How do I extract the active chname from fv or fitsimage?

pllim commented 9 years ago

Never mind, I found chname being set in one of your examples. :o)

pllim commented 9 years ago

I think the original issue is resolved. Thanks!