galaxy_ellipse.py 10.6 KB
# -*- coding: utf-8 -*-
title = "RingIntegration"
tip = "elliptic ring integration"
onein = -1

import copy as cp
import numpy as np
from nodmath import extract

from guiqwt import pyplot
from guidata.qt.QtGui import QMessageBox, QApplication, QCursor, QListView, QStandardItemModel, QStandardItem, QFont, QVBoxLayout
from guidata.dataset.datatypes import DataSet
from guidata.dataset.dataitems import (IntItem, StringItem, ChoiceItem, FloatItem, BoolItem)
from guidata.qt.QtCore import Qt
from guiqwt.events import setup_standard_tool_filter, KeyEventMatch
from guiqwt.tools import AnnotatedRectangle, RectangularShapeTool
from guiqwt.curve import PolygonMapItem
from guiqwt.styles import CurveParam
from guiqwt.config import _

def nint(x):
    if x > 0: return int(x+0.5)
    else: return int(x-0.5)


class NOD3_App:

    def __init__(self, parent):
        self.parent = parent
        self.parent.activateWindow()

    def Error(self, msg):
        QMessageBox.critical(self.parent.parent(), title,
                              _(u"Error:")+"\n%s" % str(msg))

    def compute_app(self, **args):
        class FuncParam(DataSet):
            posx = StringItem('Center x:', default="0:0:0")
            posy = StringItem('Center y:', default="0:0:0")
            major = FloatItem('Major axis:', default=0.0)
            #minor = FloatItem('Minor axis:', default=0.0)
            incl = FloatItem('Inclination [deg]:', default=0.0, min=0.0, max=89.0)
            posang = FloatItem('Position angle:', default=0.0)
            #naxis1 = IntItem('shape x')
            #naxis2 = IntItem('shape y')
            ringwidth = FloatItem('Ringwidth [arcsec]:')
            ration = BoolItem("Ratio", default=False)
            cursor = BoolItem("Cursor", default=True)
            outfile = StringItem('Outfile:', default="ringint.out")
            #inclination = FloatItem('Inclination [deg]:', default=0.0)
        self.name = title.replace(" ", "")
        if args == {}:
           param = FuncParam(self.name, "Create the outer ring shape:\n\n(release cursor with the <space> key)")
        else:
           param = self.parent.ScriptParameter(name, args)
        param = self.parent.read_defaults(param)
        if not param.edit():
           return
        self.parent.write_defaults(param)
        # if no parameter needed set param to None. activate next line
        #param = None
        self.parent.del_polygon("RingInt")
        if param.cursor:
           self.parent.get_box_positions(self.name, 
                                      lambda m, p, pos: self.function(m, p, pos),
                                      param, nextbox=False, mshape="Ellipse")
        else:
           self.param = param
           self.parent.compute_11(self.name, lambda m, p: self.function(m, p), None, onein)

    def create_output_list(self):
        # add source list box
        self.sourcelist = QListView()
        self.sourcelist.setWindowTitle('Galaxy ring integration results')
        self.sourcelist.setMinimumSize(650, 200)
        self.sourcelist.setFont(QFont('Monospace'))
        self.model = QStandardItemModel(self.sourcelist)
        self.sourcelist.setModel(self.model)
        self.parent.LView = self.sourcelist
        #self.model.appendRow(QStandardItem(header))

    def PrintListBox(self, M, out):
        if M == 1:
           self.create_output_list()
           text = "  Ring  Major  Minor  Points    Beams   Average     Sigma       Ring Flux   Cumul. Flux"
           self.model.appendRow(QStandardItem(text)) 
           #text =  "          (arcsec)    Not 0     Num   (JY/BEAM )  (JY/BEAM )     (JY)        (JY)"
           text =  "          (arcsec)    Not 0     Num   (%s/BEAM )  (%s/BEAM )     (%s)        (%s)" % (self.unit, self.unit, self.unit, self.unit)
           self.model.appendRow(QStandardItem(text)) 
        self.model.appendRow(QStandardItem(out))
        #self.sourcelist.scrollToBottom()
        self.sourcelist.show()
        
    def print_out(self, filename, M, a, b, close=False):
        if M == 1:
           self.fout = open(filename, 'w')
           out = "# Ring  Major   Minor     Points  Beams     Average     Sigma       Ring Flux     Cumul. Flux"
           self.fout.write(out+"\n")
           #out =  "#         (arcsec)      Not 0     Num     (JY/BEAM)   (JY/BEAM)     (JY)        (JY)"
           out =  "#         (arcsec)      Not 0     Num     (%s/BEAM)   (%s/BEAM)     (%s)        (%s)" % (self.unit, self.unit, self.unit, self.unit)
           self.fout.write(out+"\n")
        beams = self.NUM * self.beam
        out =  str("%5d   %6.1f  %6.1f  %5d    %-9.4g  %-9.4g  %-9.4g  %-9.4g  %-9.4g" % (M, 2*a, 2*b, self.NUM, beams, self.mean, self.std, self.sum, self.cum))  
        #print out
        self.fout.write(out+"\n")
        self.PrintListBox(M, out)
        if close:
           self.fout.close() 

    def _integrate(self, data, r1, r2):
        d = 1*data
        mask1 = r1 <= 1.0
        if type(r2) != type(None):
           mask2 = r2 <= 1.0
           mask = mask1 & ~mask2
        else:
           mask = mask1
        self.cum = np.sum(d[mask1]) * self.beam
        self.sum = np.sum(d[mask]) * self.beam
        self.mean = np.mean(d[mask])
        self.std = np.std(d[mask])
        self.NUM = mask.ravel().tolist().count(True)
        #print self.sum, self.mean
        d[~mask] = np.nan
        return d

    def todeg(self, string):
        d, m, s = string.split(":")
        if string.find("-") == -1:
           sign = 1
        else:
           sign = -1
        ad = abs(float(d))
        return sign*(ad + float(m)/60.0 + float(s)/3600.0)*self.parent.vu

    def todms(self, degree):
        degree = degree/self.parent.vu
        sign = np.sign(degree)
	ad = abs(degree)
        d = int(ad)
        md = (ad-d)*60
        m = int(md)
        s = (md-m)*60
        if sign < 0:
           return str("-%2.2d:%2.2d:%-5.2f" % (d, m, s))
        else:
           return str("%2.2d:%2.2d:%-5.2f" % (d, m, s))

    def Ellipse(self, header, data, p, Pos):
        rows, cols = data.shape
        if type(Pos) == type(None):
           x0 = self.todeg(p.posx)*15
           y0 = self.todeg(p.posy)
           a = p.major/2.0
           #b = p.minor
           b = a * np.cos(p.incl*np.pi/180.0)
           phi = p.posang*np.pi/180.0
        else:
           x0, y0 = Pos[0][:2]
           a = max(Pos[1][:2])/2
           b = min(Pos[1][:2])/2
           p.incl = np.arccos(b/a) * 180.0/np.pi
           angle = Pos[2]
           phi = angle * np.pi/180.0
           if Pos[1][0] < Pos[1][1]:
              phi = -(angle+90) * np.pi/180.0
           else:
              phi = -(angle+180) * np.pi/180.0
        xpix0, ypix0 = self.parent.get_pixel_coordinates(x0, y0)
        p.posx = self.todms(x0/15.)
        p.posy = self.todms(y0)
        p.major = round(2*a, 4)
        p.minor = round(2*b, 4)
        p.posang = round(phi*180./np.pi, 4)
        p.cursor = False
        self.parent.write_defaults(p)
        t = np.pi/180.0 * np.arange(360.0)
        pos = []
        off = []
        col = []
        M = 0
        f = self.parent.vu / 3600.0
        if p.ration:
           f1 = f * a/b
        else:
           f1 = f
        theta = np.pi/2.0 - phi
        rot = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
        yy, xx = np.ogrid[:rows, :cols]
        xr, yr = np.dot(rot, np.array([xx-xpix0, yy-ypix0])) 
        xplt = []
        yplt = [] 
        first = True
        if 'BUNIT' in header:
           self.unit = header['BUNIT']
        else:
           self.unit = "?"
        while b > f*p.ringwidth/4.0 or first:
              first = False
              x = x0 + a*np.cos(theta)*np.cos(t) - b*np.sin(theta)*np.sin(t)
              y = y0 + a*np.sin(theta)*np.cos(t) + b*np.cos(theta)*np.sin(t)
              l = len(x)
              ap = a / self.parent.vu / abs(header['CDELT1'])
              bp = b / self.parent.vu / abs(header['CDELT2'])
              r = (xr/ap)**2 + (yr/bp)**2
              if M > 0:
                 d = self._integrate(data, r1, r)
                 self.print_out(p.outfile, M, A, B)
                 xplt.append(a)
                 yplt.append(self.mean)
                 #if M == 3: return d
              A = np.round(1*a, decimals=1) 
              B = np.round(1*b, decimals=1)
              r1 = 1*r
              pos.append(np.array([x, y]).T)
              off.append([M, M*l])
              col.append([0xffffffff, 0x00000000])
              #col.append([0xff000000, 0x00000000])
              M += 1
              b -= f*p.ringwidth
              a -= f1*p.ringwidth
        d = self._integrate(data, r1, None)
        self.plot(xplt, yplt)
        self.print_out(p.outfile, M, A, B, close=True)
        positions = cp.copy(pos)
        pos = np.concatenate(pos)
        off = np.array(off)
        curveparam = CurveParam(title="PolygonMap", icon="ellipse.png")
        curveparam.label = "RingInt"
        crv = PolygonMapItem(curveparam=curveparam)
        crv.set_data(pos, off, col)
        crv.setTitle(curveparam.label)
        crv.Name = "Polygons"
        #crv.Positions = self.projection(positions)
        crv.Positions = positions
        crv.Color = 'white'
        crv.Linewidth = 1.0
        crv.Pixel = False
        crv.Factor = self.parent.vu
        crv.update_params()
        self.parent.plot.add_item(crv)
        self.parent.plot.replot()
        QApplication.restoreOverrideCursor()

    def projection(self, pos):
        i, j, k = np.shape(pos)
        for m in range(i):
            for n in range(j):
                l, b = pos[m][n]
                x, y = self.parent.get_pixel_coordinates(l, b)
                l1, b1 = self.parent.get_plot_coordinates(x, y)
                pos[m][n] = (l1*self.parent.vu, b1*self.parent.vu)
        return pos

    def plot(self, x, y):
        if self.parent.vu == 3600.0: unit="arcsec"
        elif self.parent.vu == 60.0: unit="arcmin"
        else: unit="degree"
        self.parent.fig = pyplot.figure("Ring Integration")
        pyplot.plot(x, y, "B.")
        pyplot.plot(x, y, "r-")
        #pyplot.legend(pos="TR")
        pyplot.xlabel("Radius [%s]" % unit)
        pyplot.ylabel("Intensity")
        pyplot.zlabel("Ring Integration")
        pyplot.show(mainloop=False)

    def function(self, m, p, pos=None):
        if hasattr(self, "param"): p = self.param
        if not 'BMAJ' in m.header or not 'BMIN' in m.header:
           self.Error("BMAJ and BMIN not found in header, please define the FWHM")
           return [], p
        self.parent.del_polygon("RingInt")
        self.parent.poly_visible = True
        self.parent.delete_item('MapEllipse')
        self.beam = abs(m.header['CDELT1'] * m.header['CDELT2']) / (m.header['BMAJ'] * m.header['BMIN'])
        m.data = self.Ellipse(m.header, m.data, p, pos)
        return [], p