Source code for image.gui.pixmap_view

# -*- python -*-
#
#       image.gui : spatial nd images
#
#       Copyright 2006 - 2011 INRIA - CIRAD - INRA
#
#       File author(s): Jerome Chopard <jerome.chopard@sophia.inria.fr>
#                       Eric Moscardi <eric.moscardi@gmail.com>
#
#       Distributed under the Cecill-C License.
#       See accompanying file LICENSE.txt or copy at
#       http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
#       OpenAlea WebSite : http://openalea.gforge.inria.fr
################################################################################

"""
This module provide a 2D QPixmap view on spatial images
"""

__license__= "Cecill-C"
__revision__=" $Id$ "

__all__ = ["PixmapView","PixmapStackView",
           "ScalableLabel","ScalableGraphicsView"]

def load_local(mod,modules):
    modules = modules.split()
    modules = ''.join(modules).split(',')

    for m in modules:
        globals()[m] = mod.__getattribute__(m)

from numpy import array,uint32, uint8
from openalea.vpltk.qt import QtCore, QtGui
load_local(QtCore,'Qt,SIGNAL')
load_local(QtGui,"""QImage,QPixmap,QTransform,QMatrix,
                         QLabel,QGraphicsView """)                         
                         
from pixmap import to_img, to_pix
from openalea.image.spatial_image import SpatialImage

