示例#1
0
    def test_8485_1901_bpt_no_figure(self):

        maps = Maps(plateifu=self.plateifu)
        bpt_return = maps.get_bpt(show_plot=False,
                                  return_figure=False,
                                  use_oi=False)

        self.assertIsInstance(bpt_return, dict)
示例#2
0
    def test_8485_1901_bpt_snr_min(self):

        maps = Maps(plateifu=self.plateifu)
        masks = maps.get_bpt(snr_min=5, return_figure=False, show_plot=False)

        for em_mech in self.emission_mechanisms:
            self.assertIn(em_mech, masks.keys())

        self.assertEqual(np.sum(masks['sf']['global']), 28)
        self.assertEqual(np.sum(masks['sf']['sii']), 112)
示例#3
0
    def test_8485_1901_bpt_no_oi(self):

        maps = Maps(plateifu=self.plateifu)
        masks, figure = maps.get_bpt(show_plot=False,
                                     return_figure=True,
                                     use_oi=False)
        self.assertIsInstance(figure, plt.Figure)

        for em_mech in self.emission_mechanisms:
            self.assertIn(em_mech, masks.keys())

        self.assertNotIn('oi', masks['sf'].keys())

        self.assertEqual(np.sum(masks['sf']['global']), 149)
        self.assertEqual(np.sum(masks['sf']['sii']), 176)
示例#4
0
    def test_8485_1901_bpt_snr_deprecated(self):

        maps = Maps(plateifu=self.plateifu)

        with warnings.catch_warnings(record=True) as warning_list:
            masks = maps.get_bpt(snr=5, return_figure=False, show_plot=False)

        self.assertTrue(len(warning_list) == 1)
        self.assertEqual(
            str(warning_list[0].message),
            'snr is deprecated. Use snr_min instead. '
            'snr will be removed in a future version of marvin')

        for em_mech in self.emission_mechanisms:
            self.assertIn(em_mech, masks.keys())

        self.assertEqual(np.sum(masks['sf']['global']), 28)
        self.assertEqual(np.sum(masks['sf']['sii']), 112)
