#!/usr/bin/env python from __future__ import division # Allow 1/10 = 0.1 for eval() #---------------------------------------------------------------------- # FILE # PURPOSE: Visualization application which shows how to integrate PyQt, # Matplotlib and GIPSY and how to mix event handling between # PyQt and GIPSY # AUTHOR: M.G.R. Vogelaar, University of Groningen, The Netherlands # DATE: March 27, 2011 # UPDATES: March 27, 2011 # October 08, 2012 # December 14, 2012 (changed name to 'visions' __version__ = '1.0.8' # # (C) University of Groningen # Kapteyn Astronomical Institute # Groningen, The Netherlands # E: gipsy@astro.rug.nl #---------------------------------------------------------------------- #TODO: """ - Graticule werkt op data in tab, maar displayed in current image XX -Presetting van combi graticule colorbar en dan image laden geeft crash Opgelost. Anastasia: 1/2 - Remove tab geeft een crash als er een set staat. Ik zie geen crash, maar de cubes blijft nog wel in de movielijst staan. XX -Kaart met foute CUNIT toch laten displayen Er is een knopje bij het data inputveld geplaatst waarmee je de wcs check achterwege laat. XX - Foutmelding bij data invoer staat op verkeerde plaats Dankzij voorbeeld op web, nu implementatie in gipsy.QTmessage XX -Posities bij zoomen niet goed als er panels aan hangen. Actie 1: Elke herpositionering van het window wordt uitgevoerd met de gegeven schaalfactor (als deze bestaat). Dat wil zeggen dat een eenmaal gekozen schaal, het image op dezelfde grootte houdt, ongeacht hoe je het window wijzigt. Actie 2: Verbeterde positionering van panels (nu met kleine ruimte tussen panels) XX-Crash bij zoom zonder cube. (Ondervangen) TODO: Marc: XX 1) Maak kaart met kolommen met 1 kleur en elke kolom met een andere kleur dan de buur. Bekijk dan wat een groot image doet op een klein deel van het scherm. Worden er kleuren weggelaten? Respons: Als er meer data pixels zijn dan schermpixels, dan worden er datapixels weggelaten bij het samplen. Er wordt geen interpolatie toegepast. 2) Maak een optie zodat je kunt stappen door de slice panels. Nu is de mousebeweging aan de groffe kant. 2a) Cliplevels in termen van mean/rms omdraaien, dus van -4 naar 5 3) Bouw een optie in om een kaart te reprojecteren naar een andere header, zodat deze kaart kan worden vergeleken, met de huidige. 4) Bouw een optie in om een movie te kunnen maken. 5) bouw een grafiek in voor een z profiel. XX 6) maak een kruiscursor voor in de slicepanels XX 7) Labels in colorbar voorzien van achtergrond voor zichtbaarheid 8) Voeg graticule toe in slicepanels XX 9) Viewport met schermpixels kunnen instellen Respons: Is geimplementeerd met een aantal zoomstappen. Deze berekenen de afmetingen van een frame dat precies op een integer aantal achermpixels past. XX 10) Kaarten met incorrecte wcs toch kunnen afbeelden. Gebruik kaart 050131_6RR als voorbeeld. Respons: Dit was van het begin af de opzet. Werkt dus. Moet je wel de goede assen invoeren Er is nog wel een issue met een foute cunit in de header voor een spectrale as. Dan kan je niets meer displayen. XX 11) BWlut default maken. Favorieten toegevoegd (4 GIPSY luts uit GIDS menu). Deze staan nu bovenaan de lijst. XX 12) Iets ruimte tussen de hoofd en zijpanelen Is nu 4 pixels voor alle overgangen. XX 13) Colorbar ticks breder maken. Respons: Iets breder gemaakt XX 14) Aspect ratio kunnen instellen. Respons: Gedaan om twee redenen. 1) Mogelijk om aspect ratio gelijk te maken aan andere kaart. 2) Pixels kunnen op schermpixels worden afgebeeld. 15) Bij hertekenen (DRAW knop) van plot, wordt de huidige colormap niet meegenomen. XX 16) Clip in termen van mean -4*rms mean +5*rms. Omdat je toch ook al de min/max van de data bepaalt, kan je hetzelfde algoritme gebruiken om van elke subset de mean en rms te bepalen. Er is een algoritme dat de mean van twee datasets bepaalt en ook de rms kan zo bepaalt worden. Door elke subset apart in te lezen kunnen we zo een soort running mean en rms maken. ------------------- TODO: Als een blankcolor geselecteerd is voordat een zijpaneel wordt geopend, dan wordt deze kleur nog niet meegenomen. XX Interactie met externe taak. Via plotcommand= kan je plotcommando's naar 't huidige frame sturen. Deze plotacties zijn vluchtig. Bedenk een manier om ze bestendig te maken tegen image wisselingen. Je wilt niet de hele plot meenemen bij een image wisseling. Hiervoor heb ik een 'redraw-all' knopje ingebouwd die de plotkan herstellen op het moment je dat zelf wilt. XX CLIPS= heeft nu speciale status. Zoek uit hoe deze gecombineerd kan worden met DATASETn (dus CLIPSn=) Je wilt vmin, vmax van tevoren kunnen opgeven. Dat gaat goed met CLIPS= De clips op het datablad vormen de defaults voor de clips op de color tab. XX -Als je data hebt ingelezen dan wil je dat min, max als gegeven in de data tab staat. -Maak cursor anders voor color editing. Dit is prettig als het veranderen van luts etc lang duurt. -Implementeer movies maker XX - De resetknop bij clips reset naar de waarden vmin, vmax. Dat zijn niet altijd de echte min max van de data. Als je b.v. de data hebt ingelezen met CLIPS=a, b, dan zijn a, b de reset waarden geworden. Je zou kunnen besluiten om altijd de echte min max te bekijken. XX - Lees keywords datamin, datamax uit GIPSY headers Commentaar: Gedaan. Is als optie beschikbaar bij het instellen van cliplevels XX -Terugkoppeling met colorediting in plot zelf. GUI wordt niet bijgewerkt. Commentaar: Gemaakt. Alle acties worden teruggekoppeld. TODO: Maar ook die van histogram equalization? XX - Movie tab disable -en als je een movie laadt. Commentaar: Gedaan XX -Zoom en pan modes in de positionbar zetten. Dit kan niet met de movie_notify_events want die worden in die modes geblokt. Algemeen: --------- XX - Bij het beeindigen van het programma door het window te sluiten is het misschien verstandig de gebruiker dit te laten bevestigen. Commentaar: Stoppen van de applicatie is een bewuste actie. Zowel stoppen via menu, kruisje als ctrl-Q zijn acties die niet per ongeluk worden uitgevoerd. Mij is het nog niet overkomen. Ik vind de extra handelingen die je moet uitvoeren om dan het programma te verlaten, onvriendelijk. Een applicatie als ds9 doet het trouwens ook niet. Data tab: --------- XX - als er geen Box e.d. is ingevuld, zouden de defaults meteen kunnen worden ingevuld. Commentaar. Implementatie lag er al. Opnieuw aangesloten en werkt met de vlag header cliplevel == 'not set'. Is gebruiksvriendelijker geworden. XX -'Ok' bij 'Min, max from data' en 'Swap axes' lijkt me overbodig. Commentaar: Min max bestaat niet meer (nieuwe clip levels blok). Ok bij swap is inderdaad overbodig. XX - Als 'Graticule' aantaat en er wordt op 'Draw' geklikt, crasht het programma: Commentaar: Hier was in een nieuwere versie al een try except omheen gezet, die nu dus ook deze situatie afvangt. TODO: Alleen in deze situatie zou je geen foutmelding moeten zien. Traceback (most recent call last): File "/home/gipsy/exe/linux64/kapteyn/mplutil.py", line 957, in reached self.proc(self) File "/home/gipsy/exe/linux64/kapteyn/maputils.py", line 11342, in loadimages fig.subplots_adjust(left=self.colbwidth, right=1.0, bottom=0.0, top=1.0) File "/home/gipsy/exe/linux64/kapteyn/maputils.py", line 411, in subplots_adju self.ext_callback() File "/home/gipsy/exe/linux64/kapteyn/maputils.py", line 10823, in reposition gratframe = cube.grat.frame AttributeError: 'NoneType' object has no attribute 'frame' DEMOVIEWER.PY Uncaught Python exception - FATAL XX - Na het beeindigen van het programma zie ik de volgende boodschap in de log file: Module matplotlib was already imported from /home/gipsy/exe/linux64/matplotlib/__init__.py, but /usr/lib/pymodules/python2.6 is being added to sys.path Commentaar: Is tijdelijk zolang GIPSY naar eigen mpl wijst. We wachten op bevestiging dat 1.1.1 overal is geinstalleerd. Utils tab: ---------- XX - Label 'Split im.' liever voluit. XX - Ik moest even zoeken in de help om er achter te komen dat ik voor split image 'Ctrl' moet indrukken. Een hint ter plaatse (tekst, bubble help) lijkt me handig, ook voor de betekenis van de muisknoppen. Commentaar: De grote documentatieslag moet nog plaatsvinden. Ik heb al wel tooltip en whatsthis toegevoegd om de demogebruiker niet in de kou te laten staan. Overigens staan de opties al wel in de help beschreven (f1). XX - Als de interpolatie iets anders is dan 'nearest', dan komt er bij split screen een scheiding tussen de images. Commentaar: Dit is een scherminterpolatie. De NaN's geven hier de ongewenste effecten. De interpolatie moet als zodanig geadverteerd worden. XX - Als de interpolatie iets anders is dan 'nearest', dan worden movies traag en dat blijft ook zo. Het lijkt er op dat die interpolatie telkens opnieuw wordt uitgevoerd. Commentaar: Zie vorige commentaar. Movie tab: ---------- XX - Ik denk dat het beter is de Frame slider -invoerveld te koppelen en het label rechts van de slider te laten vervallen. En de waarde in het invoerveld te laten staan. Het wordt nu na invoer leeggemaakt. Commentaar: Met blocksignal is dit nu een werkbare optie. Het geheel is nu consistent. XX - Een in-/uitvoerveld voor de snelheid lijkt me ook nuttig. Commentaar: Is bijgebouwd. Kan nuttig zijn. Ook worden expressies toegelaten (b.v. 1/5 voor 5 sec voor 1 beeldje) XX - De Speed slider kan beter een logaritmische schaal hebben. Nu is het lastig om lage snelheden in te stellen. Commentaar: Slider is niet meer lineair. Ik heb nog wat moeite om een goede schaal te vinden, maar men kan nu beter lage snelheden installen waaronder ook minder dan 1 fr/s XX - Het 'play' knopje zou als een toggle moeten werken, dus nog een keer drukken stopt de movie weer. Aan het knopje zou ook te zien moeten zijn of de movie loopt of niet. Commentaar: De knoppen zijn nu zowel wisselknoppen (richting) als aan/uit knoppen. Je kunt nu zien welke playknop is ingedrukt. XX - Een 'play reverse' knopje naast het 'play' knopje zou ook handig zijn. De gebruiker kan dan snel de richting van de movie omkeren en hoeft in een reeks interessante frames niet een nieuwe cyclus af te wachten. Commentaar: Was triviaal om in te bouwen. Kan wel handig zijn. Werkt ook in omgekeerde richting met lijst van framenummers. Colors tab: ----------- XX - De lijst met color maps is nu wel erg lang. Misschien zou je standaard een selectie van de belangrijkste laten zien en optioneel de hele lijst. Prachtig zou het zijn als de gebruiker de standaard lijst kan configureren. Commentaar: De lijst is nu (op alle platforms) beperkt tot 10 items. De belangrijkste staan bovenaan. Dit zijn de meest gebruikte GIDS luts. XX - De colormap reset zet niet alleen de slope en offset terug, maar ook de lut. Dat vind ik niet prettig. Commentaar: Color offset en slope hebben eigen resetknop. Er is een 'Reset all' bijgekomen die alles reset, nu ook inclusief blank color. Deze optie is nu nuttig omdat je na het laden van een nieuw image niet de huidige colorsettings overneemt. Slices tab: ----------- XX - Ik zag dat je de volgorde in de zijpanelen default consistent hebt gemaakt. Dat is mooi. Commentaar: - XX - Waarom staan de velden 'X/Ypanel' trouwens onder de 'Data' tab? In de 'Slices' tab had ik het verband niet meteen gelegd. Is het wel nodig om beide paren velden te hebben? Commentaar: Hier moet in ieder geval de layout anders en nog documentatie worden aangeboden. Die twee opties zijn nodig want soms wil je flexibel zijn en kan je elke volgorde in termen van framenummers aanwijzen. De andere optie is een selectie die bij een set hoort. Dit is weer gemakkelijker als je sets verwijdert. Beide opties hebben dus een voordeel. Positions tab: -------------- XX - Het 'Apply' knopje vind ik onhandig en overbodig. Alle wijzigingen die de gebruiker hier invoert, hebben pas effect als daadwerkelijk data naar buiten wordt gebracht. Commentaar: Bleek geen probleem en er waren weinig regels code voor nodig. XX- Ook hier zou een kleine helptekst op zijn plaats zijn. Commentaar: Iets toegevoegd maar echte documentatie komt nog Zoom tab: --------- (nog niet uitgebreid bekeken) """ import os, sys, string, time #------------------------------------------------------------------------------- # Startup code # 'SP' means 'socket pair'. This is the way Hemes communicates with GIPSY # tasks. It is Hermes that inserts this in the argument list. # So if it is not started by Hermes, but on the command line, # we need to involve the terminal version of Hermes (ngipsy.csh) # with the current task as argument. This can be seen as a bootstrapping # procedure. # # Starting the viewer from the command line: # # ngipsy is started with a log file which has the time of startup in # its name. It is handy to keep this file after a session because # it can hold debug information if the program fails to execute or # it can contain tables with cursor positions which you want # to keep or extract. # # A program can be started with option -p (or -P). What it does then is using # the keywords of the last run of the program. This is the same effect as using # the '!' character in front of an aplication name in GIPSY's task manager Hermes. # Example: ./visions.py -p # # Also additional keywords can be given on the command line. # For example: ./visions.py inset=m101.fits # # Ofcourse you can combine these two options (keywords and -p) # Note that semicolons and other characters that are interpreted by your # shell, should be escaped. #------------------------------------------------------------------------------- if 'SP' in sys.argv: # started by Hermes try: import gipsy except: print 'The application cannot import GIPSY Python module!' sys.exit(-1) else: logfile = 'visions'+'%f.LOG' % time.time() print "Log file is %s"%logfile # Give information about log file args = [item for item in sys.argv if item not in ['-P','-p']] # Items with -P or -p are filtered so list became smaller if len(args) < len(sys.argv): prevkey = '\!' # Insert the macro character for Hermes else: prevkey = '' arglist = "'" + prevkey + string.join(args) + "'" command = '$gip_sys/ngipsy.csh -l%s ' % logfile + arglist try: os.system(command) except Exception, errmes: print 'Program cannot start: %s'%errmes sys.exit(-1) sys.exit(0) # ------------------------------------------------------------------------------ import platform from PyQt4.QtCore import Qt, SIGNAL, SLOT, QString, qRegisterResourceData, qUnregisterResourceData,\ QUrl, QSettings, QVariant, QEvent, QFileInfo, QFile, QTimer,\ QT_VERSION_STR, PYQT_VERSION_STR, QStringList from PyQt4.QtGui import QFileDialog, QWidget, QPixmap, QLabel, QDialog, QMainWindow,\ QApplication, QAction, QIcon, QLineEdit, QPushButton,\ QCheckBox, QSlider, QFont, QFrame, QHBoxLayout, QVBoxLayout, QMessageBox,\ QKeySequence, QToolBar, QTextBrowser, QTabWidget, QSizePolicy, QComboBox,\ QRadioButton, QButtonGroup, QProgressBar, QStatusBar,\ QCompleter, QDockWidget, QGridLayout, QPalette, QColor, QGroupBox,\ QCursor # Force the application to use the QT4 canvas! from matplotlib import use use('qt4agg') # Do this before including any Matplotlib from matplotlib.backends import backend_qt4 backend_qt4.figureoptions = None from kapteyn import maputils import ioutils #---------------------------------------------------------------------- from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar from matplotlib.figure import Figure from matplotlib.artist import setp from matplotlib.pyplot import get_current_fig_manager as plt_get_current_fig_manager from matplotlib.pyplot import close as closefig from matplotlib.pyplot import ion import matplotlib.axes as axesclass # Needed to find axes objects for deletion import matplotlib.cbook as cbook # Needed to report memory import gc # Garbage Collection from math import floor, log10 import pyfits #import gipsy import numpy from string import letters from re import sub as re_sub from re import match as re_match from matplotlib import __version__ as MPL_VERSION_STR from pyfits import __version__ as PYFITS_VERSION_STR from numpy import __version__ as NUMPY_VERSION_STR from setbrowser import QtDataBrowser # Redefine buttond in backend_qt4agg to 'support' extra mouse buttons. # This avoids a crash when somebody hits such a button by accident FigureCanvas.buttond = {0:6, 1:1, 2:3, 4:2, 8:4, 16:5} # Globals blurscale = 20.0 # Needed for smoothing #TODO Make this an attribute for an image. helpforms = {} notfound = "Not found" notset = "Not set" #--------------------- Resources HOWTO ----------------------------- import res_viewer # For testing, read howto below """ Resources are included in this source file. Created with: pyrcc4 -o res_viewer.py res_viewer.qrc from resource file res_viewer.qrc, we list (part of) the content: ./kapteyn.png ./fileopen.png ./exit.png ./home.png ./back.png ./dataset_small.png ./filesaveas.png ./index.html The listed files (icons and documentation) can be used from this source. From the file res_viewer.py we create a GIPSY .src file Look in file $gip_tsk/res_viewer.py.src how to extract its make file. For testing we can make a local version of this resource file. It is also possible to include its contents into this source. Strip the first lines and remove the QtCore prefixes. It should start and end with: qt_resource_data = "\ etc. qInitResources() """ #------------------------------------------------------------------- # Tooltips & What's this text help_box = ("

Grid limits from xlo,ylo to xhi,yhi
(press shift+F1 for examples)

", "Define the boundaries of the data that should be displayed.\ The syntax is: tuple_lower tuple_upper.
\ or: tuple_center D length_x, length_y
\ Examples (See also Handbook):") help_data = ("

Enter FITS file or GIPSY set. (press shift+F1 for examples)

", "Enter FITS file or GIPSY set with a slice specification
\ using the names of the repeat axes (axes in data that do
\ not belong to the image).
\ If no repeat axes are given, the program sets the input
\ to a suitable default.
\ Examples (See also Handbook):") help_splitim = ("

Enter number of image to compare with current. Press shift+F1 for more information.", "To compare two images, one can use a split image technique\ or transparancy. The current image is compared with the\ image you enter here. To make the current image transparent,\ use the transparency slider. To split the display in the\ current image and the selected split image, use the\ control key on your keyboard with mouse buttons 1, 2 or 3.
\ ctrl+mb1 (left button) splits horizontally, ctrl+mb2 splits vertically and\ ctrl+mb3 splits in x and y direction.") help_slicepanels = "

Data cubes contain a number of images.\ This viewer provides an inspection tool which shows one or\ two so called slice panels. These slice panels are crosscuts through\ the data in your cube at the position of the mouse in an image. A slice panel\ shares one axis with the image data and the other axis is\ usually a spectral axis. The slices that are included along this slice axis are\ given in this input field in terms of movie frame numbers.\ There is mouse interaction in image and\ both slice panels. Read the manual for a description of this\ interaction.

An alternative input is possible on the data input tabs.\ You can activate this by pressing the draw button next to the\ message 'Use panels defined in data tabs'

" help_plotcommand = "Examples:" help_markercommand = "Examples:\ Contents recall file to plot galactic coordinates in equatorial system:\
\
                   {ga}  102.082421781 {} 59.922057394