[docs]class PixmapView (object) : """Base class for 2D views on spatial images Provide a QPixmap when asked to """ def __init__ (self, img = None, palette = None) : """Constructor :Parameters: - `img` (array) - a more than 3D array - `palette` (array of qRgba) - see set_palette """ self._palette = None self._img = None self._transform = 0 if palette is not None : self.set_palette(palette) if img is not None : self.set_image(img) ######################################## # # accessors # ########################################
[docs] def image (self) : """Get currently displayed image :Returns Type: `array` """ return self._img
[docs] def set_image (self, img) : """Set an image to display :Parameters: - `im` (array) """ self._img = img
[docs] def palette (self) : """Palette used to associate a color with info contains in a voxel :Returns Type: array of qRgba """ return self._palette
[docs] def set_palette (self, palette) : """Set the palette :Parameters: - `palette` (list of int) """ self._palette = palette
[docs] def pixmap (self) : """Return a pixmap representation of the spatial image """ raise NotImplementedError("TO subclass")
[docs] def resolution (self) : """Return resolution of the pixmap :Returns Type: float,float """ if self._img is None : return return getattr(self._img,"resolution",(1,1) )[:3]
[docs] def pixmap_real_size (self) : """Return the spatial extension of the pixmap :Returns Type: float,float """ res = self.resolution() if res is None : return pix = self.pixmap() if pix is None : return return pix.width() * res[0],pix.height() * res[1] ######################################## # # view transformation # ########################################
[docs] def rotate (self, orient) : """Rotate view 90 degrees :Parameters: - `orient` (int) - orientation of rotation - if orient == 1, rotation clockwise - if orient == -1, rotation counterclockwise """ self._transform = (self._transform + orient * 90) % 360
[docs] def data_coordinates (self, x_pix, y_pix) : """Convert coordinates expressed in the pixmap into coordinates expressed as indices in the data space. :Parameters: - `x_pix` (int) - `y_pix` (int) :Returns Type: int,int """ raise NotImplementedError("TO subclass")
[docs] def pixmap_coordinates (self, inds) : """Convert coordinates expressed in data space into a point in the pixmap space :Parameters: - `inds` (tuple of int) - coordinate of a point in data space e.g. i,j or i,j,k :Returns Type: int,int """ raise NotImplementedError("TO subclass")
[docs]class PixmapStackView (PixmapView) : """Pixmap view of a spatial image as a stack of images """ def __init__ (self, img = None, palette = None) : """Constructor :Parameters: - `img` (array) - a more than 3D array - `palette` (array of qRgba) - see set_palette """ self._pixmaps = [] self._current_slice = 0 PixmapView.__init__(self,img,palette) def _reconstruct_pixmaps (self, axis=2) : pal = self.palette() data = self.image() #rotation tr = QTransform() tr.rotate(self._transform) #construct pixmaps pix = [] # Manage also colored images. if len(data.shape) == 4 and data.shape[-1] in (3,4): if data.dtype != uint8: raise Exception("Only uint8 RGB[A] images supported, got %s instead"%str(data.dtype)) pal = None for z in xrange(data.shape[axis]) : if axis == 0 : dat = data[z,:,:] elif axis == 1 : dat = data[:,z,:] else : dat = data[:,:,z] if pal is not None: dat = pal[dat] if isinstance(data, SpatialImage): dat=SpatialImage(dat) #img = QImage(dat, # data.shape[0], # data.shape[1], # QImage.Format_ARGB32) dat = to_pix (dat) pix.append(dat.transformed(tr) ) self._pixmaps = pix self._current_slice = min(max(self._current_slice,0),len(pix) - 1) def _reconstruct_pixmaps_fast (self, axis=2) : pal = self.palette() data = self.image() #rotation tr = QTransform() tr.rotate(self._transform) #construct pixmaps pix = [] if len(data.shape) == 4: vdim = data.shape[3] else: vdim = 1 # when viewing in Z and in Z only, we can use # the system's native LUT forceNativeLut = False #True if axis == 2 else False # set to true to copy img to a c_contiguous buffer make_contiguous = False if (vdim == 1 or axis!=2) else True for z in xrange(data.shape[axis]) : if axis == 0 : dat = data[z,:,:] elif axis == 1 : dat = data[:,z,:] else : dat = data[:,:,z] if make_contiguous: dat = dat.copy() dat = to_pix(dat, lut=pal, forceNativeLut=forceNativeLut) pix.append(dat.transformed(tr) ) self._pixmaps = pix self._current_slice = min(max(self._current_slice,0),len(pix) - 1) ######################################## # # accessors # ########################################
[docs] def nb_slices (self) : """Number of slices in the stack :Returns Type: int """ return len(self._pixmaps)
[docs] def current_slice (self) : """Index of the currently displayed slice :Returns Type: int """ return self._current_slice
[docs] def set_current_slice (self, ind) : """Change current displayed slice :Parameters: - `ind` (int) - index of the slice in the data array """ self._current_slice = ind
[docs] def pixmap (self) : """Return a pixmap representation of the spatial image """ if self._current_slice < len(self._pixmaps) : return self._pixmaps[self._current_slice] else : return None
[docs] def set_palette (self, palette,axis=2) : """Set the palette .. warning:: will cast color value to uint32 :Parameters: - `palette` (list of int) """ PixmapView.set_palette(self,palette) if self.image() is not None : self._reconstruct_pixmaps(axis)
[docs] def set_image (self, img) : """Set an image to display :Parameters: - `im` (array) """ PixmapView.set_image(self,img) if self.palette() is not None : self._reconstruct_pixmaps() ######################################## # # view transformation # ########################################
[docs] def rotate (self, orient) : """Rotate view 90 degrees :Parameters: - `orient` (int) - orientation of rotation - if orient == 1, rotation clockwise - if orient == -1, rotation counterclockwise """ PixmapView.rotate(self,orient) tr = QTransform() tr.rotate(orient * 90) self._pixmaps = [pix.transformed(tr) for pix in self._pixmaps]
[docs] def data_coordinates (self, x_pix, y_pix, axis=0) : """Convert coordinates expressed in the pixmap into coordinates expressed as indices in the data space. :Parameters: - `x_pix` (int) - `y_pix` (int) :Returns Type: int,int,int """ if len(self._pixmaps) == 0 : raise UserWarning("no image loaded") w_pix = self.pixmap().width() h_pix = self.pixmap().height() if self._transform == 0 : i = x_pix j = y_pix elif self._transform == 90 : i = y_pix j = w_pix - x_pix elif self._transform == 180 : i = w_pix - x_pix j = h_pix - y_pix elif self._transform == 270 : i = h_pix - y_pix j = x_pix return i,j,self._current_slice
[docs] def pixmap_coordinates (self, i, j) : """Convert coordinates expressed in data space into a point in the pixmap space :Parameters: - `inds` (tuple of int) - coordinate of a point in data space e.g. i,j or i,j,k :Returns Type: int,int """ w_pix = self.pixmap().width() h_pix = self.pixmap().height() if self._transform == 0 : x_pix = i y_pix = j elif self._transform == 90 : y_pix = i x_pix = w_pix - j elif self._transform == 180 : x_pix = w_pix - i y_pix = h_pix - j elif self._transform == 270 : y_pix = h_pix - i x_pix = j return x_pix,y_pix
[docs]class ScalableLabel (QLabel) : """Scalable label that respect the ratio of the pixmap it display """ def __init__ (self, parent = None) : QLabel.__init__(self,parent) self.setScaledContents(True) self._ratio = 1. self._resolution = (1.,1.) def _compute_ratio (self) : pix = self.pixmap() if pix is not None : vx,vy = self._resolution # NOTE : this is part of the weirdness : someone serves us #with inverted voxel sizes vx, vy = vy, vx self._ratio = (pix.height() * vy) / (pix.width() * vx)
[docs] def set_resolution (self, x_scale, y_scale) : """Set the resolution of the image :Parameters: - `x_scale` (float) - size of a pixel in x direction - `y_scale` (float) - size of a pixel in y direction """ self._resolution = (x_scale,y_scale) self._compute_ratio()
[docs] def pixmap_coordinates (self, x_screen, y_screen) : """Compute pixmaps coordinates that map the given point on the screen :Parameters: - `x_screen` (int) - `y_screen` (int) """ pix = self.pixmap() if pix is None : return None x_pix = x_screen * pix.width() / self.width() y_pix = y_screen * pix.height() / self.height() return x_pix,y_pix
[docs] def resizeEvent (self, event) : if event.oldSize() != event.size() : w = event.size().width() h = event.size().height() if int(w * self._ratio) <= h : self.resize(w,w * self._ratio) else : self.resize(h / self._ratio,h)
[docs] def setPixmap (self, pix) : QLabel.setPixmap(self,pix) self._compute_ratio()
[docs] def mousePressEvent (self, event) : self.emit(SIGNAL("mouse_press"),event)
[docs] def mouseMoveEvent (self, event) : self.emit(SIGNAL("mouse_move"),event)
[docs]class ScalableGraphicsView (QGraphicsView) : """Graphics View that always zoom to fit it's content """ def __init__ (self, *args, **kwargs) : QGraphicsView.__init__(self,*args,**kwargs) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
[docs] def resizeEvent (self, event) : sc = self.scene() if sc is not None and event.oldSize() != event.size() : if sc.width() > 0 and sc.height() > 0 : s = min(event.size().width() / sc.width(), event.size().height() / sc.height() ) t = QMatrix() t.scale(s,s) self.setMatrix(t)
[docs] def mousePressEvent (self, event) : self.emit(SIGNAL("mouse_press"),event.pos(),self)
[docs] def mouseMoveEvent (self, event) : self.emit(SIGNAL("mouse_move"),event)