class SpiralGalaxy(object):
    def __init__(self, file_path):
        self.file_path = file_path
        self.filename = self.file_path.split('/')[-1]
        self.data = gz3d_fits.gz3d_fits(file_path)
        self.mangaid = self.data.metadata['MANGAID'][0]

        self.maps = Maps(self.mangaid)
        self.hamap = self.maps.emline_gflux_ha_6564
        self.hbmap = self.maps.emline_gflux_hb_4862
        self.eff_rad = self.maps.nsa['elpetro_th50_r'] * 2
        self.redshift = self.maps.nsa['z']
        self.mass = self.maps.nsa['sersic_mass']
        self.lgmass = np.log10(self.mass)
        self.elpetro_ba = self.maps.nsa['elpetro_ba']
        self.theta = np.radians(self.maps.nsa['elpetro_phi'] - 90.0)
        self.map_shape = self.hamap.shape

        self.d_mpc = ((299792.458 * self.redshift) / 70)  #Mpc
        self.d_kpc = self.d_mpc * 1E3
        self.d_m = self.d_mpc * 3.085677581E+22  # m
        self.delta = (4 * np.pi * (self.d_m**2)) / ((2.8**2.36) * (10**41.1))
        self.spax_area = (0.0000024240684055477 * self.d_kpc)**2

        self.global_df_loaded = False
        self.bpt_masks_loaded = False
        self.r_array_loaded = False

    def __repr__(self):
        return 'MaNGA ID {}'.format(self.mangaid)

    def check_usability(self, threshold=5, pix_percentage=1.5):
        image_spiral_mask = self.data.spiral_mask
        pixels_above_threshold = (image_spiral_mask >= threshold).sum()

        if (pixels_above_threshold * 100 / image_spiral_mask.size <
                pix_percentage):
            return False

        return True

    def make_emmasks(self):
        '''Takes masks from the MaNGA maps and if a spaxel is flagges as
        "DO NOT USE (2**30) then it marks it as "0". Creates global maek objects
        from these.'''

        self.ha_mask_array = self.hamap.mask.flatten()
        self.hb_mask_array = self.hbmap.mask.flatten()

        for i in range(len(self.ha_mask_array)):
            if self.ha_mask_array[i] & 1073741824 == 0:
                self.ha_mask_array[i] = 0
                self.hb_mask_array[i] = 0
            else:
                self.ha_mask_array[i] = 1
                if self.hb_mask_array[i] & 1073741824 == 0:
                    self.hb_mask_array[i] = 0
                else:
                    self.hb_mask_array[i] = 1

        self.ha_mask_array = np.array(self.ha_mask_array, dtype=bool)
        self.hb_mask_array = np.array(self.hb_mask_array, dtype=bool)

    def make_r_array(self):
        '''Goes through all spaxels and creates a global array of spaxel distances from the map centre'''

        if not self.r_array_loaded:
            r_array = np.array([])

            a, b = self.map_shape
            k, h = (a - 1) / 2.0, (b - 1) / 2.0  #map centre

            for y, x in [(y, x) for y in range(a) for x in range(b)]:
                j, i = (-1 * (y - k), x - h)  #vector from centre

                spax_angle = (np.arctan(j / i)) - self.theta
                vec_len = (j**2.0 + i**2.0)**0.5
                r = vec_len * ((np.cos(spax_angle))**2.0 + (
                    (np.sin(spax_angle)) / self.elpetro_ba)**2.0)**0.5

                r_array = np.append(r_array, r)

            self.r_array = r_array

            self.r_array_loaded = True

    def update_spirals(self,
                       spiral_threshold=3,
                       other_threshold=3,
                       ret_spiral_bool=False):
        '''Do you want to change the parametres for what does/doesn't count as a spaxel or a
        non-spaxel? Use this function to simply update the "Spiral" column of the global DF'''

        self.data.make_all_spaxel_masks(grid_size=self.map_shape)

        center_mask_spaxel_bool = self.data.center_mask_spaxel > other_threshold
        star_mask_spaxel_bool = self.data.star_mask_spaxel > other_threshold
        bar_mask_spaxel_bool = self.data.bar_mask_spaxel > other_threshold
        spiral_mask_spaxel_bool = self.data.spiral_mask_spaxel > spiral_threshold

        combined_mask = center_mask_spaxel_bool | star_mask_spaxel_bool | bar_mask_spaxel_bool

        spiral_spaxel_bool = spiral_mask_spaxel_bool & (~combined_mask)

        if ret_spiral_bool:
            return spiral_spaxel_bool

        self.spiral_spaxel_bool = spiral_spaxel_bool
        self.df['Spiral Arm'] = spiral_spaxel_bool.flatten()

    def load_btp_masks(self):
        '''Simple make arrays of indicating the BPT classification of each spxel.
        Then we append this to the global DF later.'''

        if not self.bpt_masks_loaded:
            bpt_masks = self.maps.get_bpt(return_figure=False, show_plot=False)

            self.comp = bpt_masks['comp']['global']
            self.agn = bpt_masks['agn']['global']
            self.seyfert = bpt_masks['seyfert']['global']
            self.liner = bpt_masks['liner']['global']

            self.bpt_masks_loaded = True

    def flux2sfr(self, ha_flux, ha_stdv, hb_flux, hb_stdv, avg=False):
        '''Take an H-alpha and H-beta flux, and then make an SFR measurement out of them.'''

        ha_flux = ha_flux * 1E-13
        hb_flux = hb_flux * 1E-13

        ha_stdv = ha_stdv * 1E-13
        hb_stdv = hb_stdv * 1E-13

        sfr = (self.delta * (ha_flux**3.36) * (hb_flux**-2.36))
        sfr_stdv = np.sqrt((3.36 * self.delta * (ha_flux**2.36) *
                            (hb_flux**-2.36) * ha_stdv)**2 +
                           (-2.36 * self.delta * (ha_flux**3.36) *
                            (hb_flux**-3.36) * hb_stdv)**2)

        if avg:
            return sfr / self.spax_area, sfr_stdv / self.spax_area

        return sfr, sfr_stdv

    def form_global_df(self, spiral_threshold=3, other_threshold=3):
        '''Make a global DF.'''

        if not self.global_df_loaded:
            self.load_btp_masks()
            self.make_r_array()
            self.make_emmasks()

            ha_array = self.hamap.value.flatten()
            sig_ha_array = self.hamap.error.value.flatten()
            ha_snr = self.hamap.snr.flatten()

            hb_array = self.hbmap.value.flatten()
            sig_hb_array = self.hbmap.error.value.flatten()
            hb_snr = self.hbmap.snr.flatten()

            comp_array = self.comp.flatten()
            agn_array = self.agn.flatten()
            seyfert_array = self.seyfert.flatten()
            liner_array = self.liner.flatten()

            data_array = np.array([
                self.r_array, ha_array, sig_ha_array, ha_snr, hb_array,
                sig_hb_array, hb_snr, comp_array, agn_array, seyfert_array,
                liner_array
            ]).transpose()

            df = pd.DataFrame(data=data_array,
                              columns=[
                                  'Radius', '$H_{\\alpha}$',
                                  '$\sigma H_{\\alpha}$', 'S/N $H_{\\alpha}$',
                                  '$H_{\\beta}$', '$\sigma H_{\\beta}$',
                                  'S/N $H_{\\beta}$', 'Comp', 'AGN', 'Seyfert',
                                  'Liner'
                              ])

            df['$r/r_e$'] = df['Radius'] / self.eff_rad

            df.iloc[self.ha_mask_array,
                    df.columns.get_loc('$H_{\\alpha}$')] = np.nan
            df.iloc[self.ha_mask_array,
                    df.columns.get_loc('$\sigma H_{\\alpha}$')] = np.nan
            df.iloc[self.ha_mask_array,
                    df.columns.get_loc('$H_{\\beta}$')] = np.nan
            df.iloc[self.ha_mask_array,
                    df.columns.get_loc('$\sigma H_{\\beta}$')] = np.nan

            df.iloc[self.hb_mask_array,
                    df.columns.get_loc('$H_{\\beta}$')] = np.nan
            df.iloc[self.hb_mask_array,
                    df.columns.get_loc('$\sigma H_{\\beta}$')] = np.nan

            df = df.replace([np.inf, -np.inf], np.nan)

            self.df = df
            self.update_spirals(spiral_threshold=spiral_threshold,
                                other_threshold=other_threshold)

            self.global_df_loaded = True

    def cov_matrix_maker(self, err_series):
        '''Calculates the covaraince matrix for our galaxy.That is rather intensive.'''

        corr_matrix = np.load(
            '/raid5/homes/sshamsi/galaxy_zoo/GZ3D_spiral_analysis/Matrices/corr_matrices/corr_matrix'
            + str(self.map_shape[0]) + '.npy')

        r = self.hamap.size
        cov_mat = np.zeros((r, r))

        for item, frame in err_series.iteritems():
            if pd.isnull(frame):
                k = 0
            else:
                k = frame

            cov_mat[item] = corr_matrix[item] * k
            cov_mat[:, item] = corr_matrix[:, item] * k

        return cov_mat

    def make_cov_matrices(self, mode=None):
        '''This method loads and returns the H-a/H-b covariance matrix. If not available,
        it calculates it, which is resource intensive.'''

        if mode == None:
            raise ValueError('Argument "mode" must be set to "ha" or "hb".')

        elif mode == 'ha':
            hafile = pathlib.Path("Matrices/cov_matrices/" +
                                  self.filename.split('.')[0] + 'ha.npy')
            print(hafile)

            if hafile.exists():
                ha_cov = np.load(hafile)
                return ha_cov

            else:
                print("H-a covariance file does not exist. Calculating...")
                ha_cov = self.cov_matrix_maker(self.df['$\sigma H_{\\alpha}$'])
                return ha_cov

        elif mode == 'hb':
            hbfile = pathlib.Path("Matrices/cov_matrices/" +
                                  self.filename.split('.')[0] + 'hb.npy')
            print(hbfile)

            if hbfile.exists():
                hb_cov = np.load(hbfile)
                return hb_cov

            else:
                print("H-b covariance file does not exist. Calculating...")
                hb_cov = self.cov_matrix_maker(self.df['$\sigma H_{\\beta}$'])
                return hb_cov

    def get_sfr(self, index, avg=False):
        '''Return the SFR for a bin of spxels.'''

        ha_flux, ha_stdv = self.get_emission(index, mode='ha', avg=avg)
        hb_flux, hb_stdv = self.get_emission(index, mode='hb', avg=avg)

        return self.flux2sfr(ha_flux, ha_stdv, hb_flux, hb_stdv, avg=avg)

    def get_emission(self, index, mode=None, avg=False):
        '''Return the H-a or H-b flux.'''

        self.form_global_df()

        set_index = set(index)
        tot_index = list(self.df.index)

        w_vec = np.array([[x in set_index for x in tot_index]]) * 1

        if mode == None:
            raise ValueError('Argument "mode" must be "ha", or "hb".')
        elif mode == 'ha':
            summ = self.df.loc[index.tolist(), '$H_{\\alpha}$'].sum()

            ha_cov = self.make_cov_matrices(mode=mode)
            cov_mat = ha_cov
        elif mode == 'hb':
            summ = self.df.loc[index.tolist(), '$H_{\\beta}$'].sum()

            hb_cov = self.make_cov_matrices(mode=mode)
            cov_mat = hb_cov

        var = np.linalg.multi_dot([w_vec, cov_mat, w_vec.T])[0][0]

        if avg:
            n = len(index)
            return summ / n, np.sqrt(var / (n**2))

        return summ, np.sqrt(var)
