def load_summary_table(): """Read Summary Table. Returns ------- df : QTable """ _run_get_globular_clusters() df = QTable(QTable.read(DATA_PATH + "summary.ecsv", format="ascii.ecsv")) df.add_index("Name") df.columns # convert to distance units, from angular units # this uses the distance, which is assumed to be errorless # TODO # storing information df["pm"] = np.hypot(df["pmra"], df["pmdec"]) # making skycoord for ease of use df_sc = SkyCoord( ra=df["ra"], dec=df["dec"], distance=df["dist"], pm_ra_cosdec=df["pmra"], pm_dec=df["pmdec"], radial_velocity=df["vlos"], ) df_sc.representation_type = "cartesian" # store SkyCoord df["sc"] = df_sc return df
def test_hstack_qtable_table(): # Check in particular that indices are initialized or copied correctly # for a Column that is being converted to a Quantity. qtab = QTable([np.arange(5.)*u.m], names=['s']) qtab.add_index('s') tab = Table([Column(np.arange(5.), unit=u.s)], names=['t']) qstack = hstack([qtab, tab]) assert qstack['t'].info.indices == [] assert qstack.indices == []
def test_info_no_copy_numpy(): """Test that getting a single item from Table column object does not copy info. See #10889. """ col = [1, 2] t = QTable([col], names=['col']) t.add_index('col') val = t['col'][0] # Returns a numpy scalar (e.g. np.float64) with no .info assert isinstance(val, np.number) with pytest.raises(AttributeError): val.info val = t['col'][:] assert val.info.indices == []
def test_table_index_does_not_propagate_to_column_slices(col): # They lost contact to the parent table, so they should also not have # information on the indices; this helps prevent large memory usage if, # e.g., a large time column is turned into an object array; see gh-10688. tab = QTable() tab['t'] = col tab.add_index('t') t = tab['t'] assert t.info.indices tx = t[1:] assert not tx.info.indices tabx = tab[1:] t = tabx['t'] assert t.info.indices
def test_info_no_copy_mixin_with_index(col): """Test that getting a single item from Table column object does not copy info. See #10889. """ t = QTable([col], names=['col']) t.add_index('col') val = t['col'][0] assert 'info' not in val.__dict__ assert val.info.indices == [] val = t['col'][:] assert 'info' in val.__dict__ assert val.info.indices == [] val = t[:]['col'] assert 'info' in val.__dict__ assert isinstance(val.info.indices[0], SlicedIndex)
# density - g cm^-3 _density = [ 147.74, 146.66, 142.73, 116.10, 93.35, 72.73, 48.19, 34.28, 21.958, 15.157, 10.157, 5.566, 2.259, 0.4483, 0.1528, 0.042, 0.00361, 1.99e-7 ] * u.g * u.cm**-3 _d = { 'radius': _radius, 'mass': _mass, 'luminosity': _luminosity, 'temperature': _temperature, 'density': _density } interior = QTable(_d) interior.source = 'Turck-Chieze et al. (1988)' interior.add_index('radius') # time - 10^9 years _time = [ 0, 0.143, 0.856, 1.863, 2.193, 3.020, 3.977, 4.587, 5.506, 6.074, 6.577, 7.027, 7.728, 8.258, 8.7566, 9.805 ] * u.Gyr # luminosity - L_sun _tluminosity = [ 0.7688, 0.7248, 0.7621, 0.8156, 0.8352, 0.8855, 0.9522, 1.0, 1.079, 1.133, 1.186, 1.238, 1.318, 1.399, 1.494, 1.760 ] * u.Lsun # radius - R_sun _tradius = [
def filament_profile(skeleton, image, pixscale, max_dist=0.025 * u.pc, distance=250. * u.pc, num_avg=3, verbose=False, bright_unit="Jy km/s", noise=None, fit_profiles=True): ''' Calculate radial profiles along the main extent of a skeleton (ie. the longest path). The skeleton must contain a single branch with no intersections. Parameters ---------- skeleton : np.ndarray Boolean array containing the skeleton image : np.ndarray Image to compute the profiles from. Must match the spatial extent of the skeleton array. pixscale : `~astropy.units.Quantity` Angular size of a pixel in the image. Must have units equivalent to degrees. max_dist : astropy Quantity, optional The angular or physical (when distance is given) extent to create the profile away from the centre skeleton pixel. The entire profile will be twice this value (for each side of the profile). distance : astropy Quantity, optional Physical distance to the region in the image. If None is given, results will be in angular units based on the header. num_avg : int, optional Number of points before and after a pixel that is used when computing the normal vector. Using at least three points is recommended due to small pixel instabilities in the skeletons. verbose : bool, optional Enable plotting of the profile and the accompanying for each pixel in the skeleton. bright_unit : string or astropy Unit Brightness unit of the image. noise : np.ndarray, optional RMS array for the accompanying image. When provided, the errors are calculated along each of the profiles and used as weights in the fitting. fit_profiles : bool, optional When enabled, fits a Gaussian model to the profiles. Otherwise only the profiles are returned. Returns ------- line_distances : list Distances along the profiles. line_profiles : list Radial profiles. profile_extents : list Contains the pixel position of the start of the profile, the skeleton pixel, and the end of the profile. tab : astropy QTable Table of the fit results and errors with appropriate units. ''' deg_per_pix = pixscale.to(u.deg) / u.pixel if distance is not None: phys_per_pix = distance * (np.pi / 180.) * deg_per_pix / u.deg max_pixel = (max_dist / phys_per_pix).value else: # max_dist should then be in pixel or angular units if not isinstance(max_dist, u.Quantity): # Assume pixels max_pixel = max_dist else: try: max_pixel = max_dist.to(u.pix).value except u.UnitConversionError: # In angular units equiv = [(u.pixel, u.deg, lambda x: x / (pixscale * u.pix), lambda x: x * pixscale * u.pix)] max_pixel = max_dist.to(u.pix, equivalencies=equiv).value if bright_unit is None: bright_unit = u.dimensionless_unscaled elif isinstance(bright_unit, str): bright_unit = u.Unit(bright_unit) elif isinstance(bright_unit, u.UnitBase): pass else: raise TypeError("bright_unit must be compatible with astropy.units.") # Make sure the noise array is the same shape if noise is not None: assert noise.shape == image.shape # Get the points in the skeleton (in order) skel_pts = walk_through_skeleton(skeleton) line_profiles = [] line_distances = [] profile_extents = [] profile_fits = [] red_chisqs = [] for j, i in enumerate(range(num_avg, len(skel_pts) - num_avg)): # Calculate the normal direction from the surrounding pixels pt1 = avg_pts([skel_pts[i + j] for j in range(-num_avg, 0)]) pt2 = avg_pts([skel_pts[i + j] for j in range(1, num_avg + 1)]) vec = np.array([float(x2 - x1) for x2, x1 in zip(pt1, pt2)]) vec /= np.linalg.norm(vec) per_vec = perpendicular(vec) line_pts = find_path_ends(skel_pts[i], max_pixel, per_vec) left_profile, left_dists = \ profile_line(image, skel_pts[i], line_pts[0]) right_profile, right_dists = \ profile_line(image, skel_pts[i], line_pts[1]) total_profile = np.append(left_profile[::-1], right_profile) * \ bright_unit if noise is not None: left_profile, _ = \ profile_line(noise, skel_pts[i], line_pts[0]) right_profile, _ = \ profile_line(noise, skel_pts[i], line_pts[1]) noise_profile = np.append(left_profile[::-1], right_profile) * \ bright_unit else: noise_profile = None if distance is not None: total_dists = np.append(-left_dists[::-1], right_dists) \ * u.pix * phys_per_pix else: total_dists = np.append(-left_dists[::-1], right_dists) \ * u.pix * deg_per_pix if noise is not None: if len(total_profile) != len(noise_profile): raise ValueError("Intensity and noise profile lengths do not" " match. Have you applied the same mask to" " both?") line_profiles.append(total_profile) line_distances.append(total_dists) profile_extents.append([line_pts[0], skel_pts[i], line_pts[1]]) if fit_profiles: # Now fit! profile_fit, profile_fit_err, red_chisq = \ gauss_fit(total_dists.value, total_profile.value, sigma=noise_profile) profile_fits.append(np.hstack([profile_fit, profile_fit_err])) red_chisqs.append(red_chisq) if verbose: p.subplot(121) p.imshow(image, origin='lower') p.contour(skeleton, colors='r') p.plot(skel_pts[i][1], skel_pts[i][0], 'bD') p.plot(line_pts[0][1], line_pts[0][0], 'bD') p.plot(line_pts[1][1], line_pts[1][0], 'bD') p.subplot(122) p.plot(total_dists, total_profile, 'bD') pts = np.linspace(total_dists.min().value, total_dists.max().value, 100) if fit_profiles: p.plot(pts, gaussian(pts, *profile_fit), 'r') if distance is not None: unit = (u.pix * phys_per_pix).unit.to_string() else: unit = (u.pix * deg_per_pix).unit.to_string() p.xlabel("Distance from skeleton (" + unit + ")") p.ylabel("Surface Brightness (" + bright_unit.to_string() + ")") p.tight_layout() p.show() if fit_profiles: profile_fits = np.asarray(profile_fits) red_chisqs = np.asarray(red_chisqs) # Create an astropy table of the fit results param_names = ["Amplitude", "Std Dev", "Background"] param_errs = [par + " Error" for par in param_names] colnames = param_names + param_errs in_bright_units = [True, False, True] * 2 tab = QTable() tab["Number"] = np.arange(profile_fits.shape[0]) tab.add_index("Number") tab["Red Chisq"] = red_chisqs for i, (name, is_bright) in enumerate(zip(colnames, in_bright_units)): if is_bright: col_unit = bright_unit else: if distance is not None: col_unit = (u.pix * phys_per_pix).unit else: col_unit = (u.pix * deg_per_pix).unit tab[name] = profile_fits[:, i] * col_unit return line_distances, line_profiles, profile_extents, tab else: return line_distances, line_profiles
_temperature = [15.513, 15.48, 15.36, 14.404, 13.37, 12.25, 10.53, 9.30, 8.035, 7.214, 6.461, 5.531, 4.426, 2.981, 2.035, 0.884, 0.1818, 0.005770] * u.MK # density - g cm^-3 _density = [147.74, 146.66, 142.73, 116.10, 93.35, 72.73, 48.19, 34.28, 21.958, 15.157, 10.157, 5.566, 2.259, 0.4483, 0.1528, 0.042, 0.00361, 1.99e-7] * u.g*u.cm**-3 _d = {'radius': _radius, 'mass': _mass, 'luminosity': _luminosity, 'temperature': _temperature, 'density': _density} interior = QTable(_d) interior.source = 'Turck-Chieze et al. (1988)' interior.add_index('radius') # time - 10^9 years _time = [0, 0.143, 0.856, 1.863, 2.193, 3.020, 3.977, 4.587, 5.506, 6.074, 6.577, 7.027, 7.728, 8.258, 8.7566, 9.805] * u.Gyr # luminosity - L_sun _tluminosity = [0.7688, 0.7248, 0.7621, 0.8156, 0.8352, 0.8855, 0.9522, 1.0, 1.079, 1.133, 1.186, 1.238, 1.318, 1.399, 1.494, 1.760] * u.Lsun # radius - R_sun _tradius = [0.872, 0.885, 0.902, 0.924, 0.932, 0.953, 0.981, 1.0, 1.035, 1.059, 1.082,
class Result: def __init__(self, model=None, output_dir=None, result_id=0): self.model = model self.output_dir = output_dir self.result_id = result_id self.table = QTable() self.name = None self.image = None self.cuts = None self.psf = None self.update = True def __getitem__(self, key): return self.table.__getitem__(key) def __setitem__(self, key, value): self.update = True return self.table.__setitem__(key, value) def __repr__(self): return self.table.__repr__() def __len__(self): return len(self.table) def __bool__(self): return bool(self.table) def show(self): return self.table.show_in_notebook() def loc(self, cat_number, ext_number=0): return self.table.loc['EXT_NUMBER', ext_number].loc['NUMBER', cat_number] def row(self, i): return self.table.loc['ROW', i] @property def colnames(self): return self.table.colnames def save(self): os.makedirs(self.output_dir, exist_ok=True) self.table.write(os.path.join(self.output_dir, f"{self.name}_dre.fits"), overwrite=True) def load_summary(self, summary): self.name = os.path.basename(summary).replace('_dre.fits', '') self.table = QTable.read(summary) if self.table: self.table['ROW'] = np.arange(len(self.table)) self.table['RESULT_ID'] = np.ones(len(self), dtype=int) * self.result_id self.table.add_index('ROW') self.table.add_index('EXT_NUMBER') self.table.add_index('NUMBER') def load_chi(self, chi_file): self.name = os.path.basename(chi_file).replace('_chi.h5', '') parameters = defaultdict(list) with File(chi_file, 'r') as chi_h5f: self.name = os.path.basename(chi_file).replace('_chi.h5', '') names = list(chi_h5f.keys()) for i, name in enumerate(names): parameters['ROW'].append(i) ext, numb = name.split('_') parameters['EXT_NUMBER'].append(int(ext)) parameters['NUMBER'].append(int(numb)) chi_cube = chi_h5f[name][:] params = self.model.get_parameters(chi_cube) for key, value in params.items(): parameters[key].append(value) self.table = QTable(parameters) self.table['RESULT_ID'] = self.result_id def visualize_detections(self): pass def hist(self, key=None, **kwargs): if key: plt.figure(figsize=(6, 6)) plt.hist(self.table[key], **kwargs) plt.xlabel(key, fontsize=14) plt.show() else: plt.figure(figsize=(8, 8)) for i, (key, label) in enumerate([('INDEX', r'$n$'), ('AX_RATIO', 'a/b'), ('ANGLE', r'$\theta$'), ('LOGR', r'$Log_{10}R$')]): plt.subplot(2, 2, i + 1) plt.hist(self.table[key], bins=self.model.shape[i], **kwargs) plt.xlabel(label, fontsize=14) plt.show() def plot(self, x_key, y_key, c=None, s=5, **kwargs): plt.scatter(self.table[x_key], self.table[y_key], c=c, s=s, **kwargs) plt.xlabel(x_key.lower(), fontsize=14) plt.ylabel(y_key.lower(), fontsize=14) plt.show() def join_catalog(self, cat_table, keys=None, table_names=('1', '2')): self.table = join(self.table, QTable(cat_table), join_type='inner', keys=keys, table_names=table_names) if 'EXT_NUMBER' not in self.table.colnames: self.table['EXT_NUMBER'] = self.table[ f'EXT_NUMBER_{table_names[0]}'] if 'NUMBER' not in self.table.colnames: self.table['NUMBER'] = self.table[f'NUMBER_{table_names[0]}'] self.table.sort(['EXT_NUMBER', 'NUMBER']) self.table['ROW'] = np.arange(len(self.table)) self.table.add_index('ROW') self.table.add_index('EXT_NUMBER') self.table.add_index('NUMBER') def get_data(self, i): row = self.row(i) cat_number, ext_number = row['NUMBER', 'EXT_NUMBER'] with File(os.path.join(self.cuts, f"{self.name}_cuts.h5"), 'r') as cuts_h5f: cuts = cuts_h5f[f'{ext_number:02d}_{cat_number:04d}'] data = cuts['obj'][:] segment = cuts['seg'][:] noise = cuts['rms'][:] return data, segment, noise def make_mosaic(self, i, save=False, mosaics_dir='Mosaics', cmap='gray', figsize=(15, 5), **kwargs): if self.cuts: row = self.row(i) cat_number, ext_number = row['NUMBER', 'EXT_NUMBER'] data, segment, _ = self.get_data(i) mosaic = self.model.make_mosaic(data, segment, tuple(row['MODEL_IDX']), psf_file=self.psf) if save: os.makedirs(mosaics_dir, exist_ok=True) mosaic_fits = fits.ImageHDU(data=mosaic) mosaic_fits.writeto(os.path.join( mosaics_dir, f"{self.name}_{ext_number:02d}_{cat_number:04d}_mosaic.fits" ), overwrite=True) else: plt.figure(figsize=figsize) plt.imshow(mosaic, cmap, **kwargs) plt.axis('off') plt.show() else: print("You should define the cuts image first") def make_residuals(self, i, src_index_idx=-1, ax_ratio_idx=-1, save=False, residuals_dir='Residuals', cmap='plasma', figsize=(20, 15), **kwargs): if self.cuts: row = self.row(i) cat_number, ext_number = row['NUMBER', 'EXT_NUMBER'] data, segment, _ = self.get_data(i) if self.psf: self.model.convolve(get_psf(self.psf), to_cpu=True) residual = self.model.make_residual(data, segment) if save: os.makedirs(residuals_dir, exist_ok=True) mosaic_fits = fits.ImageHDU(data=residual) mosaic_fits.writeto(os.path.join( residuals_dir, f"{self.name}_{ext_number:02d}_{cat_number:04d}_residual.fits" ), overwrite=True) else: residual_slice = residual[src_index_idx, ax_ratio_idx] plt.figure(figsize=figsize) title = f'a/b = {self.model.ax_ratio[ax_ratio_idx]:.1f}, n = {self.model.src_index[src_index_idx]:.1f}' plt.suptitle(title, fontsize=20, y=0.85) plt.imshow(residual_slice, cmap=cmap, **kwargs) plt.axis('off') plt.show() else: print("You should define the cuts image first")
def filament_profile(skeleton, image, header, max_dist=0.025 * u.pc, distance=250. * u.pc, num_avg=3, verbose=False, bright_unit="Jy km/s", noise=None): ''' Calculate radial profiles along the main extent of a skeleton (ie. the longest path). The skeleton must contain a single branch with no intersections. Parameters ---------- skeleton : np.ndarray Boolean array containing the skeleton image : np.ndarray Image to compute the profiles from. Must match the spatial extent of the skeleton array. header : FITS header Accompanying header for the image. max_dist : astropy Quantity, optional The angular or physical (when distance is given) extent to create the profile away from the centre skeleton pixel. The entire profile will be twice this value (for each side of the profile). distance : astropy Quantity, optional Physical distance to the region in the image. If None is given, results will be in angular units based on the header. num_avg : int, optional Number of points before and after a pixel that is used when computing the normal vector. Using at least three points is recommended due to small pixel instabilities in the skeletons. verbose : bool, optional Enable plotting of the profile and the accompanying for each pixel in the skeleton. bright_unit : string or astropy Unit Brightness unit of the image. noise : np.ndarray, optional RMS array for the accompanying image. When provided, the errors are calculated along each of the profiles and used as weights in the fitting. Returns ------- line_distances : list Distances along the profiles. line_profiles : list Radial profiles. profile_extents : list Contains the pixel position of the start of the profile, the skeleton pixel, and the end of the profile. tab : astropy QTable Table of the fit results and errors with appropriate units. ''' deg_per_pix = np.abs(header["CDELT2"]) * u.deg / u.pixel if distance is not None: phys_per_pix = distance * (np.pi / 180.) * deg_per_pix / u.deg max_pixel = (max_dist / phys_per_pix).value else: # max_dist should then be in pixel or angular units if not isinstance(max_dist, u.Quantity): # Assume pixels max_pixel = max_dist else: try: max_pixel = max_dist.to(u.pix).value except u.UnitConversionError: # In angular units equiv = [(u.pixel, u.deg, lambda x: x / header["CDELT2"], lambda x: x * header["CDELT2"])] max_pixel = max_dist.to(u.pix, equivalencies=equiv).value if bright_unit is None: bright_unit = u.dimensionless_unscaled elif isinstance(bright_unit, str): bright_unit = u.Unit(bright_unit) elif isinstance(bright_unit, u.UnitBase): pass else: raise TypeError("bright_unit must be compatible with astropy.units.") # Make sure the noise array is the same shape if noise is not None: assert noise.shape == image.shape # Get the points in the skeleton (in order) skel_pts = walk_through_skeleton(skeleton) line_profiles = [] line_distances = [] profile_extents = [] profile_fits = [] red_chisqs = [] for j, i in enumerate(xrange(num_avg, len(skel_pts) - num_avg)): # Calculate the normal direction from the surrounding pixels pt1 = avg_pts([skel_pts[i + j] for j in range(-num_avg, 0)]) pt2 = avg_pts([skel_pts[i + j] for j in range(1, num_avg + 1)]) vec = np.array([float(x2 - x1) for x2, x1 in zip(pt1, pt2)]) vec /= np.linalg.norm(vec) per_vec = perpendicular(vec) line_pts = find_path_ends(skel_pts[i], max_pixel, per_vec) left_profile, left_dists = \ profile_line(image, skel_pts[i], line_pts[0]) right_profile, right_dists = \ profile_line(image, skel_pts[i], line_pts[1]) total_profile = np.append(left_profile[::-1], right_profile) * \ bright_unit if noise is not None: left_profile, _ = \ profile_line(noise, skel_pts[i], line_pts[0]) right_profile, _ = \ profile_line(noise, skel_pts[i], line_pts[1]) noise_profile = np.append(left_profile[::-1], right_profile) * \ bright_unit else: noise_profile = None if distance is not None: total_dists = np.append(-left_dists[::-1], right_dists) \ * u.pix * phys_per_pix else: total_dists = np.append(-left_dists[::-1], right_dists) \ * u.pix * deg_per_pix if noise is not None: if len(total_profile) != len(noise_profile): raise ValueError("Intensity and noise profile lengths do not" " match. Have you applied the same mask to" " both?") line_profiles.append(total_profile) line_distances.append(total_dists) profile_extents.append([line_pts[0], skel_pts[i], line_pts[1]]) # Now fit! profile_fit, profile_fit_err, red_chisq = \ gauss_fit(total_dists.value, total_profile.value, sigma=noise_profile) profile_fits.append(np.hstack([profile_fit, profile_fit_err])) red_chisqs.append(red_chisq) if verbose: p.subplot(121) p.imshow(image, origin='lower') p.contour(skeleton, colors='r') p.plot(skel_pts[i][1], skel_pts[i][0], 'bD') p.plot(line_pts[0][1], line_pts[0][0], 'bD') p.plot(line_pts[1][1], line_pts[1][0], 'bD') p.subplot(122) p.plot(total_dists, total_profile, 'bD') pts = np.linspace(total_dists.min().value, total_dists.max().value, 100) p.plot(pts, gaussian(pts, *profile_fit), 'r') if distance is not None: unit = (u.pix * phys_per_pix).unit.to_string() else: unit = (u.pix * deg_per_pix).unit.to_string() p.xlabel("Distance from skeleton (" + unit + ")") p.ylabel("Surface Brightness (" + bright_unit.to_string() + ")") p.tight_layout() p.show() profile_fits = np.asarray(profile_fits) red_chisqs = np.asarray(red_chisqs) # Create an astropy table of the fit results param_names = ["Amplitude", "Std Dev", "Background"] param_errs = [par + " Error" for par in param_names] colnames = param_names + param_errs in_bright_units = [True, False, True] * 2 tab = QTable() tab["Number"] = np.arange(profile_fits.shape[0]) tab.add_index("Number") tab["Red Chisq"] = red_chisqs for i, (name, is_bright) in enumerate(zip(colnames, in_bright_units)): if is_bright: col_unit = bright_unit else: if distance is not None: col_unit = (u.pix * phys_per_pix).unit else: col_unit = (u.pix * deg_per_pix).unit tab[name] = profile_fits[:, i] * col_unit return line_distances, line_profiles, profile_extents, tab
__version__ = "unknown" __all__ = [] # roentgen specific configuration # load some data files on import _package_directory = os.path.dirname(os.path.abspath(__file__)) _data_directory = os.path.abspath(os.path.join(_package_directory, 'data')) elements_file = os.path.join(_data_directory, 'elements.csv') elements = QTable(ascii.read(elements_file, format='csv')) elements['density'].unit = u.g / (u.cm**3) elements['i'].unit = u.eV elements['ionization energy'].unit = u.eV elements['atomic mass'] = elements['z'] / elements['zovera'] * u.u elements.add_index('z') compounds_file = os.path.join(_data_directory, 'compounds_mixtures.csv') compounds = QTable(ascii.read(compounds_file, format='csv', fast_reader=False)) compounds['density'].unit = u.g / (u.cm**3) compounds.add_index('symbol') notation_translation = Table( ascii.read(os.path.join(_data_directory, 'siegbahn_to_iupac.csv'), format='csv', fast_reader=False)) emission_lines = QTable( ascii.read(os.path.join(_data_directory, 'emission_lines.csv'), format='csv', fast_reader=False))