galaxy_segments.py 14 KB
title = "Galaxy Segments"
tip = "Create segmants around a face-on galaxy"
onein = True 

import numpy as np
import copy as cp
import scipy.stats as ss
from scipy import optimize, signal
import nodfitting
from nodmath import map_rotate, map_interpolate

from guiqwt import pyplot
from guidata.qt.QtGui import QMessageBox, QListView, QStandardItemModel, QStandardItem, QFont, QFileDialog
from guidata.qt.QtCore import QObject, SIGNAL, SLOT, QModelIndex, pyqtSlot
from guidata.dataset.datatypes import DataSet
from guidata.dataset.dataitems import (IntItem, StringItem, ChoiceItem, FloatItem, BoolItem)
from guiqwt.config import _
from guiqwt.curve import PolygonMapItem
from nodmath import map_rotate, map_interpolate

RAD = np.pi/180
COLORS = [0xffffffff, 0x00000000]
#COLORS = [0xff000000, 0x00000000]

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

class NOD3ListView(QListView): 

    @pyqtSlot("QModelIndex")
    def ItemClicked(self):
        name = title.replace(" ", "")+".out"
        filename = QFileDialog.getSaveFileName(self, "Save file", name, "Results (*.out)")
        if filename == "":
           return
        rows = self.model().rowCount()
        f = open(filename, 'w')
        for row in range(rows):
            index = self.model().index(row, 0)
            out = self.model().data(index).toString()
            f.write(out+"\n")
        f.close()

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):
            #s = StringItem('s', default="string")
            #i = IntItem('i', default=0, max=100, min=0)
            radius = FloatItem('Radius ["]:', default=500)
            rings = IntItem('Number of rings:', default=6, min=2, max=15)
            sectors = IntItem('Number of sectors/ring:', default=20, min=2, max=180)
            posang = FloatItem('Position angle:', default=37., min=-360.0, max=360.0)
            inclin = FloatItem('Inclination:', default=30., min=0, max=80.0)
            model = ChoiceItem("Model:", (("Sector", "Sector"), ("Segment", "Segment")), default="Sector")
            #rms = FloatItem('Noise', default=5., min=0, max=10.0)
            #cut = BoolItem("Cut map", default=True)
            #choice = ChoiceItem("Unit", ("Degree", "Arcmin", "Arcsec"), default=2)
        self.name = title.replace(" ", "")
        if args == {}:
           param = FuncParam(_(title), "create a galaxy")
        else:
           param = self.parent.ScriptParameter(name, args)

        # if no parameter needed set param to None. activate next line
        #param = None
        self.parent.del_polygon()
        self.parent.compute_11(self.name, lambda m, p: self.function(m, p), param, onein) 

    def copy(self, m):
        mx = cp.copy(m)
        mx.header = m.header.copy()
        mx.data = 1*m.data
        return mx

    def create_circle(self, x0, y0, r, nseg=360):
        th = np.linspace(0, 2*np.pi, nseg)
        PTS = np.empty( (nseg,2), float)
        PTS[:,0] = x0 + r*np.cos(th)
        PTS[:,1] = y0 + r*np.sin(th)
        return PTS

    def create_segments1(self, x0, y0, R0, cdelt, NCIRC, NSEG):
        points = []
        offsets = []
        colors = []
        r0 = 0
        a = (R0)**2 / NCIRC
        r = 0.0
        npts = 0
        self.radii = [0.0]
        self.sectors = []
        for k in range(NCIRC):
            r = np.sqrt(a + r*r)
            pts = self.create_circle(x0, y0, r)
            points.append(pts)
            offsets.append([k, npts])
            colors.append(COLORS)
            npts += pts.shape[0]
            #self.radii.append(r / self.parent.vu / abs(header['CDELT1']))
            self.radii.append(r / self.scale)
        dangle = 360.0/NSEG
        for k in range(NSEG):
            pts = np.empty((2, 2), float)
            pts[0,0] = x0 - r0*np.cos(RAD*k*dangle)
            pts[0,1] = y0 - r0*np.sin(RAD*k*dangle)
            pts[1,0] = x0 - R0*np.cos(RAD*k*dangle)
            pts[1,1] = y0 - R0*np.sin(RAD*k*dangle)
            points.append(pts)
            offsets.append([k, npts])
            colors.append(COLORS)
            npts += pts.shape[0]
            self.sectors.append(k*dangle)
        self.sectors.append(360.0)
        self.array = False
        return points, offsets, colors

    def create_segments2(self, x0, y0, R0, cdelt, NCIRC, NSEG):
        points = []
        offsets = []
        colors = []
        dr = R0 / NCIRC
        # cricles
        a = (R0**2 - (R0-dr)**2) / NSEG
        #r = 0.0
        npts = 0
        self.radii = []
        self.sectors = []
        for k in range(NCIRC):
            r = R0 - k*dr
            pts = self.create_circle(x0, y0, r)
            points.append(pts)
            offsets.append([k, npts])
            colors.append(COLORS)
            npts += pts.shape[0]
            # segments
            n = nint((r**2 - (r-dr)**2) / a)
            #n = (r**2 - (r-dr)**2) / a
            dangle = 360.0/max(1, n)
            self.radii.append(r / self.parent.vu / cdelt)
            sectors = []
            for i in range(nint(n)):
                pts = np.empty((2, 2), float)
                pts[0,0] = x0 - (r-dr)*np.cos(RAD*i*dangle)
                pts[0,1] = y0 - (r-dr)*np.sin(RAD*i*dangle)
                pts[1,0] = x0 - r*np.cos(RAD*i*dangle)
                pts[1,1] = y0 - r*np.sin(RAD*i*dangle)
                points.append(pts)
                offsets.append([i, npts])
                colors.append(COLORS)
                npts += pts.shape[0]
                sectors.append(i*dangle)
            self.sectors.append(sectors+[360.0])
        self.radii.append(0.0)
        self.radii.reverse()
        self.sectors.reverse()
        self.array = True
        return points, offsets, colors

    def overlay(self, header, R0, NCIRC=6, NSEG=20, model="Sector"):
        #x0, y0 = header['CRVAL1']*self.parent.vu, header['CRVAL2']*self.parent.vu
        xpix0, ypix0 = header['CRPIX1'], header['CRPIX2']
        x0, y0 = self.parent.get_plot_coordinates(xpix0, ypix0)
        x0 *= self.parent.vu
        y0 *= self.parent.vu
        cdelt = abs(header['CDELT1'])
        if model == "Sector":
           points, offsets, colors = self.create_segments1(x0, y0, R0, cdelt, NCIRC, NSEG)
        else:
           points, offsets, colors = self.create_segments2(x0, y0, R0, cdelt, NCIRC, NSEG)
        # save data for matlabplot
        positions = cp.copy(points)
        points = np.concatenate(points)
        crv = PolygonMapItem()
        crv.set_data(points, offsets, colors)
        crv.Name = "Polygons"
        crv.Positions = positions
        crv.Color = 'white'
        crv.Linewidth = 1.0
        crv.Pixel = False
        crv.Factor = self.parent.vu
        self.parent.plot.add_item(crv)
        self.parent.plot.replot()

    def rotate(self, m, angle):
        q = {'angle': angle, 'mode': 'constant', 'reshape': False, 'prefilter': True, 'order': 3}
        param = self.parent.ScriptParameter("Rotation", q)
        rows1, cols1 = m.data.shape
        pix = (cols1+1)/2.0
        piy = (rows1+1)/2.0
        m.header['CROTA1'] = angle
        m.header['CROTA2'] = angle
        m.header['CTYPE1'] = m.header['CTYPE1'][:-3] + "DES"
        m.header['CTYPE2'] = m.header['CTYPE2'][:-3] + "DES"
        if 'CROTA1' in m.header: m.header['CROTA1'] = 0.0
        if 'CROTA2' in m.header: m.header['CROTA2'] = 0.0
        #x, y = self.parent.get_plot_coordinates(pix, piy)
        param.angle = angle - 90.0
        m.data = map_rotate(m.data, param)
        rows, cols = m.data.shape
        #m.header['CRVAL1'] = x
        #m.header['CRVAL1'] = y
        m.header['CRPIX1'] = (cols+1)/2.0
        m.header['CRPIX2'] = (rows+1)/2.0
        #m = self.parent.removeNANedges(m)
        return m

    def de_incl(self, m, incl):
        rows, cols = m.data.shape
        s = abs(np.cos(RAD*incl))
        Rows = nint(rows*s)
        dr2 = (rows-Rows)/2
        data = 1*m.data[dr2:rows-dr2,]
        y, x = np.indices((rows, cols))
        pix = np.array([x.ravel(), s*y.ravel()])
        Data = map_interpolate(data, pix[0], pix[1])
        m.data = np.array(Data).reshape((rows, cols))
        return m

    def sector_mask(self, centre, radius, angle_range):
        cx, cy = centre
        rad1, rad2 = radius
        rad1 = rad1 + 1
        tmin, tmax = np.deg2rad(angle_range)
        tmin = tmin + abs(tmax-tmin)/20.
        if tmax < tmin:
           tmax += 2*np.pi
        R2 = (self.X-cx)*(self.X-cx) + (self.Y-cy)*(self.Y-cy)
        theta = np.arctan2(self.X-cx, self.Y-cy) - tmin
        theta %= (2*np.pi)
        circmask1 = R2 >= rad1*rad1
        circmask2 = R2 <= rad2*rad2
        circmask = circmask1*circmask2
        anglemask = theta <= (tmax-tmin)
        return circmask*anglemask
         
    def create_output_list(self):
        # add source list box
        self.segmentlist = NOD3ListView()
        self.segmentlist.setWindowTitle('Galaxy segment integration results')
        self.segmentlist.setMinimumSize(650, 200)
        self.segmentlist.setFont(QFont('Monospace'))
        self.model = QStandardItemModel(self.segmentlist)
        self.segmentlist.setModel(self.model)
        self.parent.LView = self.segmentlist
        QObject.connect(self.segmentlist, SIGNAL("clicked(QModelIndex)"),
		        self.segmentlist, SLOT("ItemClicked(QModelIndex)"))

    def PrintOut(self, radius, sector, data, UQ):
        if self.N == 1:
           self.create_output_list() 
           if UQ:
              text = "# Radius     Segment       Points   Beams     Average     Sigma"
           else:
              text = "# Radius     Segment       Points   Beams     Average     Sigma        Segment Flux"
           self.model.appendRow(QStandardItem(text))
           if UQ:
              text = "#(ARCSEC)    (DEGREE)      Not 0    Num       (DEGREE)    (DEGREE)"
           else:
              text = "#(ARCSEC)    (DEGREE)      Not 0    Num       (JY/BEAM)   (JY/BEAM)       (JY)"
           self.model.appendRow(QStandardItem(text))
           self.uqdata = []
        d = data.ravel()
        msk = ~np.isnan(d)
        d = d[msk]
        NUM = len(d) * self.beam
        mean = np.mean(d)
        self.uqdata.append(mean)
        std = np.std(d)
        if UQ:
           std = np.std(self.PI[self.mask])/np.mean(self.PI[self.mask]) * 90.0/np.pi
           #std = self.std[self.N-1] * (1.0/(1.0+self.parang[self.N-1]**2)) * 90.0/np.pi
        else:
           self.std.append(std)
        isum = np.sum(d) * self.beam 
        R = radius[1] * self.scale
        if UQ:
           out = str("  %5.1f    %5.1f  %5.1f     %5.1f  %5.1f       %-9.4g  %-9.4g" % (R, sector[0], sector[1], len(d), NUM, mean, std))
        else:
           out = str("  %5.1f    %5.1f  %5.1f     %5.1f  %5.1f       %-9.4g  %-9.4g    %-9.4g" % (R, sector[0], sector[1], len(d), NUM, mean, std, isum))
        self.model.appendRow(QStandardItem(out))
        #self.segmentlist.scrollToBottom()
        self.segmentlist.show()

    def _integrate(self, centre, data, UQ=False):
        self.X, self.Y = np.ogrid[:data.shape[0],:data.shape[1]]
        self.N = 0
        for ir in range(len(self.radii)-1):
            if not self.array:
               sect = self.sectors
            else:
               sect = self.sectors[ir]
            radius = (self.radii[ir], self.radii[ir+1])
            for it in range(len(sect)-1):
                sector = (sect[it], sect[it+1])
                #print radius, sector
                mask = self.sector_mask(centre, radius, sector)
                self.mask = mask
                if UQ: 
                   data[mask] = self.parang[self.N]
                #data[mask] = np.nan
                #if ir==4 and it==3: data[mask] = np.nan
                #elif ir==3 and it==7: data[mask] = np.nan
                #elif ir==3 and it==8: data[mask] = np.nan
                #elif ir==0 and it==1: data[mask] = np.nan
                #elif ir==1 and it==0: data[mask] = np.nan
                self.N += 1
                self.PrintOut(radius, sector, data[mask], UQ)
        #return data

    def function(self, m, p):
        # inclination correction of beamsize:
	m.header['BMAJ'] /= np.cos(p.inclin * np.pi/180.0)
        self.beam = abs(m.header['CDELT1'] * m.header['CDELT2']) / (m.header['BMAJ'] * m.header['BMIN'])
        self.parent.del_polygon()
        self.parent.poly_visible = True
        if "TMPROT" in m.header:
           if abs(m.header['TMPROT'] - p.posang) > 0.1:
              m.data = map_rotate(m.data, param)
              self.update = True
           else:
              self.update = False
        else:
           m = self.rotate(m, p.posang)
           m = self.de_incl(m, p.inclin)
           self.update = True
        m.header['TMPROT'] = p.posang
        if self.update:
           self.parent.add_object(m)
           self.parent.refresh_plot()
        self.scale = self.parent.vu * abs(m.header['CDELT1'])
        self.overlay(m.header, p.radius, NCIRC=p.rings, NSEG=p.sectors, model=p.model)
        #m.data = self._integrate((m.header['CRPIX1'], m.header['CRPIX2']), m.data)
        self.std = []
        self._integrate((m.header['CRPIX1'], m.header['CRPIX2']), m.data)
        # check for U and Q maps
        if 'MAPTYPE' in m.header and m.header['MAPTYPE'][0] == "U":
           if hasattr(self.parent, "Umap"):
              del self.parent.Umap
              del self.parent.U
              if hasattr(self.parent, "Qmap"):
                 del self.parent.Qmap
                 del self.parent.Q
           self.parent.Umap = self.uqdata
           self.parent.U = m.data
        elif 'MAPTYPE' in m.header and m.header['MAPTYPE'][0] == "Q":
           self.parent.Qmap = self.uqdata
           self.parent.Q = m.data
           self.parang = np.arctan2(self.parent.Umap, self.parent.Qmap) * 90.0/np.pi + 90.0
           self.PI = np.sqrt(self.parent.U**2 + self.parent.Q**2)
           self._integrate((m.header['CRPIX1'], m.header['CRPIX2']), m.data, UQ=True)
           del self.parent.Umap
           del self.parent.Qmap
        return [], p