示例#6
0
class SpiralGalaxy(object):
    def __init__(self, file_path):
        self.file_path = file_path
        self.data = gz3d_fits.gz3d_fits(file_path)
        self.mangaid = self.data.metadata['MANGAID'][0]

        self.maps = Maps(self.mangaid)
        self.hamap = self.maps.emline_gflux_ha_6564
        self.hbmap = self.maps.emline_gflux_hb_4862
        self.eff_rad = self.maps.nsa['elpetro_th50_r'] * 2
        self.redshift = self.maps.nsa['z']
        self.mass = self.maps.nsa['sersic_mass']
        self.lgmass = np.log10(self.mass)
        self.elpetro_ba = self.maps.nsa['elpetro_ba']
        self.theta = np.radians(self.maps.nsa['elpetro_phi'] - 90.0)
        self.map_shape = self.hamap.shape

        self.d_mpc = ((299792.458 * self.redshift) / 70)  #Mpc
        self.d_kpc = self.d_mpc * 1E3
        self.d_m = self.d_mpc * 3.085677581E+22  # m
        self.delta = (4 * np.pi * (self.d_m**2)) / ((2.8**2.36) * (10**41.1))
        self.spax_area = (0.0000024240684055477 * self.d_kpc)**2

        self.global_df_loaded = False
        self.bpt_masks_loaded = False
        self.r_array_loaded = False

    def __repr__(self):
        return 'MaNGA ID {}'.format(self.mangaid)

    def check_usability(self, threshold=5, pix_percentage=1.5):
        image_spiral_mask = self.data.spiral_mask
        pixels_above_threshold = (image_spiral_mask >= threshold).sum()

        if (pixels_above_threshold * 100 / image_spiral_mask.size <
                pix_percentage):
            return False

        return True

    def make_emmasks(self):
        self.ha_mask_array = self.hamap.mask.flatten()
        self.hb_mask_array = self.hbmap.mask.flatten()

        for i in range(len(self.ha_mask_array)):
            if self.ha_mask_array[i] & 1073741824 == 0:
                self.ha_mask_array[i] = 0
                self.hb_mask_array[i] = 0
            else:
                self.ha_mask_array[i] = 1
                if self.hb_mask_array[i] & 1073741824 == 0:
                    self.hb_mask_array[i] = 0
                else:
                    self.hb_mask_array[i] = 1

        self.ha_mask_array = np.array(self.ha_mask_array, dtype=bool)
        self.hb_mask_array = np.array(self.hb_mask_array, dtype=bool)

    def make_r_array(self):
        if not self.r_array_loaded:
            r_array = np.array([])

            a, b = self.map_shape
            k, h = (a - 1) / 2.0, (b - 1) / 2.0  #map centre

            for y, x in [(y, x) for y in range(a) for x in range(b)]:
                j, i = (-1 * (y - k), x - h)  #vector from centre

                spax_angle = (np.arctan(j / i)) - self.theta
                vec_len = (j**2.0 + i**2.0)**0.5
                r = vec_len * ((np.cos(spax_angle))**2.0 + (
                    (np.sin(spax_angle)) / self.elpetro_ba)**2.0)**0.5

                r_array = np.append(r_array, r)

            self.r_array = r_array

            self.r_array_loaded = True

    def update_spirals(self,
                       spiral_threshold=3,
                       other_threshold=3,
                       ret_spiral_bool=False):
        self.data.make_all_spaxel_masks(grid_size=self.map_shape)

        center_mask_spaxel_bool = self.data.center_mask_spaxel > other_threshold
        star_mask_spaxel_bool = self.data.star_mask_spaxel > other_threshold
        bar_mask_spaxel_bool = self.data.bar_mask_spaxel > other_threshold
        spiral_mask_spaxel_bool = self.data.spiral_mask_spaxel > spiral_threshold

        combined_mask = center_mask_spaxel_bool | star_mask_spaxel_bool | bar_mask_spaxel_bool

        spiral_spaxel_bool = spiral_mask_spaxel_bool & (~combined_mask)

        if ret_spiral_bool:
            return spiral_spaxel_bool

        self.spiral_spaxel_bool = spiral_spaxel_bool
        self.df['Spiral Arm'] = spiral_spaxel_bool.flatten()

    def load_btp_masks(self):
        if not self.bpt_masks_loaded:
            bpt_masks = self.maps.get_bpt(return_figure=False, show_plot=False)

            self.comp = bpt_masks['comp']['global']
            self.agn = bpt_masks['agn']['global']
            self.seyfert = bpt_masks['seyfert']['global']
            self.liner = bpt_masks['liner']['global']

            self.bpt_masks_loaded = True

    def flux2sfr(self, ha_flux, ha_stdv, hb_flux, hb_stdv):
        ha_flux = ha_flux * 1E-13
        hb_flux = hb_flux * 1E-13

        ha_stdv = ha_stdv * 1E-13
        hb_stdv = hb_stdv * 1E-13

        sfr = (self.delta * (ha_flux**3.36) *
               (hb_flux**-2.36)) / self.spax_area
        sfr_stdv = np.sqrt((3.36 * self.delta * (ha_flux**2.36) *
                            (hb_flux**-2.36) * ha_stdv)**2 +
                           (-2.36 * self.delta * (ha_flux**3.36) *
                            (hb_flux**-3.36) * hb_stdv)**2) / self.spax_area

        return sfr, sfr_stdv

    def form_global_df(self, spiral_threshold=3, other_threshold=3):
        if not self.global_df_loaded:
            self.load_btp_masks()
            self.make_r_array()
            self.make_emmasks()

            ha_array = self.hamap.value.flatten()
            sig_ha_array = self.hamap.error.value.flatten()
            ha_snr = self.hamap.snr.flatten()

            hb_array = self.hbmap.value.flatten()
            sig_hb_array = self.hbmap.error.value.flatten()
            hb_snr = self.hbmap.snr.flatten()

            comp_array = self.comp.flatten()
            agn_array = self.agn.flatten()
            seyfert_array = self.seyfert.flatten()
            liner_array = self.liner.flatten()

            data_array = np.array([
                self.r_array, ha_array, sig_ha_array, ha_snr, hb_array,
                sig_hb_array, hb_snr, comp_array, agn_array, seyfert_array,
                liner_array
            ]).transpose()

            df = pd.DataFrame(data=data_array,
                              columns=[
                                  'Radius', '$H_{\\alpha}$',
                                  '$\sigma H_{\\alpha}$', 'S/N $H_{\\alpha}$',
                                  '$H_{\\beta}$', '$\sigma H_{\\beta}$',
                                  'S/N $H_{\\beta}$', 'Comp', 'AGN', 'Seyfert',
                                  'Liner'
                              ])

            df['$r/r_e$'] = df['Radius'] / self.eff_rad

            df.iloc[self.ha_mask_array,
                    df.columns.get_loc('$H_{\\alpha}$')] = np.nan
            df.iloc[self.ha_mask_array,
                    df.columns.get_loc('$\sigma H_{\\alpha}$')] = np.nan
            df.iloc[self.ha_mask_array,
                    df.columns.get_loc('$H_{\\beta}$')] = np.nan
            df.iloc[self.ha_mask_array,
                    df.columns.get_loc('$\sigma H_{\\beta}$')] = np.nan

            df.iloc[self.hb_mask_array,
                    df.columns.get_loc('$H_{\\beta}$')] = np.nan
            df.iloc[self.hb_mask_array,
                    df.columns.get_loc('$\sigma H_{\\beta}$')] = np.nan

            df = df.replace([np.inf, -np.inf], np.nan)

            self.df = df
            self.update_spirals(spiral_threshold=spiral_threshold,
                                other_threshold=other_threshold)

            self.global_df_loaded = True

    def cov_matrix_maker(self, err_series):
        corr_matrix = np.load(
            '/raid5/homes/sshamsi/galaxy_zoo/GZ3D_spiral_analysis/forming_cov_matrices/corr_matrices/corr_matrix'
            + str(self.map_shape[0]) + '.npy')

        r = self.hamap.size
        cov_mat = np.zeros((r, r))

        for item, frame in err_series.iteritems():
            if pd.isnull(frame):
                k = 0
            else:
                k = frame

            cov_mat[item] = corr_matrix[item] * k
            cov_mat[:, item] = corr_matrix[:, item] * k

        return cov_mat

    def make_cov_matrices(self, mode=None):
        if mode == None:
            raise ValueError('Argument "mode" must be set to "ha" or "hb".')

        elif mode == 'ha':
            ha_cov = self.cov_matrix_maker(self.df['$\sigma H_{\\alpha}$'])

            return ha_cov

        elif mode == 'hb':
            hb_cov = self.cov_matrix_maker(self.df['$\sigma H_{\\beta}$'])

            return hb_cov

    def get_sfr(self, index, avg=False):
        ha_flux, ha_stdv = self.get_emission(index, mode='ha', avg=avg)
        hb_flux, hb_stdv = self.get_emission(index, mode='hb', avg=avg)

        return self.flux2sfr(ha_flux, ha_stdv, hb_flux, hb_stdv)

    def get_emission(self, index, mode=None, avg=False):
        set_index = set(index)
        tot_index = list(self.df.index)

        w_vec = np.array([[x in set_index for x in tot_index]]) * 1

        if mode == None:
            raise ValueError('Argument "mode" must be "ha", or "hb".')
        elif mode == 'ha':
            summ = self.df.loc[index.tolist(), '$H_{\\alpha}$'].sum()

            ha_cov = self.make_cov_matrices(mode=mode)
            cov_mat = ha_cov
        elif mode == 'hb':
            summ = self.df.loc[index.tolist(), '$H_{\\beta}$'].sum()

            hb_cov = self.make_cov_matrices(mode=mode)
            cov_mat = hb_cov

        var = np.linalg.multi_dot([w_vec, cov_mat, w_vec.T])[0][0]

        if avg:
            n = len(index)
            return summ / n, np.sqrt(var / (n**2))

        return summ, np.sqrt(var)