\ {ga} 102.081754358 {} 59.922300102
\ {ga} 102.1662291260 {} 59.8328022928
\ {ga} 102.1666666666 {} 59.8333333333
\ {ga} 102d0m {} 59d45m
\
" class MyLineEdit(QLineEdit): #--------------------------------------------------------------------------- # Purpose: Define an improved version of the lineEdit class with command # history. # # This is an extension of 'QLineEdit'. The extension is a command history # which can be browsed with the arrow up/down keys. As container we # use a QStringList list of strings which has some useful methods not # found in Python lists. The container can be filled with strings # at startup time when reading QSettings. # The mouse scrollwheel can also be used to browse the history. # The maximum number of strings that are stored in the history is 20. # # For a list of keys, read: # http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qlineedit.html #--------------------------------------------------------------------------- def __init__(self, *args): QLineEdit.__init__(self, *args) self.history = QStringList([]) # List with commands self.histpointer = 0 # Number of current command in list self.histlen = 0 # Prepare completion of strings from memory lineEditCompleter = QCompleter(self.history) #lineEditCompleter.setCompletionMode(QCompleter.InlineCompletion) lineEditCompleter.setCompletionMode(QCompleter.PopupCompletion) lineEditCompleter.setCaseSensitivity(Qt.CaseInsensitive) self.setCompleter(lineEditCompleter) self.completer = lineEditCompleter def setHistory(self, hist): #--------------------------------------------------------------------------- # Purpose: Initialize this line editor's history with a list of commands # # Usually this will be commands from a previous session saved as a QSettings # object. It is also possible to fill it with a Python list with strings. # We make a copy of the initial list so that each command line builds its # own history. #--------------------------------------------------------------------------- self.history = QStringList(hist[:]) # Force copy and force the right type self.histlen = len(self.history) self.completer.model().setStringList(self.history) def appendHistory(self, command): #--------------------------------------------------------------------------- # Purpose: Append this command to the history list. #--------------------------------------------------------------------------- h = QString(command).trimmed() if h and not self.history.contains(h): self.history.append(h) self.completer.model().setStringList(self.history) while self.history.count() > 20: self.history.removeAt(0) self.histlen = len(self.history) self.histpointer = self.histlen - 1 def trigger(self): #--------------------------------------------------------------------------- # Purpose: Execute the same action as if the return/enter key was pressed. # # If you want to store the lineedit contents without pressing # enter, you can do it by triggering the append process. #--------------------------------------------------------------------------- self.appendHistory(self.text()) def event(self, event): #--------------------------------------------------------------------------- # Purpose: Intercept a number of useful QlineEdit events to process # command history # # In fact, we re-define the event() method. # Intercept the reteurn/enter, the arrow keys and the mouse scroll wheel. #--------------------------------------------------------------------------- if (event.type()==QEvent.KeyPress) and (event.key()==Qt.Key_Up): self.histprev() return True elif (event.type()==QEvent.Wheel and event.delta() > 0): self.histprev() return True elif (event.type()==QEvent.KeyPress) and (event.key()==Qt.Key_Down): self.histnext() return True elif (event.type()==QEvent.Wheel and event.delta() < 0): self.histnext() return True elif (event.type()==QEvent.KeyPress) and (event.key() in [Qt.Key_Return, Qt.Key_Enter]): self.appendHistory(self.text()) return QLineEdit.event(self, event) def histprev(self): #--------------------------------------------------------------------------- # Purpose: Step back in command history #--------------------------------------------------------------------------- if self.histlen: hp = self.histpointer-1 if hp < 0: hp = self.histlen - 1 self.histpointer = hp self.setText(self.history[self.histpointer]) def histnext(self): #--------------------------------------------------------------------------- # Purpose: Step forward in command history #--------------------------------------------------------------------------- if self.histlen: hp = self.histpointer + 1 if hp > self.histlen - 1: hp = 0 self.histpointer = hp self.setText(self.history[self.histpointer]) class floatSlider(QWidget): #--------------------------------------------------------------------------- # Purpose: Replace QT slider with one that works with floating point numbers #--------------------------------------------------------------------------- def __init__(self, fmin, fmax, *args): #QWidget.__init__(self, *args) QWidget.__init__(self) self.steps = 200 self.fmin = fmin self.fmax = fmax self.slider = QSlider(*args) self.slider.setRange(0, self.steps) self.connect(self.slider, SIGNAL("valueChanged(int)"), self.adjustValue) #self.adjustValue(self.slider.value()) def adjustValue(self, ival): fval = ival* (self.fmax-self.fmin)/float(self.steps) + self.fmin self.emit(SIGNAL("valueChanged(float)"), fval) def setSliderPosition(self, fval): ival = float(self.steps) * (fval-self.fmin)/(self.fmax-self.fmin) self.slider.setSliderPosition(ival) def setValue(self, fval): ival = float(self.steps) * (fval-self.fmin)/(self.fmax-self.fmin) self.slider.setValue(ival) def setStyleSheet(self, style): self.slider.setStyleSheet(style) def setToolTip(self, tip): self.slider.setToolTip(tip) class HeaderForm(QDialog): #--------------------------------------------------------------------------- # Purpose: A class for showing a window which displays header information #--------------------------------------------------------------------------- def __init__(self, text, title=None, parent=None, widgetid=None): super(HeaderForm, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowModality(Qt.NonModal) self.widgetid = widgetid self.textBrowser = QTextBrowser() # The core of this form is a browser self.textBrowser.setReadOnly(True) layout = QVBoxLayout() layout.addWidget(self.textBrowser, 1) self.setLayout(layout) self.resize(700, 800) self.textBrowser.setText(text) if title: self.setWindowTitle(self.tr(title)) def closeEvent(self, event): global helpforms if self.widgetid is not None: del helpforms[self.widgetid] class HelpForm(QDialog): #--------------------------------------------------------------------------- # A class that creates a form with help information for the user after # clicking on the help menu. The Help information is written in HTML and is # packed into the resources file. #--------------------------------------------------------------------------- def __init__(self, page, parent=None, widgetid=None, xsize=700, ysize=800): super(HelpForm, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowModality(Qt.NonModal) self.widgetid = widgetid #self.setWindowModality(Qt.ApplicationModal) backAction = QAction(QIcon(":/back.png"), self.tr("&Back"), self) backAction.setShortcut(QKeySequence.Back) homeAction = QAction(QIcon(":/home.png"), self.tr("&Home"), self) homeAction.setShortcut(self.tr("Home")) self.pageLabel = QLabel() toolBar = QToolBar() toolBar.addAction(backAction) toolBar.addAction(homeAction) toolBar.addWidget(self.pageLabel) self.textBrowser = QTextBrowser() # The core of this form is a browser # We included links to external pages e.g. for Matplotlib # Then one has to tell the browser that this is ok (no by default) self.textBrowser.setOpenExternalLinks(True) layout = QVBoxLayout() layout.addWidget(toolBar) layout.addWidget(self.textBrowser, 1) self.setLayout(layout) self.connect(backAction, SIGNAL("triggered()"), self.textBrowser, SLOT("backward()")) self.connect(homeAction, SIGNAL("triggered()"), self.textBrowser, SLOT("home()")) self.connect(self.textBrowser, SIGNAL("sourceChanged(QUrl)"), self.updatePageTitle) self.textBrowser.setSearchPaths([":/"]) self.textBrowser.setSource(QUrl(page)) self.resize(xsize, ysize) self.setWindowTitle(self.tr("%1 Help").arg(QApplication.applicationName())) def updatePageTitle(self): # Method to update the page label. Only used within this class self.pageLabel.setText(self.textBrowser.documentTitle()) def closeEvent(self, event): global helpforms if self.widgetid is not None: del helpforms[self.widgetid] def getHeader(dataset, showErrorMessage): #------------------------------------------------------------------------------- # Purpose: Show header information for GDS or FITS file. # # Input dataset: string from data input field (or other source) # Input statusBar: The destination of error messages # # Get the header information of the set in the dataset input field in a # string and display this in a separate window. # Use GIPSY program HEADER to get this information for a GIPSY set. # If it is a FITS file, a pseudo GIPSY set will be created for which the # same procedure with task HEADER will be applied. For GIPSY sets, # the meta information is always drawn from the top level. # # Use deputy instead of xeq, so that wrong INSET= specification for HEADER # does not stop the program. Hermes will take care of these situations. # HEADER is called with a file name as destination. The information will be # read from this file as plain text and shown somewhere in the calling # environment. # # Close the GIPSY set after use. #------------------------------------------------------------------------------- if not dataset: return datasrc = str(dataset) # It must be a file that can be read by GIPSY try: #gipsyset = ioutils.WCSset(datasrc, create=False, write=False, gethdu=lambda x: 0) gipsyset = gipsy.Set(datasrc, create=False, write=False, gethdu=lambda x: 0) except Exception, errmes: #e = "'ioutils.WCSset' fails: " + str(errmes) e = "'gipsy.Set()' fails: " + str(errmes) if e == '': e = 'Unknown error. Perhaps image part is empty?' showErrorMessage(e) return None title = "Header from: " head = "
"   # Prevent html formatting in textbrowser

   # Send HEADER report to file on disk. Then read this file and display results
   HEADER_TMP = './header.tmp'
   if gipsyset.name:
      try:
         gipsy.wkey("A=%s"%gipsyset.name)
         gipsy.wkey("B=AG")
         gipsy.wkey("C=%s"%HEADER_TMP)
         gipsy.wkey("D=N")
         gipsy.subst("INSET=A=MODE=B=FILENAME=C=HISTORY=D=")
         # Cancel keywords before the deputy() call. Otherwise they
         # will not be removed from the keyword list of this application
         gipsy.deputy('HEADER')
         gipsy.cancel("A=")
         gipsy.cancel("B=")
         gipsy.cancel("C=")
         gipsy.cancel("D=")
         #gipsy.deputy('HEADER')
         title = "Header from: %s"%datasrc
         if not gipsyset.fits:
            title += ' (top level)'
      except gipsy.XeqError, mes:
         if str(mes).find("-11"):
            s = "Cannot create header. Probably file does not exist!"
         else:
            s = "Cannot create header: %s"%(unicode(mes))
         showErrorMessage(s)
         return None
      try:
         f = open(HEADER_TMP,"r")
      except:
         s = "Unable to read header temporary file created by HEADER task"
         showErrorMessage(s)
         return None

   tmp=f.read()
   if len(tmp) == 0:
      return None
   f.close()
   os.remove(HEADER_TMP)
   gipsyset.close()

   head += tmp
   head += " 
