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)
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)
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)
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)
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')
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)