class SpiralGalaxy(object):
    def __init__(self, file_path):
        self.file_path = file_path
        self.data = gz3d_fits.gz3d_fits(file_path)
        self.mangaid = self.data.metadata['MANGAID'][0]
        
        self.maps = Maps(self.mangaid)
        self.hamap = self.maps.emline_gflux_ha_6564
        self.hbmap = self.maps.emline_gflux_hb_4862
        self.eff_rad = self.maps.nsa['elpetro_th50_r'] * 2
        self.redshift = self.maps.nsa['z']
        self.mass = self.maps.nsa['sersic_mass']
        self.lgmass = np.log10(self.mass)
        self.elpetro_ba = self.maps.nsa['elpetro_ba']
        self.theta = np.radians(self.maps.nsa['elpetro_phi'] - 90.0)
        self.map_shape = self.hamap.shape
        
        self.d_mpc = ((299792.458 * self.redshift) / 70) #Mpc
        self.d_kpc = self.d_mpc * 1E3
        self.d_m = self.d_mpc * 3.085677581E+22 # m
        self.delta = (4 * np.pi * (self.d_m**2)) / ((2.8**2.36) * (10**41.1))
        self.spax_area = (0.0000024240684055477 * self.d_kpc)**2
        
        self.global_df_loaded = False
        self.bpt_masks_loaded = False
        self.r_array_loaded = False
        self.corr_matrix_loaded = False
        self.sfr_cov_loaded = False
        self.ha_cov_loaded = False
        
        
    def __repr__(self):
        return 'MaNGA ID {}'.format(self.mangaid)
    
    
    def form_correllation_matrix(self):
        if not self.corr_matrix_loaded:
            self.corr_matrix = np.load('corr_matrices/corr_matrix' + str(self.map_shape[0]) + '.npy')
            self.corr_matrix_loaded = True
    
    
    def check_usability(self, threshold = 5, pix_percentage = 1.5):
        image_spiral_mask = self.data.spiral_mask
        pixels_above_threshold = (image_spiral_mask >= threshold).sum()
                
        if (pixels_above_threshold * 100 / image_spiral_mask.size < pix_percentage):
            return False
        
        return True
    
    
    def make_emmasks(self):
        self.ha_mask_array = self.hamap.mask.flatten()
        self.hb_mask_array = self.hbmap.mask.flatten()
        
        for i in range(len(self.ha_mask_array)):
            if self.ha_mask_array[i] & 1073741824 == 0:
                self.ha_mask_array[i] = 0
                self.hb_mask_array[i] = 0
            else:
                self.ha_mask_array[i] = 1
                if self.hb_mask_array[i] & 1073741824 == 0:
                    self.hb_mask_array[i] = 0
                else:
                    self.hb_mask_array[i] = 1
                    
        self.ha_mask_array = np.array(self.ha_mask_array, dtype=bool)
        self.hb_mask_array = np.array(self.hb_mask_array, dtype=bool)
        
        
    def make_r_array(self):
        if not self.r_array_loaded:
            r_array = np.array([])

            a, b = self.map_shape
            k, h = (a - 1) / 2.0, (b - 1) / 2.0 #map centre

            for y, x in [(y, x) for y in range(a) for x in range(b)]:
                j, i = (-1 * (y - k), x - h) #vector from centre

                spax_angle = (np.arctan(j / i)) - self.theta
                vec_len = (j**2.0 + i**2.0)**0.5
                r = vec_len * ((np.cos(spax_angle))**2.0 + ((np.sin(spax_angle))/self.elpetro_ba)**2.0)**0.5

                r_array = np.append(r_array, r)

            self.r_array = r_array
            
            self.r_array_loaded = True
    
        
    def update_spirals(self, spiral_threshold=3, bar_threshold=3):
        self.data.make_all_spaxel_masks(grid_size = self.map_shape)
        
        arms_spaxel_mask = self.data.spiral_mask_spaxel > spiral_threshold
        bar_spaxel_mask = self.data.bar_mask_spaxel > bar_threshold
        
        common_spaxels = np.bitwise_and(arms_spaxel_mask, bar_spaxel_mask)
        spiral_mask = np.bitwise_and(arms_spaxel_mask, ~common_spaxels)
        
        self.df['Spiral Arm'] = spiral_mask.flatten()
        
    
    def load_btp_masks(self):
        if not self.bpt_masks_loaded:
            bpt_masks = self.maps.get_bpt(return_figure=False, show_plot=False)

            self.comp = bpt_masks['comp']['global']
            self.agn = bpt_masks['agn']['global']
            self.seyfert = bpt_masks['seyfert']['global']
            self.liner = bpt_masks['liner']['global']
            
            self.bpt_masks_loaded = True
    
    
    def flux2sfr(self, x):
        ha_flux = x['$H_{\\alpha}$'] * 1E-13
        hb_flux = x['$H_{\\beta}$'] * 1E-13
        
        ha_stdv = x['$\sigma H_{\\alpha}$'] * 1E-13
        hb_stdv = x['$\sigma H_{\\beta}$'] * 1E-13
        
        x['SFR'] = (self.delta * (ha_flux**3.36) * (hb_flux**-2.36)) / self.spax_area
        x['$\sigma$SFR'] = np.sqrt((3.36 * self.delta * (ha_flux**2.36) * (hb_flux**-2.36) * ha_stdv)**2 +
                                   (-2.36 * self.delta * (ha_flux**3.36) * (hb_flux**-3.36) * hb_stdv)**2) / self.spax_area
        
        return x
    
    
    def form_global_df(self):
        if not self.global_df_loaded:
            self.load_btp_masks()
            self.make_r_array()
            self.make_emmasks()
            
            ha_array = self.hamap.value.flatten()
            sig_ha_array = self.hamap.error.value.flatten()
            
            hb_array = self.hbmap.value.flatten()
            sig_hb_array = self.hbmap.error.value.flatten()
                        
            comp_array = self.comp.flatten()
            agn_array = self.agn.flatten()
            seyfert_array = self.seyfert.flatten()
            liner_array = self.liner.flatten()
            
            data_array = np.array([self.r_array, ha_array, sig_ha_array, hb_array, sig_hb_array,
                                   comp_array, agn_array, seyfert_array, liner_array]).transpose()
            
            df = pd.DataFrame(data=data_array, columns=['Radius', '$H_{\\alpha}$', '$\sigma H_{\\alpha}$', 
                                                        '$H_{\\beta}$', '$\sigma H_{\\beta}$',
                                                        'Comp', 'AGN', 'Seyfert', 'Liner'])
            
            df = df.replace([np.inf, -np.inf], np.nan)
            
            df.iloc[self.ha_mask_array, df.columns.get_loc('$H_{\\alpha}$')] = np.nan
            df.iloc[self.ha_mask_array, df.columns.get_loc('$\sigma H_{\\alpha}$')] = np.nan
            df.iloc[self.ha_mask_array, df.columns.get_loc('$H_{\\beta}$')] = np.nan
            df.iloc[self.ha_mask_array, df.columns.get_loc('$\sigma H_{\\beta}$')] = np.nan
            
            df.iloc[self.hb_mask_array, df.columns.get_loc('$H_{\\beta}$')] = np.nan
            df.iloc[self.hb_mask_array, df.columns.get_loc('$\sigma H_{\\beta}$')] = np.nan
            
            df = df.apply(self.flux2sfr, axis=1)
            
            df = df.replace([np.inf, -np.inf], np.nan)
            
            df['$r/r_e$'] = df['Radius'] / self.eff_rad
            
            self.df = df
            
            self.update_spirals()
            
            self.global_df_loaded = True
            
            
    def cov_matrix_maker(self, err_series):        
        r = self.hamap.size
        cov_mat = np.zeros((r, r))
        
        for item, frame in err_series.iteritems():
            if pd.isnull(frame):
                k = 0
            else:
                k = frame
                
            cov_mat[item] = self.corr_matrix[item] * k
            cov_mat[:, item] = self.corr_matrix[:, item] * k
        
        return cov_mat
    
    
    def make_cov_matrices(self, mode=None):
        self.form_correllation_matrix()
        
        if mode == None:
            raise ValueError('Argument "mode" must be set to "sfr" or "ha".')
            
        elif mode == 'sfr':
            if not self.sfr_cov_loaded:
                self.sfr_cov = self.cov_matrix_maker(self.df['$\sigma$SFR'])
                self.sfr_cov_loaded = True
        
        elif mode == 'ha':
            if not self.ha_cov_loaded:
                self.ha_cov = self.cov_matrix_maker(self.df['$\sigma H_{\\alpha}$'])
                self.ha_cov_loaded = True
                
                
    def get_var(self, index, mode=None, avg=False):
        index = set(index)
        tot_index = list(self.df.index)
        
        w_vec = np.array([[x in index for x in tot_index]]) * 1
                
        if mode == None:
            raise ValueError('Argument "mode" must be "sfr" or "ha".')
        elif mode == 'ha':
            self.make_cov_matrices(mode=mode)
            cov_mat = self.ha_cov
        elif mode == 'sfr':
            self.make_cov_matrices(mode=mode)
            cov_mat = self.sfr_cov
            
        var = np.linalg.multi_dot([w_vec, cov_mat, w_vec.T])[0][0]
        
        if avg:
            n = len(index)
            return var / (n**2)
        
        return var
    
    
    def integrate_sfr(self, mode='all', upper_limit=1, lower_limit=0.1, avg=False, sfr_lim=5, sig_sfr_lim=5, sfr_sig_cutoff=True):
        df = self.df
        
        df = df[(df['$r/r_e$'] <= upper_limit) & (df['$r/r_e$'] >= lower_limit)]
        df = df[(df.Comp == 0) & (df.AGN == 0) & (df.Seyfert == 0) & (df.Liner == 0)]
        
        if sfr_sig_cutoff:
            df = df.dropna()
            df = df[df['SFR'] >= df['$\sigma$SFR']]
        else:
            df.loc[df['SFR'] > sfr_lim, 'SFR'] = np.nan
            df.loc[df['$\sigma$SFR'] > sig_sfr_lim, '$\sigma$SFR'] = np.nan
            df = df.dropna()
                
        if mode == 'spirals':
            df = df[df['Spiral Arm'] == 1]
            
        if mode == 'non-spirals':
            df = df[df['Spiral Arm'] == 0]
        
        summ = df.SFR.sum()
        
        if avg:
            return summ / len(df.index), self.get_var(df.index, mode='sfr', avg=avg)
        
        return summ, self.get_var(df.index, mode='sfr')