" return head, title def nint(x): #------------------------------------------------------------------------------- # A universal function to find the nearest integer of a floating point number. # The function is compatible with the nearest integer finders in GIPSY. #------------------------------------------------------------------------------- return floor(x+0.5) def triggerkey(key, value=None): #------------------------------------------------------------------------------- # Helper function which reads the value of a GIPSY keyword and # uses that keyword, value combination to trigger associated code # with GIPSY's wkey() routine. # Note that wkey() can also be used in external programs to trigger # functions in this program. #------------------------------------------------------------------------------- value = gipsy.usertext(key, default=2, defval=value) if value is not None: gipsy.wkey("%s%s" % (key, value)) class AppForm(QMainWindow): #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- labelstyle = "QLabel {color: rgb(20,20,255); background-color: #eeeeee; border:1px solid rgb(128,128,128);}" groupstyle = """ QGroupBox { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #E0E0E0, stop: 1 #FFFFFF); border: 2px solid gray; border-radius: 5px; padding-top: 8px; margin-top: 1ex; /* leave space at the top for the title */ } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top center; /* position at the top center */ padding: 0 8px; background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #FFCECE, stop: 1 #FFFFFF); } """ def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.setWindowTitle('VISIONS (%s)'%__version__) settings = QSettings() self.recentFiles = settings.value("RecentFiles").toStringList() # Needed for create_uppermenu() self.create_uppermenu() self.plotCommandHist = settings.value("PlotCommandHistory").toStringList() self.markerHist = settings.value("MarkerHistory").toStringList() self.create_main_frame() self.create_status_bar() #self.textbox.setText('') #self.boxform.setText('') self.setclipsformLo.setText('') # Lower and upper data values to scale colors self.setclipsformHi.setText('') # Lower and upper data values to scale colors self.grat = None self.fitsfile = None self.colsource = None self.colormap = None self.restoreGeometry(settings.value("Geometry").toByteArray()) self.lineEditHist = settings.value("LineEditHistory").toStringList() self.movieimages = None self.eventfrommovieslider = None # Can have values None, True, False #self.helpforms = {} self.plotcommandcb = None # Callback id for external plot commands self.markercb = None # Callback id for marker positions self.setbrowsers = {} def cleanData(self): #--------------------------------------------------------------------------- # Purpose: Cleanup data and reload all # # The idea is to clean up as much as possible, do a garbage collect and # read the lineedit text from all data input fields to reload all. #--------------------------------------------------------------------------- # 1. myCubes has movieimages which is a Moviecontainer # 2. myCubes has a cubelist with Cubeatts objects # 3. myCubes has pointer to gipsyset to close it # 4. movieimages has a annimagelist list # 5. cubelist object have imagesinthiscube list # TODO: Elke cube heeft een attribuut imagesinthiscube. Deze # ook deleten?? cidkey= self.myCubes.movieimages.cidkey cidscroll = self.myCubes.movieimages.cidscroll self.canvas.mpl_disconnect(cidkey) # Prevent problems while loading self.canvas.mpl_disconnect(cidscroll) # Prevent problems while loading gipsy.cancel("CLIPLO=") gipsy.cancel("CLIPHI=") # Only when these next two lines are put in front of the other removals, # the (excessive) memory leaking stopped del maputils.annotatedimage_list # Is a global variable in maputils which keeps references to Annotatedimage objects maputils.annotatedimage_list = [] for annim in self.myCubes.movieimages.annimagelist: annim.disconnectCallbacks() del annim.image.im._imcache # = None del annim.image.im._A #annim.image.im.remove() del annim.image.data del annim.image.im del annim.data del annim.data_orig del annim.image del annim.objlist del annim del self.myCubes.movieimages.annimagelist del self.myCubes.movieimages for cube in self.myCubes.cubelist: if cube.panelscb: cube.panelscb.deschedule() if cube.splitcb: cube.splitcb.deschedule() #cube.fitsobj.hdr.close() # The hdr is a GIPSY Set object and already closed del cube.fitsobj.dat del cube.fitsobj.boxdat #del cube.fitsobj.origdata del cube.fitsobj del cube.imagesinthiscube if cube.annimp1: cube.annimp1.disconnectCallbacks() del cube.annimp1 if cube.annimp2: cube.annimp2.disconnectCallbacks() del cube.annimp2 previd = None for fr in self.fig.findobj(axesclass.Axes): frid = id(fr) if frid != previd: fr.clear() self.fig.delaxes(fr) previd = frid self.fig.clf() # self.myCubes.resizecb.deschedule() del self.myCubes self.myCubes = None gipsy.anyout(str(self.fig), 16) closefig(self.fig) # Matplotlib's close() gipsy.anyout(str(self.fig), 16) gc.enable() #gc.set_debug(gc.DEBUG_LEAK) notreached = gc.collect() gipsy.anyout("GARBAGE COLLECT objects not reached: %d"%notreached, 16) gipsy.anyout("Len of garbage list=%d"%(len(gc.garbage)), 16) #print "GABAGELIST.... ",gc.garbage #sys.stdout.flush() #for ga in gc.get_objects(): for ga in gc.garbage: s = str(ga) #try: # if len(ga) > 80: ga = ga[:77]+'...' #except: # pass if str(ga).find("Annotatedimage") >=0: gipsy.anyout(str(ga), 16) """ try: #if str(ga).upper().find("IMAGE") >=0: if 1: #str(ga).find("Annotatedimage") >=0: gipsy.anyout(str(ga)) except: pass """ def resetpalette(self): #--------------------------------------------------------------------------- # Purpose: Helper function for showErrorMessage(). It resets the color # of the text in the status bar #--------------------------------------------------------------------------- palette = QPalette(self.statusBar) palette.setColor(QPalette.WindowText, QColor(0, 0, 0)) self.statusBar().setPalette(palette) def showErrorMessage(self, message, messagetime=5000, color='red', widget=None): #--------------------------------------------------------------------------- # Purpose: A modified statusbar which shows a message in an arbitrary # color. # # The current version of QT (4.6.2) has no methods to do this directly # 'messagetime' is number of milliseconds that a message is displayed in # the status bar. #--------------------------------------------------------------------------- mes = QString(message) palette = QPalette(self.statusBar) palette.setColor(QPalette.WindowText, QColor(color)) self.statusBar().setPalette(palette) self.statusBar().showMessage(mes, messagetime) QTimer.singleShot(messagetime, self.resetpalette) if widget: gipsy.QtMessage(widget, mes, delay=3.0, fgcolor='red', bgcolor='white') """ red = QColor('red') white = QColor('white') alert = QMessageBox() alert.setPalette(QPalette(red, white, white, red, red, red, white)) alert.setStandardButtons(QMessageBox.StandardButtons(0)) alert.setText('%s
\n' % mes) QTimer.singleShot(3000, alert, SLOT('hide()')) alert.exec_() """ def setDefaults(self, tabinf, filename): #--------------------------------------------------------------------------- # Purpose: From the suggested name in filename, find defaults for box etc. # #--------------------------------------------------------------------------- if not filename: return tabinf.datainput.trigger() minimalhead = tabinf.minimalhead.isChecked() #hdunum = 0 try: # The lambda expression in gethdu is necessary. # If there is more than one hdu, the Set class requires an hdu so # we take a safe default (0) gipsyset = ioutils.WCSset(filename, create=False, write=False, gethdu=lambda x: 0, minimal=minimalhead) # Here we add a new attribute 'hdus'. It contains a list with hdu numbers # that represent an image. For each valid hdu, we look for alternative # headers. For a FITS file with two image hdu's and with two alternate # headers 'A' and 'Z' in the second, we get the list: # gipsyset.hdus = [(0, []), (1, ['A','Z'])] gipsyset.hdus = [] # The attribute remains empty for GIPSY sets if gipsyset.fits: hdulist = gipsyset.hdulist #pyfits.open(gipsyset.fitsname) #hdulist = pyfits.open(gipsyset.fitsname) hdus = [] for i, hd in enumerate(hdulist): # This is our criterion for an image hdu isimage = hd.header.has_key('XTENSION') and hd.header['XTENSION'].find('IMAGE') != -1 if i == 0 or isimage: alternates = [] # There can be more than one alternate headers in one hdu # Look for these alternates by trying to find keyword CRPIXn # followed by a character. for a in letters[:26]: k = "CRPIX1%c" % a.upper() # To be sure that it is uppercase if hd.header.has_key(k): alternates.append(a) hdus.append((i,alternates)) hdulist.close() # Flatten this list and check whether the selected hdu is in the list with valid hdu's' if gipsyset.hdunum not in [item for sublist in hdus for item in sublist]: e = "This HDU does not contain an image" self.showErrorMessage(e, widget=tabinf.datainput) return gipsyset.hdus = hdus # New attribute except Exception, errmes: # Could not make a gds set. Display the reason why. e = "'ioutils.WCSset' fails: " + str(errmes) if e == '': e = 'Unknown error. Perhaps image part is empty?' self.showErrorMessage(e, widget=tabinf.datainput) return gipsy.anyout("HDUS: %s"%(str(gipsyset.hdus)), 16) subdim = gipsyset.ndims(subset=True) setdim = gipsyset.ndims() if subdim != 2: if subdim <= 1: if setdim < 2: self.showErrorMessage("Dimension data is %d and must be at least 2"%setdim, widget=tabinf.datainput) return else: self.showErrorMessage("Image must have 2 axes", widget=tabinf.datainput) return else: # Now we have a special case. The dimension of the dataset is > 2. # Instead of returning immediately we try to re-create a data # input string that describes a two dimensional slice. # But don't do this when a user entered more than the file name # alone because that should be regarded as an attempt to specify # slices, which we don't want to overwrite. fl = filename.split() if len(fl) > 1: self.showErrorMessage("Incorrect specification of slice, try name without repeat axes.", widget=tabinf.datainput) return else: # One (or more) axis was missing to set two dimensional # images. Find the names of the missing axis (axes) and # if it only one, display all images along this axes. # For more axes, take anly one grid on those axes. newspec = filename for axnum in range(3, setdim+1): axis = gipsy.Axis(gipsyset, axnum-1, subset=0) # Here axnums start with 0 firstsubset = 1.0 - nint(axis.crpix) if axnum == 3: newspec += " %s"%(axis.name) else: newspec += " %s %d"%(axis.name, firstsubset) filename = newspec tabinf.datainput.setText(filename) gipsyset.close() gipsyset = self.setDefaults(tabinf, filename) # Try again TODO weghalen gipsyset= # TODO: Dit moet in twee stappen. Als er een suggestie is voor een slice # dan moet de aanroepomgeving dat weten en kan deze een 2e poging doen return # Don't forget to return, otherwise the rest of this code will be executed twice. self.addRecentFile(filename) # Handle the box try: box = "" # This is the default gblo, gbhi = gipsyset.setwcsbox(box) # Limits in grids! s = "%d %d %d %d"%(gblo[0], gblo[1], gbhi[0], gbhi[1]) tabinf.boxinput.setText(s) except Exception, errmes: self.showErrorMessage(str(errmes), widget=tabinf.boxinput) return # Spectral translations TODO: tekst klopt niet meer # This needs some explanation. First we read the contents of the spectrans GIPSY keyword. # This can be set either at the Hermes command line or with the open file widget. # Then a list with spectral translation is created. To this list we prepend 'None' # to select the spectral axis of the header (i.e. no spectral translation). # If there is a value in the spectrans keyword then find it in the (new) list of # the current set (which can also be a new set). Set this item to the current # item in the menu with spectral translations. Finally, if there is a translation given, # tell it the FITSimage object. #spectrans = gipsy.usertext("SPECTRANS=", default=2, defval=None) tabinf.spectransmenu.clear() tabinf.spectransmenu.addItem("Native") if gipsyset.projection.altspec is None: gipsyset.projection.altspec = [] for trans, unit in gipsyset.projection.altspec: s = "%s (%s)"%(trans.split('-')[0], unit) tabinf.spectransmenu.addItem(s) vmin = vmax = '' if gipsyset.has_key('DATAMIN'): vmin = gipsyset['DATAMIN'] tabinf.clipheadmin.setText("%8g"%vmin) else: tabinf.clipheadmin.setText(notfound) if gipsyset.has_key('DATAMAX'): vmax = gipsyset['DATAMAX'] tabinf.clipheadmax.setText("%8g"%vmax) else: tabinf.clipheadmax.setText(notfound) c = [vmin, vmax] clips = gipsy.userreal("CLIPS=", default=2, defval=c, nmax=2) if len(clips) == 1: clips.append(None) vmin, vmax = clips # Set defaults for the slice panels (per tab) s = "0:%d"%(len(gipsyset.subsets)-1) tabinf.slicesPanelX.setText(s) tabinf.slicesPanelY.setText(s) gipsyset.close() return def finishedLoading(self, tabinf): #--------------------------------------------------------------------------- # Purpose: Define a function that can be used as a callback after loading # images, to set cursor and some parameters. # # It is a callback, so we don't know when this function is executed. # But it should write data min. & max. values if the user required this. # In the mean time we could have switched to another tab. # Therefore we cannot use: n = self.datatabs.currentIndex(). So this method # is specified as a callback with an argument. In our case, this is # the tabinfo object that was used to store the new cube (after a call # to the append() method of the cubes container. #--------------------------------------------------------------------------- #nt = self.datatabs.count() #for i in range(nt): # tabinf = self.getTabInfo(i) #if tabinf.clipfromdata.isChecked() or tabinf.clipfrommeanrms.isChecked(): dmin = tabinf.cube.datmin if not (dmin is None): tabinf.clipdatamin.setText("%8g"%(dmin)) dmax = tabinf.cube.datmax if not (dmax is None): tabinf.clipdatamax.setText("%8g"%(dmax)) mean = tabinf.cube.mean rms = tabinf.cube.rms if mean and rms: s = float(str(tabinf.cliprmsmin.text())) tabinf.clipmeanmin.setText("%8g"%(mean-s*rms)) s = float(str(tabinf.cliprmsmax.text())) tabinf.clipmeanmax.setText("%8g"%(mean+s*rms)) if tabinf.clipfromuser.isChecked(): s = str(tabinf.clipusermin.text().trimmed()) if not s: if not (dmin is None): tabinf.clipusermin.setText("%8g"%(dmin)) s = str(tabinf.clipusermax.text().trimmed()) if not s: if not (dmax is None): tabinf.clipusermax.setText("%8g"%(dmax)) s = str(tabinf.pixelaspectratio.text().trimmed()) if not s: asp = tabinf.cube.imagesinthiscube[0].aspect tabinf.pixelaspectratio.setText("%g"%asp) # At this moment we know the total number of stored images so we can # update the movie slider rangemax = len(self.movieimages.annimagelist) self.movieslider.setRange(0, rangemax-1) tickinterval = 10 if rangemax > 1: tickinterval = nint(rangemax/10.0) + 1 self.movieslider.setTickInterval(tickinterval) self.tabs.setTabEnabled(3, True) # Make tab for color editing available self.tabs.setTabEnabled(4, True) # Same for slice panels # Make sure that the slider range is adjusted before setting # the required image. # Set first image of calling tab if self.currenttabinf.cube: imnr = self.currenttabinf.cube.movieframeoffset else: imnr = 0 # TODO ?? Is already set in maputils... self.setCurrentImage(imnr) if not self.plotcommandcb: key = "PLC=" self.plotcommandcb = gipsy.InputCallback(self.plotcommand, key, nmax=2048) key = "MARKER=" self.markercb = gipsy.InputCallback(self.markercommand, key, nmax=2048) #key = "MOVIEFRAME=" #gipsy.InputCallback(self.g_setThisImage, key, function=gipsy.userint) def readClipLevels(self, tabinf, gipsyset=None, rmsfactorchanged=False): #--------------------------------------------------------------------------- # Purpose: Read clip levels from settings on this tab #--------------------------------------------------------------------------- clipmn = [4,5] clipmode = 0 # It could be that the image is displayed with the clip levels from the # header. In that case the data minimum and maximum are not set yet. # This is the place and time to do that, at least when it is requested if tabinf.cube: if tabinf.clipfromdata.isChecked() or tabinf.clipfrommeanrms.isChecked(): if tabinf.cube.datmin is None or tabinf.cube.datmax is None: tabinf.cube.imagestats() dmin = tabinf.cube.datmin if not (dmin is None): tabinf.clipdatamin.setText("%8g"%(dmin)) dmax = tabinf.cube.datmax if not (dmax is None): tabinf.clipdatamax.setText("%8g"%(dmax)) # Call could come from changing one of the the rms factors # so update these always if the radio button for data or mean.rms # is checked. mean = tabinf.cube.mean rms = tabinf.cube.rms if not (mean is None) and not (rms is None): try: s = float(str(tabinf.cliprmsmin.text())) except: s = 0.0 tabinf.clipmeanmin.setText("%8g"%(mean-s*rms)) try: s = float(str(tabinf.cliprmsmax.text())) except: s = 0.0 tabinf.clipmeanmax.setText("%8g"%(mean+s*rms)) if tabinf.clipfromuser.isChecked(): try: vmin = float(str(tabinf.clipusermin.text())) # From QString -> str -> number except: vmin = None try: vmax = float(str(tabinf.clipusermax.text())) except: vmax = None clipmode = 0 elif tabinf.clipfromheader.isChecked(): vmin = str(tabinf.clipheadmin.text()) if vmin == notfound: vmin = None elif vmin != notset: vmin = float(vmin) else: # Not set by default settings if gipsyset: if gipsyset.has_key('DATAMIN'): vmin = gipsyset['DATAMIN'] gipsy.anyout("Viewer uses value from header: DATAMIN=%g"%vmin, 16) tabinf.clipheadmin.setText("%.4g"%vmin) else: tabinf.clipheadmin.setText(notfound) vmin = None vmax = str(tabinf.clipheadmax.text()) if vmax == notfound: vmax = None elif vmax != notset: vmax = float(vmax) else: # Not set by default settings if gipsyset: if gipsyset.has_key('DATAMAX'): vmax = gipsyset['DATAMAX'] gipsy.anyout("Viewer uses value from header: DATAMAX=%g"%vmax, 16) tabinf.clipheadmax.setText("%.4g"%vmax) else: tabinf.clipheadmax.setText(notfound) vmax = None clipmode = 0 elif tabinf.clipfromdata.isChecked(): if tabinf.cube: # Something is already displayed vmin = str(tabinf.clipdatamin.text().trimmed()) if vmin == notset: vmin = None else: vmin = float(vmin) vmax = str(tabinf.clipdatamax.text().trimmed()) if vmax == notset: vmax = None else: vmax = float(vmax) else: vmin = vmax = None clipmode = 1 elif tabinf.clipfrommeanrms.isChecked(): if tabinf.cube: # Image is already displayed. Re-use setting vmin = str(tabinf.clipmeanmin.text().trimmed()) if vmin == notset: vmin = None else: vmin = float(vmin) vmax = str(tabinf.clipmeanmax.text().trimmed()) if vmax == notset: vmax = None else: vmax = float(vmax) else: vmin = vmax = None clipmode = 2 clipmn = (float(str(tabinf.cliprmsmin.text())), float(str(tabinf.cliprmsmax.text()))) gipsy.anyout("Viewer uses value for cliplevels %s %s"%(str(vmin), str(vmax)), 16) return vmin, vmax, clipmode, clipmn def makeplot(self, tabinf=None): #--------------------------------------------------------------------------- # This is the most important method of this class. It reads data from # a given source. This can be either a FITS file or a GIPSY data set. # The source can be either 2 or 3 dimensional data. # TODO: A file browser is being created which always returns a # string with a valid file en axes specification. For 3 dimensional # sets, a so called repeat axis must be given. # # Some considerations: # # We display images and movies with software from module maputils, # which is part of the Kapteyn Package. This module appends the data # from given sources to a list of movies. #--------------------------------------------------------------------------- tabinf.datainput.trigger() # Add filename to history filename = str(tabinf.datainput.text()) # Is QString if from Line editor box = str(tabinf.boxinput.text()) if filename == '': self.showErrorMessage("Nothing to do", color='green') return # Nothing to do if self.myCubes is None: # No container for cubes yet self.myCubes = maputils.Cubes(self.fig, toolbarinfo=True, printload=False, helptext=False, imageinfo=False, callbackslist={'memory' :self.memoryStatus.setText, #'movchanged' :self.movieinfo, 'progressbar':self.progressBar}) self.movieimages = self.myCubes.movieimages self.movieimages.addcallbacks({'movchanged' :self.movieinfo, 'cubechanged':self.cubechanged }) if not self.fitsfile is None: del self.fitsfile self.fitsfile = None hdunum = 0 minimalhead = tabinf.minimalhead.isChecked() try: # Nothing for hdu and alternate header yet. gipsyset = ioutils.WCSset(filename, create=False, write=False, gethdu=lambda x: hdunum, minimal=minimalhead) #getalt=lambda x,y: altwcs) except Exception, errmes: # Could not make a gds set. Display the reason why. e = "'ioutils.WCSset' fails: " + str(errmes) if e == '': e = 'Unknown error. Perhaps image part is empty?' self.showErrorMessage(e, widget=tabinf.datainput) return # ioutils.WCSset cannot check on required dimensionality, so we need # to check it here. subdim = gipsyset.ndims(subset=True) setdim = gipsyset.ndims() if subdim != 2: if subdim <= 1: if setdim < 2: self.showErrorMessage("Dimension data is %d and must be at least 2"%setdim, widget=tabinf.datainput) return else: self.showErrorMessage("Image must have 2 axes", widget=tabinf.datainput) return else: self.showErrorMessage("Image cannot have more than 2 axes", widget=tabinf.datainput) return # A possible good candidate, so add to recent file self.addRecentFile(filename) # Read if user want to swap the image axes. if tabinf.swapped.isChecked(): gipsyset.swapaxes() #print gipsyset.projection.source self.fitsfile = maputils.FITSimage(externalheader=gipsyset.projection.source, externaldata=gipsyset.image, externalname=gipsyset.dataname) if gipsyset.slicepositions: slpos = gipsyset.slicepositions[0] else: slpos = None self.fitsfile.set_imageaxes(gipsyset.projaxnum[0], gipsyset.projaxnum[1], slicepos=slpos) # Handle the box try: ## and self.box: gblo, gbhi = gipsyset.setwcsbox(box) # Limits in grids! # We need pixels in myCubes.append blo = gipsyset.subproj.grid2pixel(tuple(gblo)) bhi = gipsyset.subproj.grid2pixel(tuple(gbhi)) if not box: s = "%d %d %d %d"%(gblo[0], gblo[1], gbhi[0], gbhi[1]) tabinf.boxinput.setText(s) except Exception, errmes: self.showErrorMessage(str(errmes), widget=tabinf.boxinput) return gipsy.anyout("BOX from setwcsbox in pixels %s %s"%(str(blo), str(bhi)), 16) # Spectral translations Read current value from spectral translations menu # in this tab. The menu is filled in the setdefaults() method. if tabinf.spectransmenu.count() > 0: # Is there a filled list? ci = tabinf.spectransmenu.currentIndex() if ci > 0: # Then it could be that a user wanted to set an alternative. # Convert QString to str. Remove units part and add questionmark extension: self.fitsfile.spectrans = str(tabinf.spectransmenu.currentText()).split()[0] + "-???" # TODO: In de slicemessage info staan ook de ???. Weghalen daar! gipsy.anyout("Debug: In makeplot() is selected spectrans%s"%(self.fitsfile.spectrans), 16) else: # Fill menu with translations tabinf.spectransmenu.clear() tabinf.spectransmenu.addItem("Native") if gipsyset.projection.altspec is None: gipsyset.projection.altspec = [] for trans, unit in gipsyset.projection.altspec: s = "%s (%s)"%(trans.split('-')[0], unit) tabinf.spectransmenu.addItem(s) # Get current clip level settings and other settings vmin, vmax, clipmode, clipmn = self.readClipLevels(tabinf, gipsyset) hasgraticule = tabinf.hasgraticule.isChecked() hascolbar = tabinf.hascolbar.isChecked() pas = str(tabinf.pixelaspectratio.text()).strip() if pas: pixelaspectratio = eval(pas) else: pixelaspectratio = None label = 'MF_' + str(id(tabinf)) # Unique id for main frame fr = self.fig.add_subplot(1,1,1,label=label, frameon=False) self.tabs.setTabEnabled(3, False) # Make tab for color editing unavaiable self.tabs.setTabEnabled(4, False) # Same for slice panels # We create a new cube and append it to the cube container. # The result of this call is the new cube, which we make an attribute # of the tabinfo object (tabinf). tabinf.cube = self.myCubes.append(fr, self.fitsfile, gipsyset.projaxnum, gipsyset.slicepositions, pxlim=(blo[0], bhi[0]), pylim=(blo[1], bhi[1]), vmin=vmin, vmax=vmax, hasgraticule=hasgraticule, gridmode=True, hascolbar=hascolbar, pixelaspectratio = pixelaspectratio, clipmode=clipmode, clipmn=clipmn, callbackslist={ 'slope' :self.updateColorSlope, 'offset' :self.updateColorOffset, 'lut' :self.updateColorLut, 'inverse' :self.updateInverseLut, 'scale' :self.updateColorScale, 'blankcol':self.updateBlankColor, 'exmes' :self.setPositionMessage, 'waitcursor' :lambda : QApplication.setOverrideCursor(Qt.WaitCursor), 'resetcursor':QApplication.restoreOverrideCursor, 'finished' :lambda : self.finishedLoading(tabinf)}) # Notes on the lambda expression # def f(x): # print x # F = lambda :f(3) # F() --> 3 # f(4) --> 6 # F(4) --> () takes no arguments (1 given) # So in maputils there are no arguments for method finishedLoading() # For setting and resetting a QT cursor, we also use a callback. # Each myCubes.append() call starts its own 'waitcursor'. # So each cube that is finished loading # should reset it. This can be done with callbacks for maputils because # the loading method knows when to set and reset a cursor. # The finishedLoading() method is not suitable because if loading processes # are queued, only one callback will be generated. Then there are not # enough cursor resets. # In drawImages() the cursor can be reset if this method failed. # For slice panels, we set a default, if this default has not been set yet. s = "0:%d"%(len(gipsyset.subsets)-1) panels = str(tabinf.slicesPanelX.text()) if not panels: tabinf.slicesPanelX.setText(s) panels = str(tabinf.slicesPanelY.text()) if not panels: tabinf.slicesPanelY.setText(s) # We want the filename as a tooltip for the tab # There is not a straight correspondence between the tabs # in self.datatabs and out tabinf.tid number, because the latter # can be arbitrary. We compare the tabInfo objects of the # self.datatabs tabs with the current tabInfo object. # If those are the same, we have the right tab to set the # required tooltip nt = self.datatabs.count() for i in range(nt): tabinf2 = self.getTabInfo(i) if tabinf2.tid == tabinf.tid: self.datatabs.setTabToolTip(i, filename) break return True """ def addgrat(self): # Not used at the moment graticule = self.grid_cb.isChecked() if graticule: self.grat = self.annim.Graticule() self.grat.setp_axislabel(fontsize=7) self.grat.plot(self.axes) else: if not self.grat is None: self.grat.frame.clear() self.fig.delaxes(self.grat.frame) self.grat = None # TODO: was: gipsy.wkey("CSLOPE= %g"%(self.annim.cmap.slope)) # TODO: was: gipsy.wkey("COFFSET= %g"%(self.annim.cmap.shift)) #self.on_draw() #this is done by the wkeys """ def save_plot(self): #--------------------------------------------------------------------------- # This is a method which is called from the Menu bar to save the current # figure. But its functionality is the same as that from Matplotlib's # save button in the toolbar of the mpl canvas. Therefore we copied # the procedure of pressing key 's' in backend_bases.py #--------------------------------------------------------------------------- self.canvas.toolbar.save_figure(self.canvas.toolbar) """ file_choices = "PNG (*.png)|*.png" path = unicode(QFileDialog.getSaveFileName(self, 'Save file', '', file_choices)) if path: self.canvas.print_figure(path, dpi=self.dpi) # Show a message in the status bar for a short while. self.statusBar().showMessage('Saved to %s' % path, 2000) """ def on_about(self): #------------------------------------------------------------------------ # In menu 'help' in the upper menu bar, we show an 'about' # button which displays an about message with a.o. version numbers. #------------------------------------------------------------------------ msg = "VISIONS version %s (14 Dec 2012)
\ Viewer and plotter of FITS & GIPSY data.
\ Shows movie of selected images.
\ Plots slices of data cubes.

\ \ \ \ \ \ \ \ \ \
Python:%s
QT:%s
PyQT:%s
Matplotlib:%s
PyFITS:%s
NumPy:%s
GIPSY:%s
Platform:%s

\ Author: M.G.R. Vogelaar (gipsy@astro.rug.nl)

\

\ Copyright (c): Kapteyn Astronomical Institute,
\ University of Groningen, The Netherlands

\ "%(__version__, platform.python_version(), QT_VERSION_STR, PYQT_VERSION_STR, MPL_VERSION_STR, PYFITS_VERSION_STR, NUMPY_VERSION_STR, 'n.a.', platform.system()) QMessageBox.about(self, "About the viewer", msg.strip()) def contextHelp(self, wid, anchor='', xsize=700, ysize=800): #--------------------------------------------------------------------------- # Purpose: Create a new pop-up window with help information # # Inspect the dictionary with helpforms # 'wid' is the widget id and is used here as a key. If the form already # exists, then a request was done from the same source (e.g. a button) # and the window is closed. The dictionary with open help windows # is maintained in the closeEvent() method of the form #--------------------------------------------------------------------------- global helpforms if helpforms.has_key(wid): # Then the form exists and must be closed form = helpforms[wid] form.close() # Call the redefined closeEvent() method of this form #form.deleteLater() #del self.helpforms[wid] else: url = ':index.html' if anchor: url += '#%s'%anchor form = HelpForm(url, parent=self, widgetid=wid, xsize=xsize, ysize=ysize) helpforms[wid] = form form.show() def helpButton(self, anchor, buttonname='?', xsize=700, ysize=400): #--------------------------------------------------------------------------- # Purpose: Create a button which opens a context sensitive help # # Probably will be replaced by "What's this?" messages. #--------------------------------------------------------------------------- helpbut = QPushButton(buttonname) #helpbut.setMaximumWidth(20) idhb = id(helpbut) callback = lambda anchor=anchor, idhb=idhb, xsize=xsize, ysize=ysize :self.contextHelp(idhb, anchor, xsize, ysize) self.connect(helpbut, SIGNAL('clicked()'), callback) return helpbut def setspectrans(self, i, widget): #--------------------------------------------------------------------------- # Purpose: Change the default spectral translation for the images in # cube that corresponds tab from which this call was triggered. # # Note: The connection between combobox and this function is a bit # special. The signals are methods that supply either text or # an integer. But we want to identify the tab too. So with a # lambda function we defined two parameters. The first is an # integer and the second is a widget (a tab in our case). # We prefer to use the combobox' integer value instead of the # text, because with the index we can find whether the default # is selected or a translation. #--------------------------------------------------------------------------- index = self.datatabs.indexOf(widget) tabinf = self.getTabInfo(index) if tabinf.cube: ci = tabinf.spectransmenu.currentIndex() if ci == 0: st = None else: # Convert QString to str. Remove units part and add questionmark extension st = str(tabinf.spectransmenu.currentText()).split()[0] + "-???" gipsy.anyout("Change the spectral translation of %s to %s"%(tabinf.keyword, str(st)), 16) self.myCubes.set_spectrans(tabinf.cube, st) #self.setCurrentImage(self.myCubes.movieimages.indx) # Refresh current image def on_draw(self): #--------------------------------------------------------------------------- # Purpose: Do a canvas draw(). # # The method has a parameter 'resize'. Only for resizes there must be done # extra updating (e.g. side panels). #--------------------------------------------------------------------------- # Redraws the figure self.canvas.draw() def drawImages(self, cb): #--------------------------------------------------------------------------- # Purpose: User pushed Draw button to draw the images. #--------------------------------------------------------------------------- # The early setting of self.currenttabinf is necessary because # we don't know when the loading of images is ready. # When it is ready, the finishedLoading() method is # called, which needs this setting. self.currenttabinf = cb.tabinf if cb.tabinf.cube: self.cleanData() # Cleans and replots also with new one # In this case, all plots are remade, but in order of the # tabinfs. We would like to end up with the first image of the # cube that belongs to the current tab. # We don't know when the loading is finished. So we need # it in the finishedLoading method nt = self.datatabs.count() for i in range(nt): tabinf = self.getTabInfo(i) if self.makeplot(tabinf=tabinf): # Update corresponding GIPSY keyword gipsy.wkey("%s%s"%(tabinf.keyword, str(tabinf.datainput.text()))) gipsy.wkey("%s%s"%(tabinf.boxkeyword, str(tabinf.boxinput.text()))) else: # Clean start if self.makeplot(tabinf=cb.tabinf): # Update corresponding GIPSY keyword gipsy.wkey("%s%s"%(cb.tabinf.keyword, str(cb.tabinf.datainput.text()))) gipsy.wkey("%s%s"%(cb.tabinf.boxkeyword, str(cb.tabinf.boxinput.text()))) # If all plots are ok then Cursor will be reset by finishedLoading() def showHeader(self, widget): #--------------------------------------------------------------------------- # Purpose: Pop up window with meta information about data source # # The source of the data is the text that is found in the # lineedit field of the active data tab. For this tab, data is retrieved # which contains a pointer to the lineedit widget, so that the text # can be read. In method getHeader() we distinguish FITS # and GDS data and find the Meta data in the header of the FITS file # or the header of the GDS file. The latter can also contain meta data # at subset (slice) level, whether FITS data only has single meta data in # a header data unit. #--------------------------------------------------------------------------- global helpforms widgetid = id(widget) if helpforms.has_key(widgetid): # Then the form exists and must be closed form = helpforms[widgetid] form.close() # Call the redefined closeEvent() method of this form return n = self.datatabs.indexOf(widget) tabinf = self.getTabInfo(n) dataset = tabinf.datainput.text() result = getHeader(dataset, self.showErrorMessage) if result: # Exception messages for previous method are displayed in statusbar text, title = result # Make it an independent window so that it does not stay # at top of the parent. We do this by setting the parent to None text = 'Information from the GIPSY (pseudo) header
' + text if tabinf.cube: text += 'Information about the sky system:
' + tabinf.cube.fitsobj.str_wcsinfo()+'
'+\ 'More axes FITS- and debug information:
' + tabinf.cube.fitsobj.str_axisinfo(long=True) +'
' form = HeaderForm(text, title, parent=self, widgetid=widgetid) form.show() helpforms[widgetid] = form def g_setThisImage(self, cb): #--------------------------------------------------------------------------- # Purpose: Keyword handler for MOVIEFRAME= # # A movie frame can also be set with a GIPSY keyword. This # method reads the keyword contents and sends it to the method # that updates the image, slider and movie number label. #--------------------------------------------------------------------------- movieframenr = gipsy.userint(cb.key, default=2, defval=0) # Set the movie slider. It will trigger the update of the image self.movieslider.setSliderPosition(movieframenr) #self.setThisImage(movieframenr, slider=False) def g_fillDataInput(self, cb): #--------------------------------------------------------------------------- # Purpose: Keyword handler for DATASETn= # # The only purpose of this method is to fill the data input field with # the contents of the keyword. #--------------------------------------------------------------------------- cb.datainput.setText(gipsy.usertext(cb.key, default=2)) def g_fillBoxInput(self, cb): #--------------------------------------------------------------------------- # Purpose: Keyword handler for BOXn= # # If user changes box, update the box input field. The box is updated is # return if editing is finished (return pressed or out of focus). # Nothing is triggered because the contents is read later if a plot # is made. #--------------------------------------------------------------------------- cb.boxinput.setText(gipsy.usertext(cb.key, default=2)) def movieaction(self, action): #--------------------------------------------------------------------------- # Purpose: Send a key to the movieloop control panel in module maputils. # # Use the gui buttons for single step, start and stop. They send a character # which is used as key for the control panel for movie loops. #--------------------------------------------------------------------------- if not self.movieimages: return if action != 'start' and self.startmovie.isChecked(): self.startmovie.setChecked(False) if action != 'startback' and self.startmovieback.isChecked(): self.startmovieback.setChecked(False) self.movieimages.controlpanel(None, externalkey=action) def cubechanged(self, annim): #--------------------------------------------------------------------------- # Purpose: Callback for maputils method (see myCubes.append()) to # retrieve color information after changing cubes in a movie #--------------------------------------------------------------------------- cmap = annim.image.im.cmap self.slider_slope.blockSignals(True) self.slider_slope.setSliderPosition(cmap.slope) self.slider_slope.blockSignals(False) self.slider_offset.blockSignals(True) self.slider_offset.setSliderPosition(cmap.shift) self.slider_offset.blockSignals(False) self.lutlist.blockSignals(True) lutname = cmap.source self.lutlist.setCurrentIndex(maputils.cmlist.colormaps.index(lutname)) self.lutlist.blockSignals(False) scalename = cmap.scale.lower() self.scalelist.blockSignals(True) i = self.scalelist.findText(QString(scalename)) self.scalelist.setCurrentIndex(i) self.scalelist.blockSignals(False) # Finally, set the color for blank/bad values if cmap.bad_set: col = cmap.bad_val[0] # Is tuple: (color, alpha) i = maputils.Annotatedimage.blankcols.index(col) else: i = maputils.Annotatedimage.blankcols_default self.blankcolor.blockSignals(True) self.blankcolor.setCurrentIndex(i) self.blankcolor.blockSignals(False) cube = self.myCubes.cubelist[annim.cubenr] cliplo = cube.vmin cliphi = cube.vmax self.setclipsformLo.blockSignals(True) self.setclipsformLo.setText("%g"%cliplo) self.setclipsformLo.blockSignals(False) self.setclipsformHi.blockSignals(True) self.setclipsformHi.setText("%g"%cliphi) self.setclipsformHi.blockSignals(False) def movieinfo(self, info): #--------------------------------------------------------------------------- # Purpose: Callback for maputils method (see myCubes.append()) to # retrieve information after changing an image. # # The retrieved index and the current cube number are enough to # calculate the image number with respect to the cube's list # with images. If a tab changes, this 'lastimagenr' is used to restore # the image number with respect to the movielist. This is valid, also # when a cube is removed. TODO: Maar dat wordt niet gedaan als je tabs wisselt # dus kan je het echte imagenr onthouden. #--------------------------------------------------------------------------- # Put information about position on repeat axis of current image self.repeataxesinfo.setText(info.slicemessage) # TODO: Het werkt nu zo: trek je aan een slider, dan wordt setcurrentImage a # aangeroepen. Vul je wat in het frame input field, dan wordt dat ook gedaan maar via # een andere route waarbij eerst het getal wordt gecontroleerd. # Daarna zal de setimage routine in maputils deze callback uitvoeren. # (movieinfo, kan je beter moviechanged o.i.d. noemen). # In deze routine worden dan beide widgets geupdate, dat is dus # altijd 1 teveel. Ik wil hier onderscheid tussen frame input field en # movieslider. self.currentmovienr_label.setText(str(info.indx)) # Update label with current number # Update frame number input field. Suppress events, because the source # is not the line editor itself. self.framenredit.blockSignals(True) self.framenredit.setText(str(info.indx)) self.framenredit.blockSignals(False) if not self.eventfrommovieslider: # This is a bit nasty code. Changing the movieslider will change # the current image. But after changing an image, a callback # (this method, i.e. movieinfo()) will be executed and the slider # will be changed. This causes a chain reaction if we don't block # signals if methods are callbacks executed in maputils. # Note that self.movieslider.setTracking(False) does not # block the valueChanged(int) signal. self.movieslider.blockSignals(True) self.movieslider.setSliderPosition(info.indx) self.movieslider.blockSignals(False) # We need to keep track of the last image viewed in a cube because # if we jump (in the calling environment) to another cube and back, # there should be a way to restore the last displayed image for this # cube. C = self.myCubes.cubelist[info.cubenr] C.lastimagenr = info.indx - C.movieframeoffset self.eventfrommovieslider = None # Reset def getCurrentImage(self): #--------------------------------------------------------------------------- # Return the Annotated image that is currently displayed. If there are # no images loaded or some other problem occurred, None is returned #--------------------------------------------------------------------------- try: indx = self.myCubes.movieimages.indx # Current image on display annim = self.myCubes.movieimages.annimagelist[indx] return annim except: return None def setCurrentImage(self, imnum): #--------------------------------------------------------------------------- # Purpose: Set an image by its index #--------------------------------------------------------------------------- if not self.movieimages: return else: self.movieimages.setimage(imnum) # Is the same as self.myCubes.movieimages def setThisImage(self, imnr, slider): #--------------------------------------------------------------------------- # Purpose: Movie slider, frame input field or keyword requested a # new movie on display. #--------------------------------------------------------------------------- # Set a frame number using the frame input field or slider value # The method is also connected to a GIPSY keyword via method # g_setThisImage # TODO: we kunnen met een wkey het gipsy keyword zetten (MOVIEFRAME=) if not self.movieimages: return if not slider: try: # A user could have entered weird things, so do a try except. imnr = int(self.framenredit.text()) # Reset the input field to empty because it is only actual at # one moment. It is not changed by other actions like slider movements. #self.framenredit.setText("") except: pass self.movieimages.setimage(imnr) # We set the 'eventfrommovieslider' attribute. The callback that # is triggered after maputils did set the new image, resets it to None. # That is because we ...... self.eventfrommovieslider = slider def setMovieSpeed(self, framespersec=None): #--------------------------------------------------------------------------- # Purpose: Set refresh rate for image movie # # Notes: Note that if the number is given by the edit field, the expression # is evaluated using Python's eval() function. So one can use # math to enter the speed e.g. 1/10 to get 1 frame per 10 seconds. #--------------------------------------------------------------------------- if not self.movieimages: return if framespersec: frs = 2.0**(framespersec/100.) # More resolution in slow speeds self.movieimages.setspeed(frs) #self.speedslider_label2.setText("%3d"%framespersec) self.moviespeededit.blockSignals(True) self.moviespeededit.setText(str("%.1f"%frs)) self.moviespeededit.blockSignals(False) else: s = str(self.moviespeededit.text()) frs = eval(s) self.movieimages.setspeed(frs) try: framespersec = 100.0*log10(frs)/log10(2.0) if 50 < framespersec < 0: return except: return gipsy.anyout("Via speededit framespersec(log)=%f"%(framespersec), 16) self.speedslider.blockSignals(True) self.speedslider.setValue(framespersec) self.speedslider.blockSignals(False) def setMovieFrames(self): #--------------------------------------------------------------------------- # Purpose: Set the frame numbers (0 based) that you will include in the # movie. The keyword is connected to method setloopframes(). Look there # for more documentation. #--------------------------------------------------------------------------- if not self.movieimages: return s = "LOOPFRAMES=%s"%(self.movieframes.text()) gipsy.wkey(s) def drawGraticule(self, i, widget): #--------------------------------------------------------------------------- # Purpose: #--------------------------------------------------------------------------- if not self.myCubes: return index = self.datatabs.indexOf(widget) tabinf = self.getTabInfo(index) on_off = tabinf.hasgraticule.isChecked() try: self.myCubes.set_graticule_on(tabinf.cube, on_off) except Exception, errmes: e = "Cannot create graticule: " + str(errmes) self.showErrorMessage(e) # Refresh by resetting current image #imnr = self.myCubes.movieimages.indx #self.setCurrentImage(imnr) def setAspectRatio(self, widget): #--------------------------------------------------------------------------- # Purpose: # # Routine in maputils resets frame of current image #--------------------------------------------------------------------------- if not self.myCubes: return index = self.datatabs.indexOf(widget) tabinf = self.getTabInfo(index) pas = str(tabinf.pixelaspectratio.text()).strip() if pas: pixelaspect = eval(pas) needtoreset = False else: pixelaspect = None needtoreset = True self.myCubes.set_aspectratio(tabinf.cube, pixelaspect) if needtoreset: tabinf.pixelaspectratio.setText(str(tabinf.cube.pixelaspectratio)) def setSkyout(self, sky, widget): #--------------------------------------------------------------------------- # Purpose: Change the celestial system so that graticule grid and -labels # and mouse positions, are valid for the new celestial system. #--------------------------------------------------------------------------- if not self.myCubes: return gipsy.anyout("Set sky to %s, type=%s"%(str(sky), type(sky)), 16) if sky == 'Native': sky = None else: sky = str(sky) # Was QString index = self.datatabs.indexOf(widget) tabinf = self.getTabInfo(index) try: self.myCubes.set_skyout(tabinf.cube, sky) except Exception, errmes: e = "Cannot change sky sys.: " + str(errmes) self.showErrorMessage(e) def drawColorbar(self, i, widget): #--------------------------------------------------------------------------- # Purpose: #--------------------------------------------------------------------------- index = self.datatabs.indexOf(widget) tabinf = self.getTabInfo(index) on_off = tabinf.hascolbar.isChecked() if self.myCubes: self.myCubes.set_colbar_on(tabinf.cube, on_off) def setCrossHair(self, i): #--------------------------------------------------------------------------- # Purpose: #--------------------------------------------------------------------------- self.myCubes.set_crosshair(bool(i)) def getTabInfo(self, n): #--------------------------------------------------------------------------- # Purpose: #--------------------------------------------------------------------------- currenttabdata = self.datatabs.tabBar().tabData(n) return currenttabdata.toPyObject() # Back from QVariant to Python object """ def removeTab(self, index=-1): if (self.tabs.count() < 1): return if (index < 0): index = self.tabs.currentIndex() self.tabs.removeTab(index) gipsy.anyout("Removed tab with index %d"%index) """ def removeTab(self, widget): index = self.datatabs.indexOf(widget) self.datatabs.removeTab(index) gipsy.anyout("Removed tab with index %d"%index, 16) # TODO: nu moet je ook een aantal andere zaken opruimen zoals # de dictionary met setbrowsers def clearData(self, widget): index = self.datatabs.indexOf(widget) gipsy.anyout("Clear data from tab with index %d"%index, 16) tabinf = self.getTabInfo(index) #gipsy.anyout(str(tabinf.tid)) tabinf.datainput.setText("I am cleared") def changeTab(self, tabnr): #--------------------------------------------------------------------------- # Purpose: A tab has been changed. Change the image on display to the # last visited image of the cube that corresponds to this tab. # # Each cube has an attribute 'lastimagenr' which stores it last visited # image. We maintain this number in this application in method movieinfo() # because that is a callback in the maputils method that changes images. #--------------------------------------------------------------------------- tabinf = self.getTabInfo(tabnr) if tabinf and tabinf.cube: if hasattr(tabinf.cube, 'lastimagenr'): # Counter is cube based, not movie. Also save when a cube was previously removed imnr = tabinf.cube.movieframeoffset + tabinf.cube.lastimagenr gipsy.anyout("Changing image after changing tab: imnr=%d"%(imnr), 16) self.setCurrentImage(imnr) # TODO: Er is iets mis met lastimagenr. Dit is niet robuust. Onderzoek dit op # alle plaatsen in de demoviewer. Het attribuut komt niet voor in maputils # Is 't correct dat je in de demoviewer omgeving zo'n attribuut toevoegt. # Kan het zijn dat een tabwisseling tijdens het laadproces onveilig is? # File "/home/vogelaar/VIEWER/demoviewer.py", line 2745, in changeTab # imnr = tabinf.cube.movieframeoffset + tabinf.cube.lastimagenr # AttributeError: 'Cubeatts' object has no attribute 'lastimagenr' # Uncaught Python exception - FATAL def addDataTab(self, n=None): #--------------------------------------------------------------------------- # This method adds a tab with fields for entering data. # Parameter n is the proposed id of the tab. #--------------------------------------------------------------------------- class tabinfo: def __init__(self, tid, datainput, boxinput, spectransmenu, swapped, clipfromheader, clipheadmin, clipheadmax, clipfromdata, clipdatamin, clipdatamax, clipfrommeanrms, clipmeanmin, clipmeanmax, cliprmsmin, cliprmsmax, clipfromuser, clipusermin, clipusermax, keyword, boxkeyword, slicesPanelX, slicesPanelY, hasgraticule, hascolbar, skyoutlist, pixelaspectratio, minimalhead): self.tid = tid # Table id self.datainput = datainput self.boxinput = boxinput self.spectransmenu = spectransmenu # List with spectral translations self.swapped = swapped self.keyword = keyword # GIPSY keyword self.boxkeyword = boxkeyword self.slicesPanelX = slicesPanelX self.slicesPanelY = slicesPanelY self.hasgraticule = hasgraticule self.hascolbar = hascolbar self.skyoutlist = skyoutlist self.pixelaspectratio = pixelaspectratio self.minimalhead = minimalhead self.clipfromheader = clipfromheader self.clipheadmin = clipheadmin self.clipheadmax = clipheadmax self.clipfromdata = clipfromdata self.clipdatamin = clipdatamin self.clipdatamax = clipdatamax self.clipfrommeanrms = clipfrommeanrms self.clipmeanmin = clipmeanmin self.clipmeanmax = clipmeanmax self.cliprmsmin = cliprmsmin # Multiply rms with this number self.cliprmsmax = cliprmsmax # Multiply rms with this number self.clipfromuser = clipfromuser self.clipusermin = clipusermin self.clipusermax= clipusermax self.cube = None # A tab is associated with 1 cube # Get all the tab id's that are on display now nt = self.datatabs.count() tids = [] for i in range(nt): tabinf = self.getTabInfo(i) tids.append(tabinf.tid) # A tab id is given. Create this one, if it does not exist yet. # Otherwise, do nothing if n is not None: if n in tids or n < 0: # Exclude also negative id's return else: # There is no tab with this id, so add it tid = n else: # A user requested a new tab with the add tab button # which does not provide a proposed tab id. # Find the first available number starting from 1 # (so keep Data tab 0 alive under all circumstances) tid = 1 while tid in tids: tid += 1 if tid == 0: close = False else: close = True tab1 = QWidget() #tab1.min-width: 8ex;(40) tab1.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) #title = QLabel('Data %d'%tid) #title.setStyleSheet("QLabel { color: #0000FF }") #datalabel = QLabel('Data %d'%tid) #datalabel.setToolTip(help_data[0]) # Help text is defined at the beginning of the code openfile = QPushButton("&Compose") #openfile.setMaximumWidth(80) #openfile.setMinimumWidth(80) # Add a data browser for each tab self.setbrowsers[tid] = QtDataBrowser(self, missionTxt="Select two box axes to define an image", classDim=2) openfile.setToolTip("

Open file browser with selection of GIPSY sets and FITS files

") self.connect(openfile, SIGNAL('clicked()'), lambda tid=tid: self.openFileGui(tid)) # See also purpose line in method checkDefaults() # If a file name is entered (return/enter is pressed), then set # the defaults for that source only if this has not be done by # a previous data source. This check is done in method checkDefaults() # This check is also done when data sources are entered using the # file browser. We check whether the contents of field # tabinf.clipheadmin is set to the string 'not set'. This seems an # easy flag to check whether defaults are set or not. datainput = MyLineEdit() #datainput.setMinimumWidth(300) datainput.setHistory(self.lineEditHist) datainput.setToolTip(help_data[0]) # Help text is defined at the beginning of the code datainput.setWhatsThis(help_data[1]) self.connect(datainput, SIGNAL('returnPressed()'), self.checkDefaults) help_datainput = self.helpButton('data', "Help") help_datainput.setToolTip("Pop up manual") boxlabel = QLabel('Box') boxinput = MyLineEdit() help_boxinput = self.helpButton('box', "Box") boxinput.setToolTip(help_box[0]) boxinput.setWhatsThis(help_box[1]) #helpgroup = QGroupBox("Help") #helpgroup.setFlat(False) #helpgroup.setStyleSheet(self.groupstyle) boxbut = QPushButton("Copy box from display") boxbut.setToolTip("

Copy current box on display in box input field

") self.connect(boxbut, SIGNAL('clicked()'), lambda widget=tab1:self.getDisplayBox(widget)) spectransmenu = QComboBox() spectransmenu.setToolTip("

Select a spectral coordinate system for mouse position, slice information and coordinate labels

") #spectransmenu.setMaximumWidth(50) self.connect(spectransmenu, SIGNAL('activated(int)'), lambda i, widget=tab1:self.setspectrans(i, widget)) #self.connect(spectransmenu, SIGNAL('currentIndexChanged()'), lambda widget=tab1:self.setspectrans(widget)) spectrans_label = QLabel('Spectral') help_spectrans = self.helpButton('spectrans', "Spectral") # The callback for this button is set by a GIPSY keyword draw_button = QPushButton("&Draw") draw_button.setToolTip("

Draw the image defined by the parameters in this tab

") #self.connect(draw_button, SIGNAL('clicked()'), lambda widget=tab1:self.drawImages(widget)) header_button = QPushButton("&Header") header_button.setToolTip("

Show meta information for source in data input field.The data is extracted from FITS- or GIPSY header

") self.connect(header_button, SIGNAL('clicked()'), lambda widget=tab1:self.showHeader(widget)) swapped = QCheckBox("") swapped.setChecked(False) swapped.setToolTip("

Change X and Y axis in your current cube or image. Use the draw button to reload the data.

") #self.connect(self.swapped, SIGNAL('stateChanged(int)'), self.makeplot) clearbut = QPushButton("Clear") self.connect(clearbut, SIGNAL("clicked()"), lambda widget=tab1 :self.clearData(widget)) self.outputstyle = "QLabel { color: green }" clipleveloptions = QGroupBox("Origin clip levels") clipfromheader = QRadioButton("Header") clipfromheader.setChecked(True) clipfromheader.setToolTip("

Scale colors between these values found in the header

") clipfromdata = QRadioButton("Data") clipfromdata.setToolTip("

Check this box if you want to scale colors between the min, max values of the image data. If not set, it will be calculated if you activate the button.

") clipfrommeanrms = QRadioButton("Mean/rms") clipfrommeanrms.setToolTip("

Scale colors to data interval around the mean. The limits are set in terms of the rms of the data

") clipfromuser = QRadioButton("User") clipfromuser.setToolTip("

Scale colors between these image values

") self.connect(clipfromheader, SIGNAL('clicked(bool)'), self.setclipsRadioButtons) self.connect(clipfromdata, SIGNAL('clicked(bool)'), self.setclipsRadioButtons) self.connect(clipfrommeanrms, SIGNAL('clicked(bool)'), self.setclipsRadioButtons) self.connect(clipfromuser, SIGNAL('clicked(bool)'), self.setclipsRadioButtons) clipheadmin = QLabel(notset) clipheadmax = QLabel(notset); clipheadmax.setStyleSheet(self.outputstyle) clipdatamin = QLabel(notset); clipdatamin.setStyleSheet(self.outputstyle) clipdatamax = QLabel(notset); clipdatamax.setStyleSheet(self.outputstyle) clipmeanmin = QLabel(notset); clipmeanmin.setStyleSheet(self.outputstyle) clipmeanmax = QLabel(notset); clipmeanmax.setStyleSheet(self.outputstyle) for lab in [clipheadmin, clipheadmax, clipdatamin, clipdatamax, clipmeanmin, clipmeanmax]: lab.setStyleSheet(self.outputstyle) lab.setTextInteractionFlags(Qt.TextSelectableByMouse) cliprmsmin = MyLineEdit() #cliprmsmin.setMaximumWidth(15) cliprmsmin.setText("4") cliprmsmin.setToolTip("

Define the lower clip value as the data mean minus this number times the data rms of the current data

") cliprmsmin.setMaximumWidth(30) cliprmsmax = MyLineEdit() cliprmsmax.setText("5") cliprmsmax.setToolTip("

Define the upper clip value as the mean plus this number times the rms of the current data

") cliprmsmax.setMaximumWidth(30) #cliprmsmax.setMaximumWidth(15) clipusermin = MyLineEdit() clipusermin.setToolTip("

Enter the lower value of the image data interval in which you want to scale the colors

") clipusermax = MyLineEdit() clipusermax.setToolTip("

Enter the upper value of the image data interval
in which you want to scale the colors

") self.connect(clipusermin, SIGNAL('returnPressed()'), self.setclipsRadioButtons) self.connect(clipusermax, SIGNAL('returnPressed()'), self.setclipsRadioButtons) self.connect(cliprmsmin, SIGNAL('returnPressed()'), self.setclipsRadioButtons) self.connect(cliprmsmax, SIGNAL('returnPressed()'), self.setclipsRadioButtons) clipleveloptions.setFlat(False) clipleveloptions.setStyleSheet(self.groupstyle) clipgrid = QGridLayout() clipgrid.addWidget(clipfromheader, 0, 0) clipgrid.addWidget(clipheadmin, 0, 1) clipgrid.addWidget(clipheadmax, 0, 2) clipgrid.addWidget(clipfromdata, 1, 0) clipgrid.addWidget(clipdatamin, 1, 1) clipgrid.addWidget(clipdatamax, 1, 2) clipgrid.addWidget(clipfrommeanrms, 2, 0) #clipgrid.addWidget(clipmeanmin, 2, 1) #clipgrid.addWidget(clipmeanmax, 2, 2) rmsgroup = QGroupBox("") rmsgroup.setFlat(False) #rmsgroup.setStyleSheet(self.groupstyle) rmsgrid = QGridLayout() rmsgrid.addWidget(QLabel("mean -"), 0, 0) rmsgrid.addWidget(cliprmsmin, 0, 1) rmsgrid.addWidget(QLabel("x rms ="), 0, 2) rmsgrid.addWidget(clipmeanmin, 0, 3) rmsgrid.addWidget(QLabel("mean +"), 1, 0) rmsgrid.addWidget(cliprmsmax, 1, 1) rmsgrid.addWidget(QLabel("x rms ="), 1, 2) rmsgrid.addWidget(clipmeanmax, 1, 3) rmsgroup.setLayout(rmsgrid) clipgrid.addWidget(rmsgroup, 2, 1, 1, 2) #clipgrid.addWidget(QLabel(QString("mean ± n.rms    n=")),3, 1) #clipgrid.addWidget(cliprmsmin, 3, 2) #clipgrid.addWidget(cliprmsmax, 3, 3) clipgrid.addWidget(clipfromuser, 5, 0) clipgrid.addWidget(clipusermin, 5, 1) clipgrid.addWidget(clipusermax, 5, 2) clipleveloptions.setLayout(clipgrid) setdefaults = QPushButton("Defaults") setdefaults.setToolTip("

Refresh default settings for current data

") slicesPanelX = MyLineEdit() slicesPanelX.setToolTip("

Set the range of images that you want to use to fill the horizontal slice panel. Use GIPSY syntax to enter ranges e.g.: 0:100

") slicesPanelX.setMaximumWidth(80) slicesPanelY = MyLineEdit() slicesPanelY.setToolTip("

Set the range of images that you want to use to fill the vertical slice panel. Use GIPSY syntax to enter ranges e.g.: 0:100

") slicesPanelY.setMaximumWidth(80) hasgraticule = QCheckBox("") hasgraticule.setChecked(False) hasgraticule.setToolTip("

Draw a grid with lines showing the world coordinate system

") self.connect(hasgraticule, SIGNAL("stateChanged(int)"), lambda i, widget=tab1 :self.drawGraticule(i,widget)) help_graticule = self.helpButton('graticules', "Graticule") pixelaspectratio = MyLineEdit() pixelaspectratio.setMaximumWidth(80) pixelaspectratio.setToolTip("

Set the pixel aspect ratio (CDELTy/CDELTx for spatial maps or nx/ny for others) for current image. An empty field resets the default

") self.connect(pixelaspectratio, SIGNAL('returnPressed()'), lambda widget=tab1 :self.setAspectRatio(widget)) skyoutlist = QComboBox() skyoutlist.setToolTip("

Set an alternative sky system which effects labels and mouse positions

") skysystems = ['Native', 'eq fk4-no-e', 'eq fk4', 'eq fk5', 'icrs', 'ecliptic', 'galactic', 'supergalactic'] # TODO: Als de kaart niet spatieel is, dan mag dit menu niet actief zijn. # De reden is dat in hybride kaarten niet met een alternatieve skyout kan worden gewerkt, omdat # wcslib dat niet ondersteunt. skyoutlist.addItems(skysystems) skyoutlist.setCurrentIndex(0) self.connect(skyoutlist, SIGNAL('currentIndexChanged(QString)'), lambda i, widget=tab1:self.setSkyout(i, widget)) hascolbar = QCheckBox("") hascolbar.setChecked(True) hascolbar.setToolTip("

Toggle color bar. The image will resize to adjust to the available display size (or remain fixed if you used the fixed zoom option)

") self.connect(hascolbar, SIGNAL("stateChanged(int)"), lambda i, widget=tab1 :self.drawColorbar(i,widget)) # A user can chose to use a minimal header to avoid conflicts with headers # that are not standard FITS minimalhead = QCheckBox("") minimalhead.setChecked(False) minimalhead.setToolTip("

Check this if you have an invalid FITS header, but still want to see the data. A minimal header is used for wcs transformations

") minimalhead.setMaximumWidth(20) mh_label = QLabel("m") mh_label.setMaximumWidth(20) hline = QFrame() hline.setFrameShape(QFrame.HLine) hline.setFrameShadow(QFrame.Sunken) grid = QGridLayout(tab1) grid.setRowStretch(0,1) #grid.addWidget(title, 0, 0, 1, 3) # Widget, Row, column label = 'Data %d'%tid datagroup = QGroupBox(label) #datagroup.setMinimumWidth(380) datagroup.setFlat(False) datagroup.setStyleSheet(self.groupstyle) datagrid = QGridLayout() datagrid.addWidget(openfile, 0, 0) datagrid.addWidget(datainput, 0, 1, 1, 3) datagrid.addWidget(mh_label, 0, 4) datagrid.addWidget(minimalhead, 0, 5) datagrid.addWidget(header_button, 3, 1) datagrid.addWidget(setdefaults, 3, 2) datagrid.addWidget(help_datainput, 3, 3) datagrid.addWidget(boxlabel, 4, 0) datagrid.addWidget(boxinput, 4, 1, 1, 3) datagrid.addWidget(boxbut, 5, 1, 1, 2) datagroup.setLayout(datagrid) grid.addWidget(datagroup, 1, 0, 1, 3) grid.setRowStretch(2,1) grid.addWidget(clipleveloptions, 3, 0, 1, 3) grid.setRowStretch(4,1) miscgroup = QGroupBox("Miscellaneous") miscgroup.setFlat(False) miscgroup.setStyleSheet(self.groupstyle) miscgrid = QGridLayout() miscgrid.addWidget(spectrans_label, 0, 0) miscgrid.addWidget(spectransmenu, 0, 1) miscgrid.addWidget(QLabel("Sky"), 0, 2) miscgrid.addWidget(skyoutlist, 0, 3) miscgrid.addWidget(QLabel('Graticule'), 1, 0) miscgrid.addWidget(hasgraticule, 1, 1) miscgrid.addWidget(QLabel('Colorbar'), 1, 2) miscgrid.addWidget(hascolbar, 1, 3) miscgrid.addWidget(QLabel('Swap axes'), 2, 0) miscgrid.addWidget(swapped, 2, 1) miscgrid.addWidget(QLabel('Aspect'), 2, 2) miscgrid.addWidget(pixelaspectratio, 2, 3) miscgrid.addWidget(QLabel('Xpanel'), 3, 0) miscgrid.addWidget(slicesPanelX, 3, 1) miscgrid.addWidget(QLabel('Ypanel'), 3, 2) miscgrid.addWidget(slicesPanelY, 3, 3) miscgroup.setLayout(miscgrid) grid.addWidget(miscgroup, 5, 0, 1, 3) grid.setRowStretch(6,2) #grid.addWidget(clearbut, 7, 1) grid.addWidget(draw_button, 7, 2) grid.setRowStretch(8,10) """ groupgrid = QGridLayout() groupgrid.addWidget(help_datainput, 1, 0) groupgrid.addWidget(help_boxinput, 1, 1) groupgrid.addWidget(help_spectrans, 2, 0) groupgrid.addWidget(help_graticule, 2, 1) helpgroup.setLayout(groupgrid) grid.addWidget(helpgroup, 9, 1, 1, 2) """ #grid.a(hline, 16, 1, 1, 2) """ grid.addWidget(hline, 16, 1, 1, 2) grid.addWidget(QLabel('Help'), 17,0) grid.addWidget(help_datainput, 17, 1) grid.addWidget(help_boxinput, 17, 2) grid.addWidget(help_spectrans, 18, 1) grid.addWidget(help_graticule, 18, 2) """ grid.setRowStretch(18,1) #grid.setColumnStretch(4,1) tabbar = self.datatabs.tabBar() # Where do we insert the new tab? pos = 0 if tid > 0: pos = len(tids) for i, p in enumerate(tids): if tid < p: pos = i break gipsy.anyout("Insert new tab at position %d"%pos, 16) n = tabbar.count() # TODO: waar is deze n voor nodig? self.datatabs.insertTab(pos, tab1, "Data %d"%(tid)) gipsy.anyout("Creating a tab with tid=%d"%tid, 16) if close: # Then it is not the first tab. These tabs can be removed. delbut = QPushButton("") delbut.resize(19,16) # The close icon is 16x16 pixels delbut.setIcon(QIcon(':/cross.png')) tabbar.setTabButton(pos, tabbar.RightSide, delbut) self.connect(delbut, SIGNAL("clicked()"), lambda widget=tab1 :self.removeTab(widget)) self.datatabs.setCurrentIndex(pos) # Set focus to new tab # Each tab for data input has its own line editor for entering # data sources (Either FITS or GDS). Each editor is associated with # a GIPSY keyword which starts with DATASET followed by a number # This number is transferred as an attribute of the callback and # can be used to read the appropriate lineditor to get the information # about the data. key = "DATASET%d="%tid boxkey = "BOX%d="%tid tabinf = tabinfo(tid, datainput, boxinput, spectransmenu, swapped, clipfromheader, clipheadmin, clipheadmax, clipfromdata, clipdatamin, clipdatamax, clipfrommeanrms, clipmeanmin, clipmeanmax, cliprmsmin, cliprmsmax, clipfromuser, clipusermin, clipusermax, key, boxkey, slicesPanelX, slicesPanelY, hasgraticule, hascolbar, skyoutlist, pixelaspectratio, minimalhead) tabbar.setTabData(pos, tabinf) # Now we filled the tabinfo object and can connect the callbacks # that need this object self.connect(setdefaults, SIGNAL('clicked()'), lambda : self.getfile(tabinf=tabinf)) # Set the GIPSY links for this tab cb = gipsy.KeyCallback(self.g_fillDataInput, key, schedule=True, datainput=datainput) #gipsy.QtLink(key, datainput, 'editingFinished()') triggerkey(key) # We want the input field filled if DATASETn= was pre-specified cb = gipsy.KeyCallback(self.g_fillBoxInput, boxkey, schedule=True, boxinput=boxinput) #gipsy.QtLink(key, boxinput, 'editingFinished()') triggerkey(boxkey) key = "DRAW%d="%tid cb = gipsy.KeyCallback(self.drawImages, key, schedule=True, tabinf=tabinf) gipsy.QtLink(key, draw_button, 'clicked()', compare=False) triggerkey(key) # We want the id because we need it to create translate an INSET= # keyword to DATASET#=, DRAW#= in method inset() return tid def create_main_frame(self): self.main_frame = QWidget() # Get the background color from GIPSY keyword BACKGROUND= bg = gipsy.usertext("BACKGROUND=", default=2, defval='k') # Create the mpl Figure and FigCanvas objects. # 5x4 inches, 100 dots-per-inch # self.dpi = 100 self.fig = Figure((5.0, 4.0), dpi=self.dpi, facecolor=bg) # Black background: add facecolor= '#000000' #self.fig = Figure((5.0, 4.0), dpi=self.dpi) # Black background: add facecolor= '#000000' self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self.main_frame) self.canvas.setFocusPolicy(Qt.StrongFocus) #ion() # Create the navigation toolbar, tied to the canvas # We want to replace its set_message() method by our own so # set it to None at first. self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame) self.mpl_toolbar.set_message = lambda x: None # We want that the keyboard focus is set to the mpl canvas as soon # the mouse cursor is in the canvas. This can be done by registering # callbacks for entering and leaving the Mpl canvas. Focus is # regained by using method grabKeyboard(), which is a QT method, so it is # available to our canvas object. Note that if somehow the keyboard focus # is not returned to the Gui, we should replace the grabKeyword() method # by a method the uses setFocus() and remembers which widget had the # last focus (using QWidget.focusWidget()) self.infigcb = self.canvas.mpl_connect('figure_enter_event', self.infig) self.outfigcb = self.canvas.mpl_connect('figure_leave_event', self.outfig) # Other GUI controls: # Labels for position information self.messenger = QLabel() # Create label and set initial text font = QFont() # We want a fixed width font and need a QFont object for that font.setPointSize(8) # Set font size #font.setBold(True) # Set other properties font.setFamily("Monospace") # Set the fixed width font self.messenger.setFont(font) # Apply font setting to label self.messenger.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # Place message horizontally in the center #self.messenger.setStyleSheet("QLabel { background-color: #eeeeee }") # Give text a default background self.messenger.setTextInteractionFlags(Qt.TextSelectableByMouse) # Allow text to be selected self.messenger.setFrameStyle(QFrame.Panel | QFrame.Sunken) # Put a frame around the label box self.messenger.setLineWidth(2) # Width of frame lines self.messenger.setFixedHeight(20) # Prevent vertical resizing self.repeataxesinfo = QLabel() # Create label and set initial text font = QFont() # We want a fixed width font and need a QFont object for that font.setPointSize(8) # Set font size #font.setBold(True) # Set other properties font.setFamily("Monospace") # Set the fixed width font self.repeataxesinfo.setFont(font) # Apply font setting to label self.repeataxesinfo.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # Place message horizontally in the center #self.messenger.setStyleSheet("QLabel { background-color: #eeeeee }") # Give text a default background self.repeataxesinfo.setTextInteractionFlags(Qt.TextSelectableByMouse) # Allow text to be selected self.repeataxesinfo.setFrameStyle(QFrame.Panel | QFrame.Sunken) # Put a frame around the label box self.repeataxesinfo.setLineWidth(2) # Width of frame lines self.repeataxesinfo.setFixedHeight(20) # Prevent vertical resizing # First tab contains data and box fields and is created at startup with addDataTab() #tab_widget = MyTabWidget() tab_widget = QTabWidget() tab_widget.setStyleSheet(self.labelstyle) tab_widget.setMinimumWidth(400) # TODO: Er is een probleem bij het weghalen van een tab. Er klaagt #dan een deschedule # First tab contains the data tabs tab1 = QTabWidget() self.connect(tab1, SIGNAL('currentChanged(int)'), self.changeTab) pushbut = QPushButton('') pushbut.setIcon(QIcon(':/newtab.png')) pushbut.setToolTip("

Add new tab for data input

") self.connect(pushbut, SIGNAL('clicked()'), self.addDataTab) # Next setting works not on all systems: #tab1.setTabPosition(QTabWidget.East) #tab1.setCornerWidget(pushbut, Qt.TopLeftCorner) tab1.setCornerWidget(pushbut, Qt.TopRightCorner) # New tabs (after the first which is not removeable) have a # remove icon which changes the default height of the tab. We want to # fix this, but we changed the default orientation, so we must fix the # width intead of the height. tab1.setStyleSheet("QTabBar::tab {min-width: 24px;}") datatabbar = tab1.tabBar() datatabbar.setTabsClosable(False) datatabbar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) #self.datatabbar = datatabbar self.datatabs = tab1 tab_widget.addTab(tab1, "Data") # Second tab for graticule and spectral translation #help_graticule = QPushButton("?") #help_graticule.setMaximumWidth(20) #self.connect(help_graticule, SIGNAL('clicked()'), lambda anchor='graticules' :self.contextHelp(id(help_graticule), anchor, 700,400)) self.draw_button2 = QPushButton("&Draw") #self.connect(self.draw_button2, SIGNAL('clicked()'), self.drawGraticule) #self.connect(self.grid_cb, SIGNAL('stateChanged(int)'), self.makeplot) self.smsliderX_label = QLabel('Smooth fac. X') self.smsliderX = QSlider(Qt.Horizontal) self.smsliderX.setToolTip("

Smooth current image in X. Value (in pixels) sets sigma of Gaussian used in convolution

") self.smsliderX.setRange(0, 5.0*blurscale) # To set the blur factor self.smsliderX.setValue(0) self.smsliderX.setTickInterval(10) self.smsliderX.setTracking(True) self.smsliderX.setTickPosition(QSlider.NoTicks) #self.smsliderX.setFixedWidth(175) # Connection to callback via GIPSY keyword # TODO: Moet dit nog? En zo ja, waarom dan niet hier? self.smsliderX_val = QLabel("%.2f"%(0.0)) # Set this widget to a fixed width. If you don't, then you will # see the gui resizing when the width of the contents changes. # The font is set to monospace but the strings are not of # eaqual size. self.smsliderX_val.setFixedWidth(40) self.smsliderX_val.setStyleSheet("QLabel { color: green }") font.setFamily("Monospace") # Set the fixed width font self.smsliderX_val.setFont(font) self.connect(self.smsliderX, SIGNAL('valueChanged(int)'), self.blurfunc) self.smsliderY_label = QLabel('Smooth fac. Y') self.smsliderY = QSlider(Qt.Horizontal) self.smsliderY.setToolTip("

Smooth current image in Y. Value (in pixels) sets sigma of Gaussian used in convolution

") self.smsliderY.setRange(0, 5.0*blurscale) # To set the blur factor self.smsliderY.setValue(0) self.smsliderY.setTickInterval(10) self.smsliderY.setTracking(True) self.smsliderY.setTickPosition(QSlider.NoTicks) #self.smsliderY.setFixedWidth(175) # Connection to callback via GIPSY keyword # TODO: Moet dit nog? En zo ja, waarom dan niet hier? self.smsliderY_val = QLabel("%.2f"%(0.0)) # Set this widget to a fixed width. If you don't, then you will # see the gui resizing when the width of the contents changes. # The font is set to monospace but the strings are not of # eaqual size. self.smsliderY_val.setFixedWidth(40) self.smsliderY_val.setStyleSheet("QLabel { color: green }") font.setFamily("Monospace") # Set the fixed width font self.smsliderY_val.setFont(font) self.connect(self.smsliderY, SIGNAL('valueChanged(int)'), self.blurfunc) splitimagelabel = QLabel('Split image') splitimagelabel.setToolTip(help_splitim[0]) # Help text is defined at the beginning of the code self.splitimageedit = MyLineEdit() self.splitimageedit.setMaximumWidth(50) self.splitimageedit.setText("0") self.splitimageedit.setToolTip(help_splitim[0]) # Help text is defined at the beginning of the code self.splitimageedit.setWhatsThis(help_splitim[1]) self.connect(self.splitimageedit, SIGNAL('returnPressed()'), self.setSplitImage) #self.alphaslider = floatSlider(0.0, 1.0, Qt.Horizontal) self.alphaslider = QSlider(Qt.Horizontal) self.alphaslider.setRange(0, 100) self.alphaslider.setValue(100) self.alphaslider.setTracking(True) self.alphaslider.setTickPosition(QSlider.NoTicks) #self.alphaslider.setFixedWidth(175) self.alphaslider.setToolTip("

Make current image transparent so that the image defined in the 'split image' field becomes visible

") #self.alphaslider.setStyleSheet(sliderstyle) #self.connect(self.alphaslider, SIGNAL('valueChanged(float)'), self.setAlpha) self.connect(self.alphaslider, SIGNAL('valueChanged(int)'), self.setAlpha) self.alphaslider_val = QLabel("%.2f"%(1.0)) self.alphaslider_val.setFixedWidth(40) self.alphaslider.setTickInterval(10) self.alphaslider_val.setStyleSheet("QLabel { color: green }") font.setFamily("Monospace") # Set the fixed width font self.alphaslider_val.setFont(font) interpollist = ['nearest', 'bilinear', 'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos'] self.interpolations = QComboBox() self.interpolations.setMaximumWidth(100) self.interpolations.addItems(interpollist) # Module Variable from maputils self.interpolations.setCurrentIndex(0) self.interpolations.setToolTip("

Apply Matplotlib's interpolation to the current plot. Image data remains unchanged

") self.connect(self.interpolations, SIGNAL('currentIndexChanged(QString)'), self.setInterpolation) self.plotcomfield = MyLineEdit() self.plotcomfield.setToolTip("

Enter your Matplotlib commands to add lines, labels etc. The plot history is re-drawn with button 'Redraw all'.
Activate field and press shift+F1 for more information.

") self.plotcomfield.setWhatsThis(help_plotcommand) self.plotcomfield.setHistory(self.plotCommandHist) self.connect(self.plotcomfield, SIGNAL('returnPressed()'), self.plotcommandGUI) self.markerfield = MyLineEdit() self.markerfield.setToolTip("

Enter position for a marker. The plot history is re-drawn with button 'Redraw all'.
Activate field and press shift+F1 for more information.

") self.markerfield.setWhatsThis(help_markercommand) self.markerfield.setHistory(self.markerHist) self.connect(self.markerfield, SIGNAL('returnPressed()'), self.markercommandGUI) self.markercol = QComboBox() collist =['blue', 'green', 'red', 'cyan', 'magenta', 'yellow', 'black', 'white'] self.markercol.addItems(collist) # Module Variable from maputils self.markercol.setCurrentIndex(2) self.markercol.setToolTip("

Select marker color

") self.markersym = QComboBox() #self.markersym.setMaximumWidth(40) self.markersym.setMaxVisibleItems(10) symlist = [ 'o', 'D' , 'h' , 'H' , '_' , 'None' ,\ ' ', '8' , 'p' , ',' , '+' , '.' , 's' , '*',\ 'd', '1' , '3' , '4' , '2' ,\ 'v' , '<' , '>' , '^' , ',' , 'x' , '$...$'] self.markersym.addItems(symlist) # Module Variable from maputils self.markersym.setCurrentIndex(10) self.markersym.setToolTip("

Select marker symbol

") self.markerwidth = QComboBox() #self.markerwidth.setMaximumWidth(40) self.markerwidth.setMaxVisibleItems(10) widthlist = [ '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'] self.markerwidth.addItems(widthlist) # Module Variable from maputils self.markerwidth.setCurrentIndex(5) self.markerwidth.setToolTip("

Select marker width

") self.redrawbutton = QPushButton("&Redraw all") self.redrawbutton.setToolTip("

External plot commands must be re-drawn after image changes

") self.connect(self.redrawbutton, SIGNAL('clicked()'), self.on_draw) self.crosshair = QCheckBox("Crosshair") self.crosshair.setChecked(False) self.crosshair.setToolTip("

Crosshair cursor is activated with left mouse button.
\ In slice panels you need also to activate the crosshair line in the panel.
\ Note that external plot commands are wiped out while using this cursor.

") self.connect(self.crosshair, SIGNAL('stateChanged(int)'), self.setCrossHair) # Tab 2 layout tab2 = QWidget() tab2.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) grid = QGridLayout(tab2) grid.setRowStretch(0,1) utilsgroup = QGroupBox("Utilities") utilsgroup.setFlat(False) utilsgroup.setStyleSheet(self.groupstyle) utilsgrid = QGridLayout() utilsgrid.addWidget(self.smsliderX_label, 0, 0) utilsgrid.addWidget(self.smsliderX, 0, 1) utilsgrid.addWidget(self.smsliderX_val, 0, 2) utilsgrid.addWidget(self.smsliderY_label, 1, 0) utilsgrid.addWidget(self.smsliderY, 1, 1) utilsgrid.addWidget(self.smsliderY_val, 1, 2) utilsgrid.addWidget(splitimagelabel, 2, 0) utilsgrid.addWidget(self.splitimageedit, 2, 1) utilsgrid.addWidget(QLabel("Transparency"), 3, 0) utilsgrid.addWidget(self.alphaslider, 3, 1) utilsgrid.addWidget(self.alphaslider_val, 3, 2) #utilsgrid.setRowStretch(6,2) utilsgrid.addWidget(QLabel('Interpolation'), 4, 0) utilsgrid.addWidget(self.interpolations, 4, 1) utilsgroup.setLayout(utilsgrid) grid.addWidget(utilsgroup, 1, 0 )#, 1, 5) grid.setRowStretch(2, 1) grid.addWidget(self.crosshair, 3, 0) utilsgroup2 = QGroupBox("External plot commands") utilsgroup2.setFlat(False) utilsgroup2.setStyleSheet(self.groupstyle) utilsgrid2 = QGridLayout() utilsgrid2.addWidget(QLabel("MPL Command"), 0, 0) utilsgrid2.addWidget(self.plotcomfield, 0, 1,1,3) utilsgrid2.addWidget(QLabel("Marker"), 1, 0) utilsgrid2.addWidget(self.markerfield, 1, 1,1,3) utilsgrid2.addWidget(QLabel("Color"), 2, 1) utilsgrid2.addWidget(QLabel("Symbol"), 2, 2) utilsgrid2.addWidget(QLabel("Size"), 2, 3) utilsgrid2.addWidget(self.markercol, 3,1) utilsgrid2.addWidget(self.markersym, 3,2) utilsgrid2.addWidget(self.markerwidth, 3,3) utilsgrid2.addWidget(self.redrawbutton, 4, 3) utilsgroup2.setLayout(utilsgrid2) grid.addWidget(utilsgroup2, 4, 0) #, 1, 5) grid.setRowStretch(5,10) tab_widget.addTab(tab2, "Utils") # Third tab is for movie control self.startmovie = QPushButton(QIcon(":/startmovie.png"), "", self) self.startmovie.setCheckable(True) self.startmovie.setMaximumWidth(40) self.startmovie.setToolTip("

Start movie. Toggle to stop again

") self.connect(self.startmovie, SIGNAL('clicked()'), lambda action='start':self.movieaction(action)) self.startmovieback = QPushButton(QIcon(":/startmovieback.png"), "", self) self.startmovieback.setCheckable(True) self.startmovieback.setMaximumWidth(40) self.startmovieback.setToolTip("

Start movie to run backwards. Toggle to stop again

") self.connect(self.startmovieback, SIGNAL('clicked()'), lambda action='startback':self.movieaction(action)) stopmovie = QPushButton(QIcon(":/stopmovie.png"), "", self) stopmovie.setMaximumWidth(40) stopmovie.setToolTip("Stop movie") self.connect(stopmovie, SIGNAL('clicked()'), lambda action='stop':self.movieaction(action)) stepback = QPushButton(QIcon(":/stepbackwards.png"), "", self) stepback.setMaximumWidth(40) stepback.setToolTip("Single step backwards") stepback.setAutoRepeat(True) self.connect(stepback, SIGNAL('clicked()'), lambda action='prev':self.movieaction(action)) stepforw = QPushButton(QIcon(":/stepforwards.png"), "", self) #TODO: Deze icons moeten in de resource file!!! stepforw.setMaximumWidth(40) stepforw.setToolTip("Single step forwards") stepforw.setAutoRepeat(True) self.connect(stepforw, SIGNAL('clicked()'), lambda action='next':self.movieaction(action)) # MOVIE SLIDER # The slider allows you to browse through your recorded images. # Its range is adjusted, each time a new cube is loaded. self.movieslider = QSlider(Qt.Horizontal) self.movieslider.setRange(0, 1) self.movieslider.setValue(0) self.movieslider.setTracking(True) self.movieslider.setTickPosition(QSlider.TicksAbove) self.movieslider.setToolTip("

Scroll through your images as defined in the movie frames list

") #self.movieslider.setFixedWidth(200) self.connect(self.movieslider, SIGNAL('valueChanged(int)'), lambda imnr: self.setThisImage(imnr, slider=True)) self.currentmovienr_label = QLabel("0") self.currentmovienr_label.setStyleSheet("QLabel { color: green }") font.setFamily("Monospace") # Set the fixed width font self.currentmovienr_label.setFont(font) # Apply font setting to label self.movieframes_label = QLabel("Movie frames") self.movieframes = MyLineEdit() self.movieframes.setToolTip("

Enter image numbers (starting at 0) which you want to include in a movie e.g. 20:30
An empty field selects all

") #self.movieframes.setMaximumWidth(100) #self.connect(self.movieframes, SIGNAL('returnPressed()'), self.setMovieFrames) self.connect(self.movieframes, SIGNAL('textEdited(QString)'), self.setMovieFrames) self.speedslider_label1 = QLabel("Speed (fr/s)") startspeed = 20 # Start with 20 frames per second n = 100*log10(startspeed)/log10(2.0) self.speedslider = QSlider(Qt.Horizontal) self.speedslider.setRange(-200, 600) self.speedslider.setValue(n) self.speedslider.setTickPosition(QSlider.NoTicks) # No ticks because logarithmic slider self.speedslider.setTracking(True) self.speedslider.setToolTip("

Set movie speed in frames per second. The slider is not linear. Your system limits the maximum speed.

") self.connect(self.speedslider, SIGNAL('valueChanged(int)'), self.setMovieSpeed) self.moviespeededit = QLineEdit() self.moviespeededit.setToolTip("

Set movie speed in frames per second. This number must by greater than 0 but can be smaller than 1

") self.moviespeededit.setMaximumWidth(50) self.moviespeededit.setText(str(startspeed)) self.connect(self.moviespeededit, SIGNAL('returnPressed()'), self.setMovieSpeed) framenrlabel = QLabel('Frame') self.framenredit = QLineEdit() self.framenredit.setMaximumWidth(50) self.framenredit.setToolTip("

Select an image by its movie frame number

") self.connect(self.framenredit, SIGNAL('returnPressed()'), lambda : self.setThisImage(imnr=None, slider=False)) tab3 = QWidget() tab3.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) grid = QGridLayout(tab3) grid.setRowStretch(0, 1) moviegroup = QGroupBox("Frames") moviegroup.setFlat(False) moviegroup.setStyleSheet(self.groupstyle) moviegrid = QGridLayout() moviegrid.addWidget(self.movieframes_label, 0, 0) moviegrid.addWidget(self.movieframes, 0, 1, 1, 3) moviegrid.addWidget(framenrlabel, 1 , 0) moviegrid.addWidget(self.movieslider, 1, 1, 1, 4) # TODO: variabele opruimen: grid.addWidget(self.currentmovienr_label, 2, 5) moviegrid.addWidget(self.framenredit, 1 , 5) moviegrid.addWidget(self.speedslider_label1, 2, 0) moviegrid.addWidget(self.speedslider, 2, 1, 1, 4) moviegrid.addWidget(self.moviespeededit, 2, 5) moviegroup.setLayout(moviegrid) grid.addWidget(moviegroup, 1, 0, 1, 5) grid.setRowStretch(2, 1) controlgroup = QGroupBox("Movie loop") controlgroup.setFlat(False) controlgroup.setStyleSheet(self.groupstyle) controlgrid = QGridLayout() controlgrid.addWidget(stepback, 0, 2) controlgrid.addWidget(stepforw, 0, 4) controlgrid.addWidget(self.startmovieback, 0, 1) controlgrid.addWidget(self.startmovie, 0, 5) controlgrid.addWidget(stopmovie, 0, 3) controlgroup.setLayout(controlgrid) grid.addWidget(controlgroup, 3, 0, 1, 5) grid.setRowStretch(4, 10) tab_widget.addTab(tab3, "Movie") # 4. Fourth tab for color editing -------------------------------------- # First we want to be able to set the color modes (linear, log etc.) # A user should be able to pick a color lut from a list # Then we want to be able to invert the color lut # A reset button resets everything to a default. # Create a combobox with all availble color luts. # These are the ones from the maputils module and the ones from # the Kapteyn Package luts directory. The list sorted alphabetically. # The default lut is copied from the maputils module self.combolabel = QLabel("Lut") self.lutlist = QComboBox() # Next lines are a trick to limit the number of visible items on any system # The property maxVisibleItems is ignored for non-editable comboboxes # in styles that returns true for `QStyle::SH_ComboBox_Popup such as # the Mac style or the Gtk+ Style (Webinfo: Stackoverflow) self.lutlist.setStyleSheet("QComboBox { combobox-popup: 0; }") self.lutlist.setMaxVisibleItems(10) maputils.cmlist.addfavorites(['bw.lut', 'rainbow4.lut', 'ronekers.lut', 'staircase.lut', 'backgr.lut', 'jet']) defmap = maputils.cmlist.cmap_default = 'bw.lut' # TODO: Voor de popup help toevoegen dat de eerste entries de favorieten zijn # door deze applicatie toegevoegd. Daarna volgt een alphabetische lijst # met alle luts, waar ook de favorieten nog een keer in voorkomen! self.lutlist.addItems(maputils.cmlist.colormaps) # Module Variable from maputils self.lutlist.setCurrentIndex(maputils.cmlist.colormaps.index(defmap)) self.lutlist.setToolTip("

List with color lookup tables followed (favoutites followed by list in alphabethical order). These color maps are shared by all images in a cube.

") self.connect(self.lutlist, SIGNAL('currentIndexChanged(QString)'), self.setColorLut) # The Invert button is a toggle. It inverts the current color lut. self.buttonInverseLut = QPushButton("&Invert") self.buttonInverseLut.setCheckable(True) self.buttonInverseLut.setToolTip("

Reverse the colors so that the color for high images values becomes the color for low values.

") self.connect(self.buttonInverseLut, SIGNAL('clicked()'), self.setInverseLut) # The color scales: scaleLabel = QLabel("Scale") #self.scales = maputils.cmlist.scales # Available scales defined in module maputils self.scales = maputils.Annotatedimage.lutscales self.scalelist = QComboBox() self.scalelist.addItems(self.scales) self.scalelist.setCurrentIndex(maputils.Annotatedimage.scales_default) self.scalelist.setToolTip("

To enhance certain features in your image, scale the colors with one of these options.

") # Connect and pass the index of the selected button self.connect(self.scalelist, SIGNAL('currentIndexChanged(int)'), self.setColorScale) blanklab = QLabel('Blank') self.blist = maputils.Annotatedimage.blankcols self.blankcolor = QComboBox() self.blankcolor.setCurrentIndex(maputils.Annotatedimage.blankcols_default) self.blankcolor.addItems(maputils.Annotatedimage.blanknames) self.blankcolor.setToolTip("

'Bad' pixels (blanks) get their own color. The color applies to blanks in all images that share the same color map.

") self.connect(self.blankcolor, SIGNAL('currentIndexChanged(int)'), self.setBlankColor) # Reset button for lut, scales and invert & blanks self.buttonDefaultColor = QPushButton("&Reset all") self.buttonDefaultColor.setToolTip("

Reset all fields in this group to defaults.

") self.connect(self.buttonDefaultColor, SIGNAL('clicked()'), self.colorReset) self.setclipslabelLo = QLabel('Clips') self.setclipsformLo = QLineEdit() self.setclipsformLo.setToolTip("

Set lower value for the data interval used to scale the image colors\ An empty field sets this value to the minimum of the current cube, as set at load time. Expressions are allowed (e.g. 2.37/2).

") self.connect(self.setclipsformLo, SIGNAL('returnPressed()'), self.setclipsLoHi) self.setclipsformHi = QLineEdit() self.setclipsformHi.setToolTip("

Set upper value for the data interval used to scale the image colors\ An empty field sets this value to the maximum of the current cube, as set at load time. Expressions are allowed (e.g. 2.37/2).

") self.connect(self.setclipsformHi, SIGNAL('returnPressed()'), self.setclipsLoHi) self.buttonDefaultClip = QPushButton("Reset") self.connect(self.buttonDefaultClip, SIGNAL('clicked()'), lambda clips=[None,None]:self.setclips(cb=None, clips=clips)) lab_slope = QLabel("Slope") sliderstyle = """ QSlider::groove:horizontal { border: 1px solid #999999; height: 8px; /* the groove expands to the size of the slider by default. by giving it a height, it has a fixed size */ /* background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #c4c4c4);*/ background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #c1c1c1, stop:1 #d4d4d4); margin: 2px 0; } QSlider::handle:horizontal { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); border: 1px solid #11aa11; /* #5c5c5c;*/ width: 18px; margin: -2px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ border-radius: 3px; } """ #self.slider_slope = QSlider(Qt.Horizontal) #self.slider_slope.setRange(0, 100) #self.slider_slope.setValue(50) #self.slider_slope.setFixedWidth(150) #self.slider_slope = floatSlider(0.0, 10.0, self, Qt.Horizontal) # Use the maputils.Annotatedimage class variable 'slopetrans' # to set the maximum value of the slope of the color map self.slider_slope = floatSlider(0.0, maputils.Annotatedimage.slopetrans, Qt.Horizontal) self.slider_slope.setValue(1.0) self.slider_slope.setToolTip("

Narrow or expand the range of values over which all colors are divided.

") #self.slider_slope.setStyleSheet(sliderstyle) self.connect(self.slider_slope, SIGNAL('valueChanged(float)'), self.setColorSlope) slider_resetslope = QPushButton("Reset") self.connect(slider_resetslope, SIGNAL('clicked()'), lambda: self.updateColorSlope(1.0, block=False)) lab_off = QLabel("Offset") #self.slider_offset = QSlider(Qt.Horizontal) #self.slider_offset.setRange(0, 100) #self.slider_offset.setValue(50) #self.slider_offset.setFixedWidth(150) self.slider_offset = floatSlider(-0.5, 0.5, Qt.Horizontal) #self.slider_offset.setStyleSheet(sliderstyle) self.slider_offset.setValue(0.0) self.slider_offset.setToolTip("

Shift the range of values over which all colors are divided.

") self.connect(self.slider_offset, SIGNAL('valueChanged(float)'), self.setColorOffset) slider_resetoff = QPushButton("Reset") self.connect(slider_resetoff, SIGNAL('clicked()'), lambda: self.updateColorOffset(0.0, block=False)) # The layout tab4 = QWidget() tab4.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) colorgroup = QGroupBox("Color editor") colorgroup.setFlat(False) colorgroup.setStyleSheet(self.groupstyle) colorgrid = QGridLayout() #colorgrid.addWidget(QLabel("")) colorgrid.addWidget(self.combolabel, 1, 0) colorgrid.addWidget(self.lutlist, 1, 1) colorgrid.addWidget(self.buttonInverseLut, 1, 3) colorgrid.addWidget(scaleLabel , 2, 0) colorgrid.addWidget(self.scalelist , 2, 1) colorgrid.addWidget(blanklab , 3, 0) colorgrid.addWidget(self.blankcolor , 3, 1) colorgrid.addWidget(lab_slope, 4, 0) colorgrid.addWidget(self.slider_slope.slider, 4, 1, 1, 2) colorgrid.addWidget(slider_resetslope, 4, 3) colorgrid.addWidget(lab_off, 5, 0) colorgrid.addWidget(self.slider_offset.slider, 5, 1, 1, 2) # Note the .slider part! i.e. floatslider colorgrid.addWidget(slider_resetoff, 5, 3) colorgrid.addWidget(self.buttonDefaultColor, 6, 3) colorgroup.setLayout(colorgrid) grid = QGridLayout(tab4) grid.setRowStretch(0,1) grid.addWidget(colorgroup, 1, 0, 1, 4) grid.setRowStretch(2,1) grid.addWidget(self.setclipslabelLo, 3, 0) grid.addWidget(self.setclipsformLo, 3, 1) grid.addWidget(self.setclipsformHi, 3, 2) grid.addWidget(self.buttonDefaultClip, 3, 3) grid.setRowStretch(4,10) #grid.setColumnStretch(4,1) # TODO: Bij de reset wordt de blankcolor niet gereset. tab_widget.addTab(tab4, "Colors") # 5. Fifth tab for slice panels ---------------------------------------- tab5 = QWidget() tab5.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.xpanels_label = QLabel("X slice") self.xpanels_edit = MyLineEdit() self.xpanels_edit.setMaximumWidth(200) self.xpanels_edit.setToolTip("

Enter the frame numbers of the images that should be used\ to build a slice panel along the X axis (usually a position velocity map).\ An empty field selects all available images. Press shift+F1 for more info.

") self.xpanels_edit.setWhatsThis(help_slicepanels) key = 'XPANEL=' gipsy.KeyCallback(self.setpanelframes, key, panel='X') triggerkey(key) #gipsy.QtLink(key, self.xpanels_edit, 'returnPressed()') self.ypanels_label = QLabel("Y slice") self.ypanels_edit = MyLineEdit() self.ypanels_edit.setMaximumWidth(200) self.ypanels_edit.setToolTip("

Enter the frame numbers of the images that should be used\ to build a slice panel along the Y axis (usually a position velocity map).\ An empty field selects all available images. Press shift+F1 for more info.

") self.ypanels_edit.setWhatsThis(help_slicepanels) key = 'YPANEL=' gipsy.KeyCallback(self.setpanelframes, key, panel='Y') triggerkey(key) #gipsy.QtLink(key, self.ypanels_edit, 'returnPressed()') drawPanelsButton1 = QPushButton("&Draw") drawPanelsButton1.setToolTip("

Start plotting panels defined in the above input fields.

") self.connect(drawPanelsButton1, SIGNAL('clicked()'), self.drawPanelsFromGlobal) drawPanelsButton2 = QPushButton("Draw") drawPanelsButton2.setToolTip("

Start plotting panels defined in the data tabs.

") self.connect(drawPanelsButton2, SIGNAL('clicked()'), self.drawPanelsFromTabs) # Layout grid = QGridLayout(tab5) grid.setRowStretch(0,1) slicegroup = QGroupBox("Slice panels") slicegroup.setFlat(False) slicegroup.setStyleSheet(self.groupstyle) slicegrid = QGridLayout() slicegrid.addWidget(self.xpanels_label, 1, 0) slicegrid.addWidget(self.xpanels_edit, 1, 1, 1, 3) slicegrid.addWidget(self.ypanels_label, 2, 0) slicegrid.addWidget(self.ypanels_edit, 2, 1, 1, 3) slicegrid.addWidget(drawPanelsButton1, 3, 3) slicegroup.setLayout(slicegrid) grid.addWidget(slicegroup, 1, 0, 1, 3) grid.setRowStretch(2, 2) grid.addWidget(QLabel("Use panels defined in data tabs:"), 3, 0, 1, 2) grid.addWidget(drawPanelsButton2, 3, 2) grid.setRowStretch(4, 2) grid.setRowStretch(6,10) tab_widget.addTab(tab5, "Slices") # 6. Sixth tab for cursor position insertion ---------------------------------------- tab6 = QWidget() tab6.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.coordinate_grids = QCheckBox("Grid coordinates") self.coordinate_grids.setChecked(True) self.coordinate_grids.setToolTip("

After pressing shift+left mouse button, send grid coordinates (i.e. in the GIPSY grid system) to command line and/or log file.

") self.connect(self.coordinate_grids, SIGNAL('stateChanged(int)'), self.applyCoordSettings) self.coordinate_gridprec = QComboBox() self.coordinate_gridprec.addItems([str(s) for s in range(6)]) self.coordinate_gridprec.setCurrentIndex(0) self.connect(self.coordinate_gridprec, SIGNAL('activated(int)'), self.applyCoordSettings) self.coordinate_world = QCheckBox("Lons,lats in hms/dms") self.coordinate_world.setToolTip("

After pressing shift+left mouse button, send world coordinates to command line and/or log file. If possible, print these coordinates in sexagesimal format

") self.connect(self.coordinate_world, SIGNAL('stateChanged(int)'), self.applyCoordSettings) self.coordinate_dmsprec = QComboBox() self.coordinate_dmsprec.addItems([str(s) for s in range(6)]) self.coordinate_dmsprec.setCurrentIndex(0) self.connect(self.coordinate_dmsprec, SIGNAL('activated(int)'), self.applyCoordSettings) self.coordinate_worlduf = QCheckBox("Unformatted world coords") self.coordinate_worlduf.setToolTip("

After pressing shift+left mouse button, send plain world coordinates to command line and/or log file.

") self.connect(self.coordinate_worlduf, SIGNAL('stateChanged(int)'), self.applyCoordSettings) self.coordinate_wcsprec = QComboBox() self.coordinate_wcsprec.addItems([str(s) for s in range(14)]) self.coordinate_wcsprec.setCurrentIndex(7) self.connect(self.coordinate_wcsprec, SIGNAL('activated(int)'), self.applyCoordSettings) self.coordinate_imageval = QCheckBox("Image value") self.coordinate_imageval.setToolTip("

After pressing shift+left mouse button, send image value of pixel at mouse position to command line and/or log file.

") self.connect(self.coordinate_imageval, SIGNAL('stateChanged(int)'), self.applyCoordSettings) self.coordinate_imprec = QComboBox() self.coordinate_imprec.addItems([str(s) for s in range(14)]) self.coordinate_imprec.setCurrentIndex(7) self.connect(self.coordinate_imprec, SIGNAL('activated(int)'), self.applyCoordSettings) self.coordinate_appendcr = QCheckBox("Append CR") self.coordinate_appendcr.setToolTip("

Append a carriage return to the line you send to the command line.\ This way, you can respond to GIPSY keywords of other tasks without the need of\ pressing enter.

") self.connect(self.coordinate_appendcr, SIGNAL('stateChanged(int)'), self.applyCoordSettings) # write positions by default to Hermes Log & Screen to avoid # pollution on the command line. self.coordinate_tocli = QCheckBox("Send to Hermes command line") self.coordinate_tocli.setChecked(False) self.coordinate_tocli.setToolTip("

Select destination (Hermes command line) of mouse position output

") self.connect(self.coordinate_tocli, SIGNAL('stateChanged(int)'), self.applyCoordSettings) self.coordinate_tolog = QCheckBox("Send to Hermes log and screen") self.coordinate_tolog.setChecked(True) self.coordinate_tolog.setToolTip("

Select destination (Hermes Log file) of mouse position output

") self.connect(self.coordinate_tolog, SIGNAL('stateChanged(int)'), self.applyCoordSettings) grid = QGridLayout(tab6) grid.setRowStretch(0,1) coordgroup = QGroupBox("Cursor positions") coordgroup.setFlat(False) coordgroup.setStyleSheet(self.groupstyle) coordgrid = QGridLayout() coordgrid.addWidget(QLabel("Output"),1, 0) declabel = QLabel("Decimals") declabel.setMaximumWidth(80) coordgrid.addWidget(declabel,1, 1) coordgrid.addWidget(self.coordinate_grids, 2, 0) coordgrid.addWidget(self.coordinate_gridprec, 2, 1) coordgrid.addWidget(self.coordinate_world, 3, 0) coordgrid.addWidget(self.coordinate_dmsprec, 3, 1) coordgrid.addWidget(self.coordinate_worlduf, 4, 0) coordgrid.addWidget(self.coordinate_wcsprec, 4, 1) coordgrid.addWidget(self.coordinate_imageval, 5, 0) coordgrid.addWidget(self.coordinate_imprec, 5, 1) #coordgrid.setRowStretch(6,1) coordgrid.addWidget(QLabel("Destination"), 6, 0) coordgrid.addWidget(self.coordinate_tocli, 7, 0) coordgrid.addWidget(self.coordinate_tolog, 8, 0) coordgrid.addWidget(self.coordinate_appendcr, 9, 0) coordgroup.setLayout(coordgrid) grid.addWidget(coordgroup, 1, 0) grid.setRowStretch(2,10) tab_widget.addTab(tab6, "Cursor") # 7. Seventh tab for zooming to display grids -------------------------- tab7 = QWidget() tab7.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) # The zoom buttons self.zoom = QButtonGroup() grid = QGridLayout(tab7) grid.setRowStretch(0,1) #grid.addWidget(QLabel("Zoom"), 1, 0) layout = QHBoxLayout() layout.setSpacing(1) zoomdecrease = QPushButton("-") zoomgroup = QGroupBox("Zoom") zoomgroup.setFlat(False) zoomgroup.setStyleSheet(self.groupstyle) zoomgrid = QGridLayout() zoomgrid.addWidget(zoomdecrease, 1, 1) self.zoomfac = None self.zoomoptions = ['1/16', '1/8', '1/4', '1/2', '1', '2', '4', '8'] col = 1 row = 2 for i,t in enumerate(self.zoomoptions): z = QPushButton(t) z.setCheckable(True) self.zoom.addButton(z, i) # Set index also if col > 4: # Divide layout over 2 lines row += 1 col = 1 zoomgrid.addWidget(z, row, col) col += 1 # The increase/decrease zoom (integer) factor zoomincrease = QPushButton("+") zoomgrid.addWidget(zoomincrease, 1, 2) self.connect(self.zoom, SIGNAL('buttonClicked(int)'), self.setZoom) self.connect(zoomincrease, SIGNAL('clicked()'), lambda i=-1, zoom='+' :self.setZoom(i, zoom)) self.connect(zoomdecrease, SIGNAL('clicked()'), lambda i=-1, zoom='-' :self.setZoom(i, zoom)) self.zoomfactor = QLabel("Display") self.zoomfactor.setStyleSheet("QLabel { color: green }") tofit = QPushButton("To fit") self.connect(tofit, SIGNAL('clicked()'), lambda i=-1, zoom='f' :self.setZoom(i, zoom)) self.aspectRatioOptions =QGroupBox() self.radio1 = QRadioButton("to 1.0") self.radio1.setToolTip("

") #self.aspectRatioOptions.addButton(radio1, 0) self.radio2=QRadioButton("from header") #self.aspectRatioOptions.addButton(radio2, 1) self.radio2.setChecked(True) #self.connect(self.aspectRatioOptions, SIGNAL('buttonclicked(int)'), lambda i=-1, zoom='a' :self.setZoom(i, zoom)) self.connect(self.radio1, SIGNAL('toggled(bool)'), lambda zoom='a' :self.setZoom(-1, zoom)) self.connect(self.radio2, SIGNAL('toggled(bool)'), lambda zoom='a' :self.setZoom(-1, zoom)) #grid.addWidget(QLabel("Factor"), 4, 0) zoomgrid.addWidget(self.zoomfactor, 1, 4) zoomgrid.addWidget(tofit, 1, 3) zoomgroup.setLayout(zoomgrid) grid.addWidget(zoomgroup, 1, 0, 1, 5) grid.setRowStretch(6,1) grid.addWidget(QLabel("Aspect ratio"), 7, 0) grid.addWidget(self.radio1, 7, 1, 1, 2) grid.addWidget(self.radio2, 8, 1, 1, 2) grid.setRowStretch(9,10) tab_widget.addTab(tab7, "Zoom") # Finish up and add tabs as dock widget tabbar = tab_widget.tabBar() indx = tab_widget.count()-3 # tabbar.setTabButton(indx, tabbar.LeftSide, pushbut) #tabbar.setTabEnabled(indx, False) tabbar.setTabsClosable(False) # Allow tabs to stretch vertically tab_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) # Create tab for tab with 'Data 0' #self.addDataTab(0) # Disable colors and slices tabs until there is something displayed. tab_widget.setTabEnabled(3, False) tab_widget.setTabEnabled(4, False) # #tab_widget.setTabPosition(QTabWidget.East) DockWidget = QDockWidget(self) DockWidget.setObjectName("DockWidget") #DockWidget.setAllowedAreas(Qt.BottomDockWidgetArea) DockWidget.setAllowedAreas(Qt.RightDockWidgetArea |Qt.LeftDockWidgetArea) #DockWidget.setFeatures(QDockWidget.DockWidgetClosable|QDockWidget.DockWidgetMovable) DockWidget.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) #DockWidget.setWidget(self.canvas) DockWidget.setWidget(tab_widget) self.addDockWidget(Qt.RightDockWidgetArea, DockWidget) # Place the tabs right to the display hbox_main = QHBoxLayout(self.main_frame) #hbox_main = QHBoxLayout() vbox_main = QVBoxLayout() vbox_main.addWidget(self.mpl_toolbar) vbox_main.addWidget(self.repeataxesinfo) vbox_main.addWidget(self.canvas) vbox_main.addWidget(self.messenger) hbox_main.addLayout(vbox_main) #hbox.addWidget(tab_widget) #self.canvas.setFocus() # To enable MPL interaction at once self.setCentralWidget(self.main_frame) self.tabs = tab_widget def infig(self, cb): #--------------------------------------------------------------------------- # Purpose: Callback for when mouse enters figure. Set the keyboard focus # to the plot canvas. # We abuse this event to set a message in about the toolbar mode # (pan/zoom) in the area where the positions are written. #--------------------------------------------------------------------------- self.canvas.grabKeyboard() mode = self.mpl_toolbar.mode if mode == 'zoom rect': s = "ZOOM TO RECTANGLE MODE" maputils.globalmessenger(s) elif mode == 'pan/zoom': s = "PAN/ZOOM MODE" maputils.globalmessenger(s) def outfig(self, cb): self.canvas.releaseKeyboard() def setPositionMessage(self, s): # Display information about current position in messenger bar self.messenger.setText(s) def setSpectralMessage(self, s): self.repeataxesinfo.setText(s) def closeEvent(self, event): #--------------------------------------------------------------------------- # Purpose: Store settings and close application. # # Method is scheduled for closing via menu or application cross. #--------------------------------------------------------------------------- # Is always called if user closes application settings = QSettings() settings.setValue("Geometry", QVariant(self.saveGeometry())) if self.recentFiles: recentFiles = QVariant(self.recentFiles) else: recentFiles = QVariant() settings.setValue("RecentFiles", recentFiles) # Combine history of all data input fields in one history nt = self.datatabs.count() tabinf = self.getTabInfo(0) History = tabinf.datainput.history # Add from other data input fields too for i in range(1,nt): tabinf = self.getTabInfo(i) # Prevent storing doubles for com in tabinf.datainput.history: if not History.contains(com): History.append(com) # Store this combined history settings.setValue("LineEditHistory", QVariant(History)) settings.setValue("PlotCommandHistory", QVariant(self.plotcomfield.history)) settings.setValue("MarkerHistory", QVariant(self.markerfield.history)) """ if not self.annim is None: gipsy.wkey("CSLOPE= %g"%(self.annim.cmap.slope)) gipsy.wkey("COFFSET= %g"%(self.annim.cmap.shift)) if self.movieimages: gipsy.wkey("MOVIEFRAME=%d"%(self.movieimages.indx)) """ def create_status_bar(self): #--------------------------------------------------------------------------- # Define the contents of the lower area of the GUI, which is called the # status bar. To set the elements of the statusbar we apply method # statusBar() which is a methof of the QtGui.QMainWindow class. # A temporary message can be written with method: # self.statusBar().showMessage(string, time(ms)) #--------------------------------------------------------------------------- stlab = "GIPSY/Kapteyn Package data viewer v%s"%__version__ self.status_text = QLabel(stlab) self.statusBar().addWidget(self.status_text, 1) mem = maputils.getmemory() self.memoryStatus = QLabel(mem) self.memoryStatus.setToolTip("Shows either resident memory of task in mB or mem. pages.
Updated every 10 s.") self.statusBar().addWidget(self.memoryStatus, 2) # We want a progressbar for loading images each time a new cube is defined. self.progressBar = QProgressBar() self.statusBar().addWidget(self.progressBar) self.statusBar().addPermanentWidget(gipsy.gipsy_logo()) # show this is GIPSY self.statusBar().addPermanentWidget(gipsy.kapteyn_logo()) def displayMemory(self): #--------------------------------------------------------------------------- # Purpose: Print current system resident memory use in status bar #--------------------------------------------------------------------------- mem = maputils.getmemory() self.memoryStatus.setText(maputils.getmemory()) #gipsy.anyout("Memory = %s"%(str(maputils.getmemory()))) def updateFileMenu(self): #--------------------------------------------------------------------------- # Purpose: Add the recent files part to the File menu #--------------------------------------------------------------------------- self.file_menu.clear() self.add_actions(self.file_menu, self.fileMenuActions[:-1]) recentFiles = [] for fname in self.recentFiles: # Do we want the short name? fn = QString(str(fname).split()[0]) recentFiles.append(fname) if recentFiles: #self.file_menu.addSeparator() recentFileMenu = self.file_menu.addMenu(QIcon(":/fileopen.png"), "&Recent Data files") for i, fname in enumerate(recentFiles): action = QAction(QIcon(":/dataset_small.png"), "&%d %s" % (i+1, fname), self) self.connect(action, SIGNAL("triggered()"), lambda fname=fname : self.loadData(fname)) recentFileMenu.addAction(action) self.file_menu.addSeparator() self.file_menu.addAction(self.fileMenuActions[-1]) def checkDefaults(self): #--------------------------------------------------------------------------- # Purpose: Method to inspect whether defaults of new entered data source # are set. If not, call setDefaults(). #--------------------------------------------------------------------------- n = self.datatabs.currentIndex() # Identify the current tab nmax = self.datatabs.count() # Extra check for keyword input if n < 0 or n > nmax: return tabinf = self.getTabInfo(n) if str(tabinf.clipheadmin.text()) == notset: fname = str(tabinf.datainput.text()).strip() if fname: self.setDefaults(tabinf, fname) def loadData(self, fname=None, boxline=None): #--------------------------------------------------------------------------- # Purpose: Method to process the result of a file menu action # # First we try to find out what the current tab is. In that tab the # result of the openFileGui() action should be written. # The callback can originate from a file browser selection or from # a recent files menu selection. In both situations we use the # name that is selected to find defaults for the data. So the selection # does not trigger plotting. # Note that this method resembles method getfile() with the difference that # we know here which tab is selected and the corresponding DATASETn= keyword # is not given, but derived. #--------------------------------------------------------------------------- frombrowser = fname is not None and boxline is not None if not frombrowser: if fname is None: return # Nothing to do else: fname = str(fname) # From QT widget so is a QString n = self.datatabs.currentIndex() # Identify the current tab nmax = self.datatabs.count() # Extra check for keyword input if n < 0 or n > nmax: return tabinf = self.getTabInfo(n) # Get this tab's info if frombrowser: tabinf.datainput.setText(fname) else: ext = '.IMAGE' # Clean up name if it was a GIPSY set if fname.upper().endswith(ext): fname = fname[0:-(len(ext))] tabinf.datainput.setText(fname) # Update the data input field #self.checkDefaults() self.setDefaults(tabinf, fname) if frombrowser: # Can be different from the default, so overwrite default if boxline: tabinf.boxinput.setText(boxline) # Find suitable defaults for box etc. # TODO: Hier goed over nadenken. Als je wkey gebruikt dan scheelt # dat 1x enter drukken. Maar dan wordt de regel niet in de history opgenomen. # Bijkomend voordeel van wel zelf enter drukken is dat je zelf bepaalt # wanneer je defaults zet. #gipsy.wkey("%s%s"%(tabinf.keyword, fname)) # Update the GIPSY keyword def create_uppermenu(self): #--------------------------------------------------------------------------- # Member of class AppForm. Its buttons are: # 1. the File button at the top of the gui. # 2. the Help button #--------------------------------------------------------------------------- # 1. Button title self.file_menu = self.menuBar().addMenu("&File") save_file_action = self.create_action("&Save plot", shortcut="Ctrl+S", slot=self.save_plot, tip="Save the plot", icon=':/filesaveas.png') # 2. Postpone the creation of the file menu list so that # recently added files can be added to the menu. The method # updateFileMenu() fills the list self.connect(self.file_menu, SIGNAL("aboutToShow()"), self.updateFileMenu) # 3. Open file dialog open_file_action = self.create_action("&Open Data set", slot=self.openFileGui, shortcut="Ctrl+O", tip="Open FITS file or GIPSY set", icon=':/fileopen.png') # 4. Quit button quit_action = self.create_action("&Quit", SLOT('close()'), #slot=self.close, shortcut="Ctrl+Q", tip="Close the application", icon=':/exit.png') # Add actions in order of tuple #self.add_actions(self.file_menu, (open_file_action, save_file_action, None, quit_action)) self.fileMenuActions = (open_file_action, save_file_action, None, quit_action) # Help menu self.help_menu = self.menuBar().addMenu("&Help") help_action = self.create_action("&Handbook", shortcut='F1', slot=None, tip='Help about keys') self.connect(help_action, SIGNAL("triggered()"),\ lambda wid=id(help_action) :self.contextHelp(wid)) about_action = self.create_action("&About", slot=self.on_about, tip='About this viewer') self.add_actions(self.help_menu, (help_action, about_action)) # Activate the keyboard accelerators. Without this extra call # to updateFileMenu(), we would not have activated the shortcuts self.updateFileMenu() def add_actions(self, target, actions): #--------------------------------------------------------------------------- # Helper function that allows you to add a sparator in a menu list. #--------------------------------------------------------------------------- for action in actions: if action is None: target.addSeparator() else: target.addAction(action) def create_action(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): #--------------------------------------------------------------------------- # Helper function that can set various properties of menu entries. #--------------------------------------------------------------------------- action = QAction(text, self) if icon is not None: #action.setIcon(QIcon(":/%s.png" % icon)) action.setIcon(QIcon("%s" % icon)) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def openFileGui_orig(self): # This method is called if a user selected to open a file in the File menu # It gives a file name and path. This text is substituted in de text box # associated with the DATASET= keyword. Nothing is triggered so that we can # add axis information. extensions = "Data sets (*.fits *.image *.fits.gz);;All (*)" fn = QFileDialog.getOpenFileName(self, 'Open data set', '', extensions) if fn: #self.datatabs.setCurrentIndex(0) self.loadData(fn) def openFileGui(self, tid=None): #--------------------------------------------------------------------------- # Purpose: Open the data source composer. This method is triggered when # 1) Ctrl-O is pressed # 2) 'Open Data Set' in File menu is selected # 3) Compose button on GUI is clicked # # Notes: When not triggered from 'Compose' button then 'tid' is unknown # so we have to find the active tab first. # New data will always trigger a routine that sets new defaults # The line with box information from the composer will be written # afterwards. #--------------------------------------------------------------------------- if tid is None: n = self.datatabs.currentIndex() # Identify the current tab nmax = self.datatabs.count() # Extra check for keyword input if n < 0 or n > nmax: return tabinf = self.getTabInfo(n) tid = tabinf.tid fn = self.setbrowsers[tid].getOpenSet() if fn: self.loadData(fn[0], fn[1]) #gipsy.anyout(s) #self.setbrowserDlg.raise_() #self.setbrowserDlg.activateWindow() #self.connect(self.setbrowserDlg, SIGNAL("accepted()"), self.setbrowserDlg.getValues) #if fn: #self.datatabs.setCurrentIndex(0) #self.loadData(fn) def addRecentFile(self, fname): if fname is None: return fname = fname.strip() # It is a Python string so use strip() to remove whitespace if not self.recentFiles.contains(fname): self.recentFiles.prepend(QString(fname)) while self.recentFiles.count() > 9: self.recentFiles.takeLast() def blurfuncOLD(self, cb): #--------------------------------------------------------------------------- # Purpose: Smooth the current image #--------------------------------------------------------------------------- # Set the smoothing factor in pixels. Update label and re-draw plot blur = gipsy.userreal(cb.key) blur /= blurscale annim = self.getCurrentImage() if annim is None: # or annim.image is None: return annim.set_blur(True, blur, blur, new=True) self.on_draw() s = "%.2f"%blur self.smslider_val.setText(s) # TODO: goed documenteren wat deze factor dan is. def blurfunc(self): #--------------------------------------------------------------------------- # Purpose: Smooth the current image in X and/or Y direction #--------------------------------------------------------------------------- # Set the smoothing factor in pixels. Update label and re-draw plot blurx = self.smsliderX.value() blurx /= blurscale blury = self.smsliderY.value() blury /= blurscale annim = self.getCurrentImage() if annim is None: # or annim.image is None: return annim.set_blur(True, blurx, blury, new=True) self.on_draw() s = "%.2f"%blurx self.smsliderX_val.setText(s) s = "%.2f"%blury self.smsliderY_val.setText(s) def setBlankColor(self, indx): #--------------------------------------------------------------------------- # Purpose: Callback for setting the blank color on the canvas # # Note that both the set- and the update- methods of the colormaps # should reset the foucus to the canvas. Otherwise a user must activate # the plot canvas with a mouse click. #--------------------------------------------------------------------------- annim = self.getCurrentImage() if annim: annim.set_blankcolor(self.blist[indx]) # Color as a string def updateBlankColor(self, indx): #--------------------------------------------------------------------------- # Purpose: Callback for setting the blank color combobox after interaction # with canvas keys # # The parameter 'indx' set by the canvas key and corresponds to the # Annotatedimage.blankcols class variable (list) # # Important note about update callbacks: # ====================================== # 1) The update callbacks are invoked after an action on the plot canvas. # Maputils defined several key to work with colormaps. We want the GUI # up to date when things change on the plot canvas. For this purpose # the update callbacks are defined. We need to be careful with this because # changing the gui can invoke the callback too and we have a chain reaction. # Luckily, we use signal currentIndexChanged() for the combo boxes. # This signal is only emitted when the index of the list changes. So an # update callback does not change the index and a chain reaction is # prevented. # # 2) The update callbacks set the focus to the GUI controls, while we # generated the callback from the plot canvas. So we must shift the # focus again to the plot canvas. #--------------------------------------------------------------------------- self.blankcolor.setCurrentIndex(indx) def setColorSlope(self, slope): #--------------------------------------------------------------------------- # Callback for slider that sets the color slope. The slope and the offset # set the tanslation between color range and data range. # In maputils we used slope = 2.0 * xn, where xn between 0 and 1 # So the slope runs from 0 to 2. # Note that we used a floating point slider. #--------------------------------------------------------------------------- annim = self.getCurrentImage() if annim is None: return annim.cmap.modify(slope, annim.cmap.shift) def setColorOffset(self, shift): #--------------------------------------------------------------------------- # Callback for slider that sets the color slope. The slope and the offset # set the tanslation between color range and data range. # In maputils we used offset = v-0.5, where v between 0 and 1 # So the offset runs from -0.5 to +0.5. #--------------------------------------------------------------------------- annim = self.getCurrentImage() if annim is None: return annim.cmap.modify(annim.cmap.slope, shift) def updateColorSlope(self, slope, block=True): #--------------------------------------------------------------------------- # Purpose: Callback for Maputils. It will be called after the color # offset is changed # # Prevent chain reactions by temporarily blocking all signals with: # self.slider_slope.blockSignals(True) #--------------------------------------------------------------------------- #TODO: als deze methode als callback in maputils wordt aangeroepen # dan moet je hier de signalen blokkeren om herhaling van de aanroep te voorkomen. if block: self.slider_slope.blockSignals(True) self.slider_slope.setSliderPosition(slope) if block: self.slider_slope.blockSignals(False) def updateColorOffset(self, offset, block=True): #--------------------------------------------------------------------------- # Purpose: Callback for Maputils. It will be called after the color # offset is changed #--------------------------------------------------------------------------- if block: self.slider_offset.blockSignals(True) self.slider_offset.setSliderPosition(offset) if block: self.slider_offset.blockSignals(False) def setColorLut(self, lutstr): #--------------------------------------------------------------------------- # Purpose: Callback for setting the color lut from a list. # # The parameter 'lutstr' is a string from a combobox. # Note that images from the same cube, share # a color map. So changing the colormap will have an effect on all the # images that share the map. #--------------------------------------------------------------------------- annim = self.getCurrentImage() # which object is currently on display? if annim is not None: annim.image.im.cmap.set_source(str(lutstr)) # Change color map def updateColorLut(self, cmindx): #--------------------------------------------------------------------------- # Purpose: Callback for setting the color after interaction with canvas keys # # The parameter cmindx is the index of the lut set by maputils. #--------------------------------------------------------------------------- self.lutlist.setCurrentIndex(cmindx) def setInverseLut(self): #--------------------------------------------------------------------------- # Purpose: Callback for setting the Inverse version of the color map after # pushing the Inverse button (which is checkable) #--------------------------------------------------------------------------- anim = self.getCurrentImage() if anim: anim.key_imagecolors(None, externalkey='9') def updateInverseLut(self, status): #--------------------------------------------------------------------------- # Purpose: Callback for setting the Inverse button after interaction with # canvas keys # # The parameter 'status' is the status set by the canvas key #--------------------------------------------------------------------------- self.buttonInverseLut.setChecked(status) def setColorScale(self, indx): #--------------------------------------------------------------------------- # Purpose: Callback for setting the scale of the color map after # changing its value in the GUI's combo box. #--------------------------------------------------------------------------- if 0 <= indx < len(self.scales): # Scales have character '1' to '5' key = str(indx+1) anim = self.getCurrentImage() if anim: anim.key_imagecolors(None, externalkey=key) def updateColorScale(self, scaleindx): #--------------------------------------------------------------------------- # Purpose: Callback for setting the combobox scale after interaction with # canvas keys # # The parameter 'scaleindx' is the status set by the canvas key #--------------------------------------------------------------------------- self.scalelist.setCurrentIndex(scaleindx) def colorReset(self): #--------------------------------------------------------------------------- # Purpose: Callback for resetting the color map after pushing the gui's # reset button. #--------------------------------------------------------------------------- anim = self.getCurrentImage() if anim: anim.key_imagecolors(None, externalkey='0') def setZoom(self, i, option=None): #--------------------------------------------------------------------------- # Purpose: Callback for setting a zoom factor # TODO: 1) Als je reset, moet je rekening houden met zijpanelen. # Nu wordt het image gemaximaliseerd. # 2) Als je een resize van het scherm doet, dan resized het image mee # ook al heb je een zoom to pixels gedaan. Kan dit kwaad (want je kunt het # herstellen). In ieder geval documenteren. # 3) Kijk nog even goed of de afmetingen in pixels wel integer zijn. # Anders nog afronden. #--------------------------------------------------------------------------- # What are the conditions for this method? im = self.getCurrentImage() if im is None: return gipsy.anyout("Pressed %d in Zoom group, option=%s"%(i,str(option)), 16) offset = self.zoomoptions.index('1') if 0 <= i < len(self.zoomoptions): # One of the button list self.zoomfac = i - offset elif self.zoomfac is None: self.zoomfac = 0 but = self.zoom.button(offset) but.setChecked(True) elif option: if option in ['+', '-', 'f']: # This must be from + or - button cid = self.zoom.checkedId() # Uncheck it first, i.e. if the button exists if 0 <= cid < len(self.zoomoptions): but = self.zoom.button(cid) self.zoom.setExclusive(False) but.setChecked(False) self.zoom.setExclusive(True) if option == '+': self.zoomfac += 1 if option == '-': self.zoomfac -= 1 if option == 'f': self.zoomfac = None if option in ['+', '-']: cid = self.zoomfac + offset if 0 <= cid < len(self.zoomoptions): but = self.zoom.button(cid) but.setChecked(True) if self.zoomfac is None: self.zoomfactor.setText("display") else: s = "Factor: %.4g"%2.0**(self.zoomfac) self.zoomfactor.setText(s) currentcube = im.cubenr cube = self.myCubes.cubelist[currentcube] if 1: #option == 'a': if self.radio1.isChecked(): f = 1.0; ft = "1.0" else: f = None; ft = "" self.myCubes.set_aspectratio(cube, f) nt = self.datatabs.count() for i in range(nt): tabinf = self.getTabInfo(i) if tabinf.cube is cube: tabinf.pixelaspectratio.setText(ft) tofit = (option == 'f') # User wants to maximize image if self.zoomfac is None: f = 0.0 else: f = 2.0**(self.zoomfac) self.myCubes.scaleframe(cube, f, tofit) def applyCoordSettings(self): #--------------------------------------------------------------------------- # Purpose: Callback for setting the options for coordinate retrieval #--------------------------------------------------------------------------- if not self.myCubes: # Is there already a cube? return grids = self.coordinate_grids.isChecked() world = self.coordinate_world.isChecked() worlduf = self.coordinate_worlduf.isChecked() # Unformatted world coordinates imval = self.coordinate_imageval.isChecked() appendcr = self.coordinate_appendcr.isChecked() tocli = self.coordinate_tocli.isChecked() tolog = self.coordinate_tolog.isChecked() gridprec = self.coordinate_gridprec.currentIndex() pixfmt = "%."+"%df"%(gridprec) wcsprec = self.coordinate_wcsprec.currentIndex() wcsfmt = "%."+"%df"%(wcsprec) imprec = self.coordinate_imprec.currentIndex() zfmt = "%."+"%df"%(imprec) dmsprec = self.coordinate_dmsprec.currentIndex() self.myCubes.set_coordinate_mode(grids, world, worlduf, imval, pixfmt, dmsprec, wcsfmt, zfmt, appendcr, tocli, tolog) def drawPanelsFromTabs(self): #--------------------------------------------------------------------------- # Purpose: Callback for drawing the slice panels #--------------------------------------------------------------------------- nt = self.datatabs.count() panelXframes = [] panelYframes = [] for i in range(nt): tabinf = self.getTabInfo(i) C = tabinf.cube # It could be that a user pressed the defaults button. # Then there is a tab, but there is no cube yet! # TODO: Kijk alle tabinf loopjes na om deze situatie te voorkomen if C: frlo = C.movieframeoffset; frhi = C.nummovieframes key = 'DUMMY=' gipsy.wkey("%s%s"%(key, str(tabinf.slicesPanelX.text()))) frames = gipsy.userint(key, defval=[], nmax=10000) frlo = C.movieframeoffset; frhi = C.nummovieframes panelXframes += [d+frlo for d in frames if 0 <= d < frhi] gipsy.wkey("%s%s"%(key, str(tabinf.slicesPanelY.text()))) frames = gipsy.userint(key, defval=[], nmax=10000) panelYframes += [d+frlo for d in frames if 0 <= d < frhi] self.myCubes.set_panelframes(panelXframes, panel='X') self.myCubes.set_panelframes(panelYframes, panel='Y') def drawPanelsFromGlobal(self): #--------------------------------------------------------------------------- # Purpose: Callback for drawing the slice panels #--------------------------------------------------------------------------- key = 'XPANEL=' gipsy.wkey("%s%s"%(key, str(self.xpanels_edit.text()))) panelframes = gipsy.userint(key, defval=[], nmax=10000) self.myCubes.set_panelframes(panelframes, panel='X') key = 'YPANEL=' gipsy.wkey("%s%s"%(key, str(self.ypanels_edit.text()))) panelframes = gipsy.userint(key, defval=[], nmax=10000) self.myCubes.set_panelframes(panelframes, panel='Y') def getfile(self, cb=None, tabinf=None): #--------------------------------------------------------------------------- # Purpose Read file or set from keyword and start creating plot # TODO: Beetje puinhoop. Wel of niet GIPSY keyword linken. Zo niet dan is # is zetten van actuele tab onnodig # # This method is scheduled for each data tab. An extra attribute # (tabinf) is added for the callback, so that we can retrieve # some properties of the current tab data #--------------------------------------------------------------------------- if cb is not None: tabinf = cb.tabinf filename = gipsy.usertext(cb.key, default=2) gipsy.anyout("GETFILE Get defaults via keyword %s file=%s"%(tabinf.keyword, filename), 16) else: filename = str(tabinf.datainput.text()).strip() gipsy.anyout("GETFILE Get defaults via LINEedit filename= %s"%(filename), 16) tabinf.datainput.setText(str(filename)) nt = self.datatabs.count() tids = [] for i in range(nt): tabinf2 = self.getTabInfo(i) if tabinf2.tid == tabinf.tid: self.datatabs.setCurrentIndex(i) gipsy.anyout("GETFILE Get defaults for tid=%d"%tabinf.tid, 16) if 0: #tabinf.cube: gipsy.anyout("GETFILE This cube Should be removed first!!", 16) self.cleanData() tabinf.cube = None else: gipsy.anyout("GETFILE First data for this tab", 16) #self.makeplot(origin="file", tabinf=cb.tabinf) self.setDefaults(tabinf, filename) def getDisplayBox(self, widget): #--------------------------------------------------------------------------- # Purpose: Get limits of image curently on display and set box in box input # field. # # This method is meant to facilitate the reduction of memory. If you # zoom and pan an image so that you only have the interesting part of the # data on display, then you still occupy the memory needed by all the # invisible pixels. For very big images, this can prevent other images # to load in other tabs. If you push the corresponding button, it copies # the current box to the box input field. If you draw the image(s) again, # with the new box, it will save memory (but there is no possibility to # zoom out). # # Note that the box is taken from the image that is on display. # This means that there need not to be any correspondence between # the current data and the data in the current tab. # TODO: Zou je het niet zo maken dat bij het wisselen van tab, # je de laatst gekozen image van bijbehorende data ziet. # Bij het laden van data wordt dat image 0. # Bij herladen hetzelfde. #--------------------------------------------------------------------------- # What is the current image on display. Note annim = self.getCurrentImage() if annim is None: return fr = annim.frame # Get limits in pixels from current frame xlo, xhi = fr.get_xlim() ylo, yhi = fr.get_ylim() # The pixels need to be within the limits of the image on display xlim = annim.pxlim ylim = annim.pylim # Correct for the center of the pixels xlo += 0.5; xhi -= 0.5 ylo += 0.5; yhi -= 0.5 xlo = max(xlim[0], xlo); xlo = min(xlo, xlim[1]) ylo = max(ylim[0], ylo); ylo = min(ylo, ylim[1]) xhi = max(xlim[0], xhi); xhi = min(xhi, xlim[1]) yhi = max(ylim[0], yhi); yhi = min(yhi, ylim[1]) # Convert to grids. This can also be done with pixoffset # which is an attribute of the Annotatedimage object, if # it is in grid mode (annim.gridmode == True). # We do it with the values of crpix xoff = annim.projection.crpix[0] yoff = annim.projection.crpix[1] # Grids are always integer numbers. A pixel should be # included if its center is includen in the box. xlo = nint(xlo-xoff); xhi = nint(xhi-xoff) ylo = nint(ylo-yoff); yhi = nint(yhi-yoff) s = "%d %d %d %d"%(xlo, ylo, xhi, yhi) # Set the new box in the text field. # First find the associated tab and tab info n = self.datatabs.indexOf(widget) currenttabdata = self.datatabs.tabBar().tabData(n) tabinf = currenttabdata.toPyObject() # Back from QVariant to Python object tabinf.boxinput.setText(s) def newdatatabs(self, cb): #------------------------------------------------------------------------ # Purpose: Process ADDTAB= keyword to create new data input tab # # This viewer can view image data from various sources. Each source is # connected to a GIPSY keyword with syntax DATASETn= # where n is a number. This number corresponds to an id of a tab # with fields to enter your data source. But if you want to enter # a source with for example keyword DATASET3=, then there is not yet # a tab or a connected callback scheduled. What a user can do in this # situation, is to create a tab with the ADDTAB= keyword. # The keyword accepts one or a sequence of numbers. The numbers # will set the id of a data tab and the keyword used for data input. # The maximum number of tabs to add with this keyword is limited to 10. # If the program is running, you can use ADDTAB= on the Hermes command # line or with a wkey() from another GIPSY task. #------------------------------------------------------------------------ num = gipsy.userint(cb.key, default=2, defval=[], nmax=10) if not num: return for n in num: self.addDataTab(n) def setclipsRadioButtons(self): #--------------------------------------------------------------------------- # Purpose: Set the clips as a reaction to selecting one of the radio buttons # associated with the setting of clip levels # #--------------------------------------------------------------------------- n = self.datatabs.currentIndex() # Identify the current tab tabinf = self.getTabInfo(n) cube = tabinf.cube if cube: vmin, vmax, clipmode, clipmn = self.readClipLevels(tabinf) cube.setcubeclips(vmin, vmax, clipmode, clipmn) def setclipsLoHi(self): #--------------------------------------------------------------------------- # Purpose: Set the lower and upper clip value, by reading the contents # of the clip fields on the color tab. # # Helper function for setclips(). Empty fields should return the defaults # set on the data tab. A setting is triggered after pressing the Enter button. #--------------------------------------------------------------------------- cliphi = str(self.setclipsformHi.text()).strip() if cliphi: try: cliphi = eval(cliphi) except: self.showErrorMessage("Invalid expression", widget=self.setclipsformHi) return else: cliphi = None cliplo = str(self.setclipsformLo.text()).strip() if cliplo: try: cliplo = eval(cliplo) except: self.showErrorMessage("Invalid expression", widget=self.setclipsformLo) return else: cliplo = None self.setclips(cliplo, cliphi) def setclips(self, cliplo, cliphi): #--------------------------------------------------------------------------- # Purpose: Set the lower and upper clip value. If a value is None, then # fall back to the clip value that was set at load time #--------------------------------------------------------------------------- im = self.getCurrentImage() if not im: return currentcube = im.cubenr cube = self.myCubes.cubelist[currentcube] if not cube: return if cliplo is None: cliplo = cube.vmin_def s = "%g "%(cliplo) self.setclipsformLo.setText(s) if cliphi is None: cliphi = cube.vmax_def s = "%g "%(cliphi) self.setclipsformHi.setText(s) cube.setcubeclips(cliplo, cliphi) # Adjust the image norm of all and set vmin, vmax (assume clipmode == 0) def setclips2(self, cb, clips=None): #------------------------------------------------------------------------ # Callback for CLIPS=. It checks for two numbers, a lower cliplevel # and an upper cliplevel. If nothing is entered, the minimum and # maximum of the current set is copied. If one value is entered, # then the second value (upper cliplevel) is copied from the current set. #------------------------------------------------------------------------ if clips is None: c = [None, None] clips = gipsy.userreal(cb.key, default=2, defval=c, nmax=2) if len(clips) == 1: clips.append(None) im = self.getCurrentImage() currentcube = im.cubenr cube = self.myCubes.cubelist[currentcube] cube.setcubeclips(clips[0], clips[1]) # Adjust the image norm of all and set vmin, vmax (assume clipmode == 0) # Put new values in field. Intercept None's if clips[0] is None: s = "%g "%(cube.vmin) else: s = "%g "%(clips[0]) self.setclipsformLo.setText(s) if clips[1] is None: s = "%g "%(cube.vmax) else: s = "%g "%(clips[1]) self.setclipsformHi.setText(s) def setSplitImage(self): #--------------------------------------------------------------------------- # Purpose: Set the image number of the image that you want to use to # have a splitted view with the current image. #--------------------------------------------------------------------------- # No associated keyword # TODO: uitbreiden met Try except if not self.myCubes: return # No data yet nr = int(self.splitimageedit.text()) self.myCubes.set_splitimagenr(nr) def setAlpha(self, alpha): #--------------------------------------------------------------------------- # Purpose: Set the transparency of current image #--------------------------------------------------------------------------- if not self.myCubes: return # No data yet im = self.getCurrentImage() if not im: return alpha /= 100.0 s = "%.2f"%alpha self.alphaslider_val.setText(s) cube = self.myCubes.cubelist[im.cubenr] self.myCubes.show_transparent(cube, alpha) def setInterpolation(self, ipol): #--------------------------------------------------------------------------- # Purpose: Apply one of Matplotlib's interpolation schemes to the current # plot. Note that this does not alter image data in any way. #--------------------------------------------------------------------------- if not self.myCubes: return # No data yet im = self.getCurrentImage() if not im: return cube = self.myCubes.cubelist[im.cubenr] for aim in cube.imagesinthiscube: aim.image.im.set_interpolation(str(ipol)) #self.setCurrentImage(im) self.canvas.draw() def setpanelframes(self, cb): if cb.panel == 'X': txt = gipsy.usertext(cb.key, default=2, defval='') self.xpanels_edit.setText(txt) elif cb.panel == 'Y': txt = gipsy.usertext(cb.key, default=2, defval='') self.ypanels_edit.setText(txt) #panelframes = gipsy.userint(cb.key, defval=[], nmax=10000) #self.myCubes.set_panelframes(panelframes, panel=cb.panel) def setloopframes(self, cb): #--------------------------------------------------------------------------- # Purpose: Set indices of the images that you want to display in a # sequence (movieloop). If an empty list is entered, all # images are included in the sequence. Note that there is a max. # number of images that you can set (10000). In fact there is # no limit, but the input routine requires one. #--------------------------------------------------------------------------- loopframes = gipsy.userint(cb.key, defval=[], nmax=10000) self.myCubes.set_movieframes(loopframes) """ try: loopframes = gipsy.userint(cb.key, defval=[], nmax=10000) gipsy.anyout(str(loopframes)) except Exception, errmes: e = "No valid GIPSY input: " + str(errmes) showErrorMessage(e) gipsy.anyout(e) return self.myCubes.set_movieframes(loopframes) """ """ txt = gipsy.usertext(cb.key, defval='') gipsy.anyout("LOOPframes text = %s"%(str(txt))) # Update the text field. This is necessary if the keyword (LOPPFRAMES=) was # defined at startup. Then the input field needs to be updated. self.movieframes.blockSignals(True) self.movieframes.setText(txt) self.movieframes.blockSignals(False) """ def markercommandGUI(self, cb=None): #--------------------------------------------------------------------------- # Purpose: Input field is read to set GIPSY keyword. Cannot be done with # QtLink because InputCallback does not allow that ('key already # registered') #--------------------------------------------------------------------------- command = str(self.markerfield.text()).strip() gipsy.wkey("MARKER=%s"%(command)) def plotcommandGUI(self, cb=None): #--------------------------------------------------------------------------- # Purpose: See method plotcommand(). This routine prepares the PLC= keyword # It leaves a command with a recall file unaltered. Other commands # are escaped with backquotes. #--------------------------------------------------------------------------- command = str(self.plotcomfield.text()).strip() if not command.startswith('<'): # Backquotes are not possible if a recall file (with multiple lines) is used # for input. For other commands we prepend and append a backquote. # Multiple commands are separated bu a semi colon. Split these, # insert backquotes and join parts to form a new string. command = ''.join(['`'+p+'`;' for p in command.split(";")]) # Activate the keyword handler (method plotcommand()) gipsy.wkey("PLC=%s"%(command)) def markercommand(self, cb, key=None): #--------------------------------------------------------------------------- # Purpose: Callback function for keyword MARKER= # This keyword can be used to draw markers at positions given by # a user. # # It uses a so called inputcallback from the GIPSY module so that multiple # commands are possible separated by a semi colon and as a consequence # of that, it also accepts recall files. #--------------------------------------------------------------------------- if key is None: key = cb.key if cb: pos = cb.value if type(pos)==tuple: # This is the only way to intercept a wrong input wrt a recall file self.showErrorMessage(str(pos[1]), widget=self.markerfield) return else: pos = gipsy.usertext(key, default=2, defval='') if len(pos) == 0: return im = self.getCurrentImage() cframe = im.frame # Some marker attributes mcol = str(self.markercol.currentText()).strip() msymbol = str(self.markersym.currentText()).strip() mwidth = float(str(self.markerwidth.currentText()).strip()) pos = pos.strip() # Strip remaining spaces if back quotes are used in keyword try: pc = "im.Marker(pos=pos, color=mcol, marker=msymbol, markersize=mwidth)" mrk = eval(pc) mrk.plot(cframe) self.on_draw() except Exception, errmes: self.showErrorMessage(str(errmes), widget=self.markerfield) def MyColbar(self, im, kwargs): #--------------------------------------------------------------------------- # Purpose: Helper function for colorbar method. It intercepts font # related keywords #--------------------------------------------------------------------------- setfontatts = False fontdict = {} # Assemble all keywords related to fonts cleandict = kwargs.copy() k = 'font' # If a font dict is part of kwargs then extract if k in kwargs: fontdict[k] = kwargs[k] del cleandict[k] # Remove 'font' entry setfontatts = True cb = im.frame.figure.colorbar(im.image.im, ax=im.frame, **cleandict) if setfontatts: for t in cb.ax.get_yticklabels(): setp(t, **fontdict['font']) for t in cb.ax.get_xticklabels(): setp(t, **fontdict['font']) def plotcommand(self, cb, key=None): #--------------------------------------------------------------------------- # Purpose: (Experimental) Callback function for keyword PLOTCOMMAND= # This keyword can be used to execute plot commands that are not # part of the viewer set of commands. Usually one uses a wkey() # command with the viewer as task and a set of plot commands. # # Example of an external task: # # !/usr/bin/env python # from gipsy import * # from time import sleep # # task = "visions.py" # init() # try: # xeq(task, keyword="*") # except: # etype, evalue, etrace = sys.exc_info() # efmt = traceback.format_exception(etype, evalue, etrace) # for line in efmt: # anyout(line) # wkey("ADDTAB=100", task) # wkey("DATASET100=m101.fits", task) # wkey("DRAW100=Y", task) # #sleep(2) # plotcom = "text(100, 100, 'PLC demo',fontsize=7, fontweight='bold',\ # color='w', rotation=90,horizontalalignment='left',\ # verticalalignment='top',bbox=dict(color='w', facecolor='red', alpha=0.5))" # wkey("PLC=`%s`"%plotcom, task) # wkey("PLC=plot((0,100),(0,100), 'r');plot((30,100),(80,100), 'g')", task) # wkey("PLC=`contour(im.data, levels=[6000,8000,12000], colors=['g', 'r', 'y'])`", task) # finis() # # It uses a so called inputcallback from the GIPSY module so that multiple # commands are possible separated by a semi colon and as a consequence # of that, it also accepts recall files. #--------------------------------------------------------------------------- if key is None: key = cb.key if cb: pcom = cb.value # If the inputcallback dtects an error such as a non-existing recall file, # it returns a tuple instead of a string. This is the only way to intercept # a wrong input wrt a recall file. The second element in the tuple is the error message if type(pcom)==tuple: self.showErrorMessage(str(pcom[1]), widget=self.plotcomfield) return # This is the value that is send by the InputCallback object else: # Manually and with a keyword not equal to "PLC=" pcom = gipsy.usertext(key, default=2, defval='') if len(pcom) == 0: return im = self.getCurrentImage() cframe = im.frame pcom = pcom.strip() # Strip remaining spaces # Kapteyn Package maputils commands kpc = {'marker':"im.Marker", "beam":"im.Beam", "ruler":"im.Ruler", "contours":"im.Contours", "skypolygon":"im.Skypolygon", "graticule":"im.Graticule", "pixellabels":"im.Pixellabels"} found_kpc = False for k in kpc: if pcom.lower().startswith(k): try: search = r'(?i)'+k+'\s*\((.*)' replace = kpc[k]+r'(\1' pc = re_sub(search, replace, pcom) mrk = eval(pc) mrk.plot(cframe) self.on_draw() except Exception, errmes: self.showErrorMessage(str(errmes), widget=self.plotcomfield) found_kpc = True break # Then it could be a Colorbar command which needs to be modified if not found_kpc: k = "colorbar" # This one differs from the Maputils Colorbar! if pcom.lower().startswith(k): # Match colorbar(*) and return * in kw[0] try: kw = re_match(r'(?i)'+"colorbar"+'\s*\((.*)\)', pcom).groups() # Now we got the argument list. Evaluate these as a dictionary kwargs = eval("dict("+kw[0]+")") self.MyColbar(im, kwargs) self.on_draw() except Exception, errmes: self.showErrorMessage(str(errmes), widget=self.plotcomfield) found_kpc = True # Then it can be a standard Matplotlib command if not found_kpc: s = "cframe."+pcom try: eval(s) self.on_draw() except Exception, errmes: self.showErrorMessage(str(errmes), widget=self.plotcomfield) def triggerAtStart(self): #--------------------------------------------------------------------------- # Purpose: Helper function to start reading keywords ADDTAB= and # DATASETn= in a single-shot timer. # # Add one or more tabs for data input so that one can use # various DATASETn= keywords at startup time. Note that a Triggerkey # of this method (newdatatabs()) is not enough to find all the # tabs that are set before the Triggerkey of the DATASETn= keywords # Therefore we do a plain call to the method that creates these # new tabs and # The DATASETn= keywords are triggered after the corresponding tab # is created (in addDataTab()). We also postponed the creation of # a first tab (which should always be available) until now, otherwise # data in DATASET0= is read befire the form appears, and that is what # we want to prevent. #--------------------------------------------------------------------------- self.addDataTab(0) key = "ADDTAB=" cb = gipsy.KeyCallback(self.newdatatabs, key, schedule=True) self.newdatatabs(cb) def inset(self, cb): #--------------------------------------------------------------------------- # Purpose: Enable user to use old fashioned keyword INSET= # it should add a DATASET#=, ADDTAB=# and DRAW#= keyword # # This problem is how we administer the number '#' associated with the # keywords that we must create. #--------------------------------------------------------------------------- key = "INSET=" value = gipsy.usertext(key, default=2) tabinf = self.getTabInfo(0) # First tab if not tabinf or not tabinf.cube: # This tab is free gipsy.wkey("DATASET0=%s"%(value)) gipsy.wkey("DRAW0=Y") else: tid = self.addDataTab() gipsy.wkey("DATASET%d=%s"%(tid,value)) gipsy.wkey("DRAW%d=Y"%(tid)) # The keyword has been translated to the DATASET0= and can be cancelled. # The macro for restarting with the same keywords will have the same effect. # If you don't cancel the INSET=keyword, you end up in a possible crash # because you try to plot a set twice in the same data tab and the second tries # to use the defaults of the previous set, but that one has not yet been # created. gipsy.cancel(key) def main(): app = QApplication(sys.argv) app.setOrganizationName("Kapteyn Institute") app.setOrganizationDomain("astro.rug.nl") app.setApplicationName("Visions") # The scroll wheel can be used for changing the sliders in this application # The default step is 3 lines. Here we set it to 1 so that scrolling # the movie slider has the same effect as scrolling on the canvas to change # images in the movie. app.setWheelScrollLines (1) gipsy.qtconnect() # connect GIPSY to PyQt events form = AppForm() form.myCubes = None form.show() # Prevent that a data loading process blocks the appearance of # the gui. # Add a single shot timer with a timeout of zero. # This will execute the method given in the second parameter # as soon as the event queue is empty, i.e. when show() has done its # work when we reached exec() QTimer.singleShot(0, form.triggerAtStart) #key = "BOX=" #gipsy.KeyCallback(form.getbox, key, schedule=True) #gipsy.QtLink(key, form.boxform, 'returnPressed()') #gipsy.QtLink(key, form.boxform) #key = "CLIPLO=" #gipsy.KeyCallback(form.setclipsLo, key, schedule=True) #gipsy.QtLink(key, form.setclipsformLo, 'returnPressed()') #key = "CLIPHI=" #gipsy.KeyCallback(form.setclipsHi, key, schedule=True) #gipsy.QtLink(key, form.setclipsformHi, 'returnPressed()') #key = "CLIPS=" #gipsy.KeyCallback(form.setclips, key, schedule=True) #triggerkey(key) #key = "SMFACTOR=" #gipsy.KeyCallback(form.blurfunc, key, schedule=True) #gipsy.QtLink(key, form.smslider) # link to GIPSY keyword #triggerkey(key) """ key = "CSLOPE=" gipsy.KeyCallback(form.modifycolslope, key, schedule=True) #triggerkey(key) key = "COFFSET=" gipsy.KeyCallback(form.modifycoloffset, key, schedule=True) #triggerkey(key) """ key = "MOVIEFRAME=" gipsy.KeyCallback(form.g_setThisImage, key, schedule=True) #gipsy.QtLink(key, form.movieslider) # link to GIPSY keyword gipsy.KeyCallback(form.setloopframes, 'LOOPFRAMES=') #gipsy.KeyCallback(form.plotcommand, "PLOTCOMMAND=") key = "INSET=" gipsy.KeyCallback(form.inset, key) triggerkey(key) # Here we start a timer which displays the memory currently in use # in the status bar. Note that this is best done with a timer, because # one cannot know for example at which moment garbage collection is executed. form.timer = QTimer() form.timer.connect(form.timer, SIGNAL("timeout()"), form.displayMemory) form.timer.start(10000) app.exec_() if __name__ == "__main__": gipsy.init() main() gipsy.finis() # Note that on exit, the application crashes in PyQT 4.7.2 # with a segmentation fault. This happens at least for Ubuntu. # This should be repaired in 4.7.3 # Documentation about this bug can be found at: # https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/561303i