diff --git a/cluster.py b/cluster.py index a2c2cbc590070098e0548be38d61f289b598d0b0..c3fc276b0cf0d18151f463be543221f1c6a1896c 100755 --- a/cluster.py +++ b/cluster.py @@ -16,18 +16,24 @@ from astropy.coordinates import SkyCoord, angles from astropy.stats import median_absolute_deviation as mad import astropy.units as u import numpy as np +import logging + +import matplotlib +matplotlib._log.disabled = True +logging.getLogger("matplotlib").setLevel(logging.WARNING) + import matplotlib.pyplot as plt import pandas as pd from matplotlib.patches import Circle, Rectangle, Ellipse -# import copy +# from sklearn.cluster import KMeans +from scipy.spatial import Voronoi, cKDTree -import logging logging.basicConfig(level=logging.DEBUG) def ra2deg(ra): s = np.array(ra.split(':'), dtype=float) - if s[0] < 0.0: + if ra.startswith('-'): sign = -1.0 else: sign = 1.0 @@ -367,7 +373,7 @@ def auto_clustering(fig, ax, df, wcs, resid_data, pix_arcmin_scale, nbright, src_index += 1 # skip the edge sources - if (abs(px-resid_data.shape[1]) < boxsize)or (abs(py-resid_data.shape[0]) < boxsize): + if (abs(px-resid_data.shape[1]) < boxsize) or (abs(py-resid_data.shape[0]) < boxsize): logging.debug('Skipping the edge source') continue @@ -445,17 +451,220 @@ def auto_clustering(fig, ax, df, wcs, resid_data, pix_arcmin_scale, nbright, return final_clusters -# TODO -def voronoy_clustering(): + +def voronoi_plot_2d_world(vor, ax=None, **kw): + """ + Plot the given Voronoi diagram in 2-D on "world" ax transform + + Parameters + ---------- + vor : scipy.spatial.Voronoi instance + Diagram to plot + ax : matplotlib.axes.Axes instance, optional + Axes to plot on + show_points: bool, optional + Add the Voronoi points to the plot. + show_vertices : bool, optional + Add the Voronoi vertices to the plot. + line_colors : string, optional + Specifies the line color for polygon boundaries + line_width : float, optional + Specifies the line width for polygon boundaries + line_alpha: float, optional + Specifies the line alpha for polygon boundaries + point_size: float, optional + Specifies the size of points + + + Returns + ------- + fig : matplotlib.figure.Figure instance + Figure for the plot + + See Also + -------- + Voronoi + + Notes + ----- + Requires Matplotlib. + + Examples + -------- + Set of point: + + >>> import matplotlib.pyplot as plt + >>> points = np.random.rand(10,2) #random + + Voronoi diagram of the points: + + >>> from scipy.spatial import Voronoi, voronoi_plot_2d + >>> vor = Voronoi(points) + + using `voronoi_plot_2d` for visualisation: + + >>> fig = voronoi_plot_2d(vor) + + using `voronoi_plot_2d` for visualisation with enhancements: + + >>> fig = voronoi_plot_2d(vor, show_vertices=False, line_colors='orange', + ... line_width=2, line_alpha=0.6, point_size=2) + >>> plt.show() + """ - Use Voronoy clustering instead of fixed radius around sources + from matplotlib.collections import LineCollection + + if ax: + xlim = ax.get_xlim() + ylim = ax.get_ylim() + + if vor.points.shape[1] != 2: + raise ValueError("Voronoi diagram is not 2-D") + + if kw.get('show_points', True): + point_size = kw.get('point_size', None) + ax.plot(vor.points[:,0], vor.points[:,1], '+', markersize=point_size, transform=ax.get_transform("world")) + if kw.get('show_vertices', True): + ax.plot(vor.vertices[:,0], vor.vertices[:,1], '.', transform=ax.get_transform("world")) + + line_colors = kw.get('line_colors', 'k') + line_width = kw.get('line_width', 1.0) + line_alpha = kw.get('line_alpha', 1.0) + + center = vor.points.mean(axis=0) + ptp_bound = vor.points.ptp(axis=0) + + finite_segments = [] + infinite_segments = [] + for pointidx, simplex in zip(vor.ridge_points, vor.ridge_vertices): + simplex = np.asarray(simplex) + if np.all(simplex >= 0): + finite_segments.append(vor.vertices[simplex]) + else: + i = simplex[simplex >= 0][0] # finite end Voronoi vertex + + t = vor.points[pointidx[1]] - vor.points[pointidx[0]] # tangent + t /= np.linalg.norm(t) + n = np.array([-t[1], t[0]]) # normal + + midpoint = vor.points[pointidx].mean(axis=0) + direction = np.sign(np.dot(midpoint - center, n)) * n + if (vor.furthest_site): + direction = -direction + far_point = vor.vertices[i] + direction * ptp_bound.max() + + infinite_segments.append([vor.vertices[i], far_point]) + + ax.add_collection(LineCollection(finite_segments, + colors=line_colors, + lw=line_width, + alpha=line_alpha, + linestyle='solid', + transform=ax.get_transform("world"))) + ax.add_collection(LineCollection(infinite_segments, + colors=line_colors, + lw=line_width, + alpha=line_alpha, + linestyle='dashed', + transform=ax.get_transform("world"))) + + if ax: + ax.set_xlim(xlim) + ax.set_ylim(ylim) + return ax.figure + + +def write_df_voronoi(df, vor, output=None): + output = output or 'model_clustered.txt' + with open(output, 'w') as out: + out.write("Format = Name,Patch,Type,Ra,Dec,I,Q,U,V,SpectralIndex,LogarithmicSI,ReferenceFrequency='1399603271.48438',MajorAxis,MinorAxis,Orientation\n") + nn_tree = cKDTree(vor.points) + dists, regs = nn_tree.query(np.array([df.ra, df.dec]).T) + df['nn'] = regs + df['dist'] = dists + for i, p in enumerate(vor.points): + clust_df = df.query('nn == @i').copy() + clust_df.loc[clust_df.index,'Patch'] = 'cluster{}'.format(i+1) + with open(output, 'a') as out: + out.write(', cluster{}, POINT, , , , , , , , , , , ,\n'.format(i+1)) + clust_df.to_csv(out, index=False, header=False, columns=df.columns[:-4]) + return output + + +def voronoi_clustering(fig, ax, df, wcs, resid_data, nbright, nclusters, + boxsize=250, same_source_radius=5, central_region=True): """ - pass + Use Voronoi clustering instead of fixed radius around sources + """ + + # logging.info('Checking {} brightest model components'.format(nbright)) + + bright_df = df.sort_values('I')[::-1][:nbright][['ra', 'dec', 'I']] + # csnrs = [] + # cfluxes = [] + # cmeasures = [] # the main value to clusterize by + # cellipse_params = [] + # fmin, fmax = min(a.I), max(a.I) + rms = mad(resid_data) + # resid_mean = np.mean(resid_data) + + logging.info('Getting measures for the potential clusters...') + clusters = [] + clusters_centers = [] # + + + for ra, dec, flux in bright_df.values: + c = SkyCoord(ra, dec, unit='deg') + px, py = np.round(wcs.all_world2pix(c.ra, c.dec, 0)).astype(int) +# skip the edge sources + if (abs(px-resid_data.shape[1]) < boxsize) or (abs(py-resid_data.shape[0]) < boxsize): + # logging.debug('Skipping the edge source') + continue +# Check if the component already in a cluster + if clusters and any([c.separation(_).arcmin<same_source_radius for _ in clusters]): + continue + + small_resid = resid_data[py-boxsize:py+boxsize, px-boxsize:px+boxsize] + ellipse_mean, ecc, amaj, numpix = ellipses_coh(small_resid, amin=20, amax=boxsize-1, dr=1.0) + + if abs(ellipse_mean/rms) > 1.4: + # rect = plt.Rectangle((px-boxsize, py-boxsize), 2*boxsize, 2*boxsize, + # lw=1, color='k', fc='none', alpha=0.3) + # ax.add_artist(rect) + clusters_centers.append([ra, dec]) + clusters.append(c) + print(ra, dec) + if (isinstance(nclusters, int)) and (len(clusters_centers) >= nclusters): + logging.debug('Max cluster number reached. Breaking...') + break -def main(img, resid, model, auto=True, add_manual=False, nclusters=5, boxsize=250, + if isinstance(nclusters, int) and len(clusters_centers) < nclusters: + logging.warning('Decreasing number of clusters') + nclusters = len(clusters_centers) + + # X = np.vstack([df.ra, df.dec]).T + # kmeans = KMeans(n_clusters=nclusters, init=clusters_centers, n_init=1, random_state=0, max_iter=1) + # kmeans.fit(X) + # y_kmeans = kmeans.predict(X) # cluster index for each observation + # ax.scatter(clusters_centers[:, 0], clusters_centers[:, 1], c='black', s=100, alpha=0.5, transform=ax.get_transform("world")) + + vor = Voronoi(np.array(clusters_centers)) + voronoi_plot_2d_world(vor, ax=ax, show_vertices=False) + + return vor + + +def main(img, resid, model, clustering_method='Voronoi', add_manual=False, nclusters=10, boxsize=250, nbright=80, cluster_radius=5, cluster_overlap=1.6): + """ + clustering + methods: + Voronoi + auto + manual + """ imgbase = os.path.splitext(img)[0] output = imgbase + '-clustered.txt' @@ -476,22 +685,28 @@ def main(img, resid, model, auto=True, add_manual=False, nclusters=5, boxsize=25 pix_arcmin_scale = f[0].header['CDELT2']*60 # racen = f[0].header['CRVAL1'] # deccen = f[0].header['CRVAL2'] - cluster_radius = angles.Angle(cluster_radius, unit='arcmin') fig = plt.figure(figsize=[12,12]) ax = fig.add_subplot(1,1,1, projection=wcs.celestial) vmin, vmax = np.percentile(image_data, 5), np.percentile(image_data, 95) ax.imshow(resid_data, vmin=vmin, vmax=vmax, origin='lower')#cmap='gray', vmin=2e-5, vmax=0.1)#, norm=LogNorm()) - if auto: + + if clustering_method.lower() == 'voronoi': + + vor = voronoi_clustering(fig, ax, df, wcs, resid_data, nbright, nclusters=nclusters) + write_df_voronoi(df, vor, output=output) + + elif clustering_method.lower() == 'auto': + cluster_radius = angles.Angle(cluster_radius, unit='arcmin') clusters = auto_clustering(fig, ax, df, wcs, resid_data, pix_arcmin_scale, nbright, cluster_radius, cluster_overlap, boxsize=boxsize, nclusters=nclusters) if add_manual: clusters_man = manual_clustering(fig, ax, wcs, pix_arcmin_scale, startnum=len(clusters)+1) clusters = clusters + clusters_man - else: + write_df(df, clusters, output=output) + elif clustering_method.lower() == 'manual': clusters = manual_clustering(fig, ax, wcs, pix_arcmin_scale) - - if clusters: write_df(df, clusters, output=output) + fig.tight_layout() fig.savefig(imgbase+'-clustering.png') return output @@ -500,15 +715,7 @@ def main(img, resid, model, auto=True, add_manual=False, nclusters=5, boxsize=25 ### if __name__ == "__main__": if __name__ == "__main__": - # base = '/home/kutkin/apertif/clustering/191209026/01/dical2-' - # base = '/home/kutkin/apertif/clustering/191209026/15/dical2-' - # base = '/home/kutkin/apertif/clustering/191010042/00/dical2-' - # base = '/home/kutkin/apertif/clustering/191006041_21/dical2-' - # base = '/home/kutkin/apertif/clustering/191010042_25/dical-' - # base = '/home/kutkin/apertif/clustering/191209026/10/dical2-' - base = '/home/kutkin/apertif/clustering/191010041/23/dical2-' - # base = '/home/kutkin/apertif/clustering/190915041/25/dical2-' - + base = '/kutkin/apipeline/to_validate/210109001_06-dical-' img = base + 'image.fits' resid = base + 'residual.fits' model = base + 'sources.txt' @@ -516,5 +723,5 @@ if __name__ == "__main__": # resid = sys.argv[2] # model = sys.argv[3] - main(img, resid, model, auto=True, nclusters='auto') + main(img, resid, model, clustering_method='Voronoi', nclusters=6) diff --git a/imcal.py b/imcal.py index cd6a1e4455a8435ec8cb51a05d46054e7b96df19..93618fbbce65903312f133049c484d6bc13e0097 100755 --- a/imcal.py +++ b/imcal.py @@ -199,7 +199,7 @@ def dical(msin, srcdb, msout=None, h5out=None, solint=1, startchan=0, split_ncha def ddecal(msin, srcdb, msout=None, h5out=None, solint=1, nfreq=15, - startchan=0, nchan=192, mode='diagonal', uvlambdamin=500, subtract=True): + startchan=0, nchan=0, mode='diagonal', uvlambdamin=500, subtract=True): """ Perform direction dependent calibration with DPPP """ h5out = h5out or os.path.split(msin)[0] + '/ddcal.h5' msbase = os.path.basename(msin).split('.')[0] diff --git a/imcal.yml b/imcal.yml index 997fc128970c975d6ee36d988663bf17a473aae4..e28bb2a53b0ac98f43aab8c29826683dc3ea644c 100644 --- a/imcal.yml +++ b/imcal.yml @@ -76,10 +76,11 @@ clean4: cluster: nbright: 80 # number of brightest clean components (CC) to check for artefacts boxsize: 250 # the boxsize around CC in pixels where to check for artefacts - nclusters: 10 # number of clusters ('auto' -- to set automatically) + nclusters: 6 # number of clusters ('auto' -- to set automatically) + clustering_method: 'Voronoi' +# the following is only for 'auto' and 'manual' mathods: cluster_radius: 5 # arcmin - cluster_overlap: 1.6 # if lower than 2 clusters can intersect - auto: True + cluster_overlap: 2 # if lower than 2 clusters can intersect add_manual: False ######################## DD CALIBRATION #######################