示例#8
0
class SpiralGalaxy(object):
    """Represents a spiral galaxy ready for SFR analysis.
    
    This class represents a spiral galaxy and utilises the
    GZ3D code's gz3d_fits class and Marvin's Map class
    to obtain spiral masks as well as emission maps and
    galaxy information respectively. It produces a pandas
    dataframe. The dataframe specifies radial and
    spiral information which can be used for analysis.
    
    Attributes:
        data (gz3d_fits): Is the galaxy class from the GZ3D module.
        spax_df (pandas dataframe): Lists individual spaxels with SFR and spiral details.
    """
    def __init__(self, file_path):
        """initialises galaxy with certain methods variables and methods.
        
        Taking in file path for GZ3D .FITS, the class is initialised by
        initialising the `gz3d_fits` class, initialising variables like
        the MaNGA ID, Marvin Maps class attributes like the emission maps,
        and function 'switches' to make sure resource intensive methods are
        only run once.
        
        Args:
            file_path (str): File path to the GZ3D .FITS file.
        """

        self.file_path = file_path
        self.data = gz3d_fits.gz3d_fits(file_path)
        self.mangaid = self.data.metadata['MANGAID'][0]

        self.maps = Maps(self.mangaid)
        self.hamap = self.maps.emline_gflux_ha_6564
        self.hbmap = self.maps.emline_gflux_hb_4862
        self.eff_rad = self.maps.nsa['elpetro_th50_r'] * 2
        self.redshift = self.maps.nsa['z']
        self.mass = self.maps.nsa['sersic_mass']
        self.elpetro_ba = self.maps.nsa['elpetro_ba']
        self.theta = np.radians(self.maps.nsa['elpetro_phi'] - 90.0)

        self.flux_df_loaded = False
        self.sfr_cols_loaded = False
        self.ellipical_mask_loaded = False
        self.ellipical_cutout_on = True

    def __repr__(self):
        """Represent galaxy with MaNGA ID"""

        return 'MaNGA ID {}'.format(self.mangaid)

    def check_usability(self, threshold=5, pix_percentage=1.5):
        """Checks to see if the galaxy is suitable for analysis.
        
        The method checks the raw spiral mask from the gz3d_fits class
        to confirm if the number of times the visual image pixel was marked
        (threshold) is greater than some number, and if the total area
        covered by these pixels is greater than the percentage specified.

        Args:
            threshold (int): Pixels should be marked >= this number of times.
            pix_percentage (float): Marked pixes above the threshold must be greater than this percentage of total.
            
        Returns:
            True if useful, False otherwise.
        """

        image_spiral_mask = self.data.spiral_mask
        pixels_above_threshold = (image_spiral_mask >= threshold).sum()

        if (pixels_above_threshold * 100 / image_spiral_mask.size <
                pix_percentage):
            return False

        return True

    def form_flux_df(self):
        """Forms the main dataframe with all spaxels and their flux.
        
        This method forms the main dataframe for the galaxy by gathering the H-alpha
        emission map cutouts with spiral and non-spiral masks applied as well as the
        H-beta map cutout. The cutouts are then fed into the make_spaxel_dicts
        method which then produces a list of dictionaries ready to be applied to the
        pandas dataframe.
        """

        if not self.flux_df_loaded:
            spiral_hamap_cutout, non_spiral_hamap_cutout = self.make_hamap_cutouts(
            )
            hbmap_cutout = self.apply_ellipse_cutout(self.hbmap)

            combined_dicts = self.make_spaxel_dicts(
                spiral_hamap_cutout,
                hbmap_cutout, 'Spiral') + self.make_spaxel_dicts(
                    non_spiral_hamap_cutout, hbmap_cutout, 'Non Spiral')

            self.spax_df = pd.DataFrame(combined_dicts,
                                        columns=[
                                            'radius', 'ha_flux', 'sig_ha_flux',
                                            'hb_flux', 'sig_hb_flux',
                                            'spaxel_type'
                                        ])
            self.spax_df['r_re'] = self.spax_df['radius'] / self.eff_rad

            self.flux_df_loaded = True

    def form_sfr_cols(self):
        """Forms the SFR and sigma(SFR) columns in spax_df.
        
        This method traverses the main pandas dataframe and forms two new columns
        for the SFR and the error in the SFR respectively.
        """

        if not self.sfr_cols_loaded:
            self.form_flux_df()

            self.spax_df['sfr'] = self.spax_df.apply(
                lambda row: self.flux2sfr(row), axis=1)
            self.spax_df['sig_sfr'] = self.spax_df.apply(
                lambda row: self.flux2sfr(row, stdv=True), axis=1)
            self.sfr_cols_loaded = True

    def make_hamap_spiral_masks(self):
        """Forms the global spiral and non-spiral masks for the H-alpha emission map.
        
        This method creates global (class-wide) H-alpha emission map masks. One of these
        is for spiral and the other for the non-spiral requirement. The reason for these
        being class-wide is that this mask can be used for Flux/SFR calculation as well as
        emission map plots. The maps are formed by taking the H-alpha map mask and adding
        the non/spiral requirement through adding the 'Do not use' 2**30 bitmask.
        
        Note:
            The BPT requirement has not been applied to these masks.
        """

        self.get_arms_spaxel_mask()

        mask_non_spirals = (~self.arms_spaxel_mask
                            ) * self.hamap.pixmask.labels_to_value('DONOTUSE')
        mask_spirals = (self.arms_spaxel_mask
                        ) * self.hamap.pixmask.labels_to_value('DONOTUSE')

        self.hamap_spiral_mask = deepcopy(self.hamap.mask) | mask_non_spirals
        self.hamap_non_spiral_mask = deepcopy(self.hamap.mask) | mask_spirals

    def apply_bpt(self, mask):
        """Applies the BPT requirement to any input mask.
        
        This method uses the get_bpt method of the Marvin Maps class to gather a boolean mask
        of spaxels which have been marked as Composite, AGN, Seyfert, or LINER. Since these
        are spaxels we don't wish to use, we make a mask with the 2**30 'Do not use' Bitmask
        with the boolean mask and then apply it to any input mask.

        Args:
            mask (int array): Any emission map mask array to which we may add the BPT requirement.

        Returns:
            mask (int array): Emission map mask array with the BPT requirement added.
        """

        bpt_masks = self.maps.get_bpt(return_figure=False, show_plot=False)

        comp = bpt_masks['comp']['global']
        agn = bpt_masks['agn']['global']
        seyfert = bpt_masks['seyfert']['global']
        liner = bpt_masks['liner']['global']

        overall_bpt_mask = np.logical_or(
            np.logical_or(np.logical_or(comp, agn), seyfert),
            liner) * 1073741824

        return mask | overall_bpt_mask

    def apply_ellipse_cutout(self, array):
        """Applies an elliptical cutout to any given array according to galaxy specifications.
        
        This method uses the `astropy` Elliptical Aperture module to make an elliptical cutout
        of the galaxy by taking into account the Petrosian effective radius, inclination, and
        b/a ratio for a galaxy. A cutout from the centre of  the array is then returned with
        this requirement applied. The primary reason for working with cutouts is speed of
        analysis.
        
        Args:
            array (array): Any array, which is an emission map with a modified mask in our use.

        Returns:
            array (array): An elliptical cutout of the array 
        """

        if self.ellipical_mask_loaded == False:
            x, y = self.hamap.shape

            ellipical_aperture = EllipticalAperture(
                [(x - 1) / 2, (y - 1) / 2], np.ceil(self.eff_rad),
                np.ceil(self.eff_rad * self.elpetro_ba), self.theta)
            self.mask_ellipical = ellipical_aperture.to_mask(method='exact')[0]

            self.ellipical_mask_loaded = True

        return self.mask_ellipical.cutout(array)

    def make_hamap_cutouts(self):
        """An overarching method which forms the non/spiral H-alpha map cutouts.
        
        This method forms the spiral and non-spiral H-alpha emission map cutouts
        by applying the BPT requirement to the global H-alpha emission map masks.
        The modified maps are then cut out and returned.

        Returns:
            emission map cutouts (emap touple): Cutouts for the H-alpha spiral and non-spiral maps
        """

        self.make_hamap_spiral_masks()

        # We apply the BPT requirement to the global spiral and non-spiral masks, which can be removed for non-spiral analyses
        modified_ha_spiral_mask = self.apply_bpt(
            deepcopy(self.hamap_spiral_mask))
        modified_ha_non_spiral_mask = self.apply_bpt(
            deepcopy(self.hamap_non_spiral_mask))

        modified_spiral_hamap = deepcopy(self.hamap)
        modified_spiral_hamap.mask = modified_ha_spiral_mask

        modified_non_spiral_hamap = deepcopy(self.hamap)
        modified_non_spiral_hamap.mask = modified_ha_non_spiral_mask

        if self.ellipical_cutout_on:
            spiral_hamap_cutout = self.apply_ellipse_cutout(
                modified_spiral_hamap)
            non_spiral_hamap_cutout = self.apply_ellipse_cutout(
                modified_non_spiral_hamap)

            return spiral_hamap_cutout, non_spiral_hamap_cutout

        return modified_spiral_hamap, modified_non_spiral_hamap

    def make_spaxel_dicts(self, hamap_cut, hbmap_cut, spaxel_type):
        """Takes in H-alpha and H-beta emission maps and returns fluxes for all spaxels in a dict list.
        
        This method taked in H-alpha and H-beta maps and goes through the spaxels to create a dictionary
        with flux information on it for each usable spaxel. In our use the emission maps are cutouts of
        galaxies made for efficiency. We also input modified H-alpha maps with the non/spiral and BPT
        requirement also present to mark spaxels as either spiral or non-spiral.
        
        Args:
            hamap_cut (DAP map): H-alpha map for spaxel flux collection.
            hbmap_cut (DAP map): H-beta map for spaxel flux collection.
            spaxel_type (str): The designation of the spaxel as either spiral or non-spiral

        Returns:
            dict_list (list of dictionaries): Contains all spaxel information in dictionaries.
        """

        dict_list = []

        a, b = hamap_cut.shape  #shape of the cutout
        k, h = (a - 1) / 2.0, (b - 1) / 2.0  #mapcentre

        for y, x in [(y, x) for y in range(a) for x in range(b)]:
            if hamap_cut.mask[y, x] & 1073741824 != 0:
                continue

            ha_flux = hamap_cut[y, x].value
            ha_stdv = hamap_cut[y, x].error.value

            hb_flux = hbmap_cut[y, x].value
            hb_stdv = hbmap_cut[y, x].error.value

            j, i = (-1 * (y - k), x - h)  #vector from centre

            spax_angle = (np.arctan(j / i)) - self.theta
            spax_r = (j**2.0 + i**2.0)**0.5
            spax_r_int = np.ceil(
                spax_r * ((np.cos(spax_angle))**2.0 +
                          ((np.sin(spax_angle)) / self.elpetro_ba)**2.0)**0.5)

            if (hbmap_cut.mask[y, x] & 1073741824 != 0 or hb_flux <= 0):
                dict_list.append({
                    'radius': spax_r_int,
                    'ha_flux': ha_flux,
                    'sig_ha_flux': ha_stdv,
                    'hb_flux': np.nan,
                    'sig_hb_flux': np.nan,
                    'spaxel_type': spaxel_type
                })
            else:
                dict_list.append({
                    'radius': spax_r_int,
                    'ha_flux': ha_flux,
                    'sig_ha_flux': ha_stdv,
                    'hb_flux': hb_flux,
                    'sig_hb_flux': hb_stdv,
                    'spaxel_type': spaxel_type
                })

        return dict_list

    def get_lgmass(self):
        """Returns log base 10 of galactic mass."""

        return np.log10(self.mass)

    def get_arms_spaxel_mask(self):
        """Obtains the base spaxel mask for spiral arms, and makes a boolean version of it available."""

        self.data.make_all_spaxel_masks(grid_size=self.hamap.shape)
        self.arms_spaxel_mask = self.data.spiral_mask_spaxel > 3

    def get_integrated_sfr(self, mode='all', filter_radii=0):
        self.form_sfr_cols()

        df = self.spax_df[self.spax_df.notnull()]
        df = df[(df['r_re'] <= 1) & (df['r_re'] >= filter_radii)]

        if mode == 'all':
            return df.sfr.sum(), ((df['sig_sfr']**2).mean())**0.5

        if mode == 'spirals':
            return df[df.spaxel_type == 'Spiral'].sfr.sum(), ((
                df[df.spaxel_type == 'Spiral']['sig_sfr']**2).mean())**0.5

        if mode == 'non-spirals':
            return df[df.spaxel_type == 'Non Spiral'].sfr.sum(), ((
                df[df.spaxel_type == 'Non Spiral']['sig_sfr']**2).mean())**0.5

        return "mode must be set as 'all', 'spirals', or 'non-spirals'"

    def flux2sfr(self, row, stdv=False):
        """Takes in pandas row from the main dataframe and returns SFR or sigma(SFR).
        
        This method is designed to be used with the pandas df.apply function. It takes in a row from
        the main dataframe and uses it to calculate the SFR and it's uncertainty. It used the H-alpha
        and H-beta flux to calculate luminosities, which are used to obtain a corrected H-alpha
        luminosity. After this either the SFR or SFR standard deviation from it is retuened depending
        on the value of stdv.
        
        Args:
            row (pandas row): Contains needed flux information.
            stdv (boolean): Optional boolean value can be used to obtain either the magnitude or flux in SFR.
            
        Returns:
            sigma/SFR (float): The value of the sigma/SFR.
        """

        ha_flux = row['ha_flux']
        hb_flux = row['hb_flux']

        dis = (299792 * self.redshift / 70) * (3.08567758128E+22)  # in metres
        dis_kpc = (299792 * self.redshift / 70) * 1E3

        lum_ha = ha_flux * (4 * np.pi * (dis**2)) * 1E-13
        lum_hb = hb_flux * (4 * np.pi * (dis**2)) * 1E-13

        corrected_ha_lum = lum_ha * ((lum_ha / lum_hb) / 2.8)**2.36

        if stdv:
            ha_stdv = row['sig_ha_flux']
            hb_stdv = row['sig_hb_flux']

            lum_ha_stdv = ha_stdv * (4 * np.pi * (dis**2)) * 1E-13
            lum_hb_stdv = hb_stdv * (4 * np.pi * (dis**2)) * 1E-13

            corrected_ha_lum_stdv = (((3.36 * lum_ha ** 2.36 * lum_ha_stdv)/((2.8 * lum_hb) ** 2.36)) ** 2 \
                            + ((-2.36 * lum_ha ** 3.36 * lum_hb_stdv)/(2.8 ** 2.36 * lum_hb ** 3.36)) ** 2) ** 0.5

            return (corrected_ha_lum_stdv / 10**41.1) / (
                (dis_kpc * 0.0000024240684055)**2)

        return (corrected_ha_lum / 10**41.1) / (
            (dis_kpc * 0.0000024240684055)**2)