def subdivide_mask(self, n_shortside=3, n_longside=4, preview=False, rotation_angle=None, padding=None, only_show=None, save_plot=None): """ Subdivide mask takes the image mask, draws a rectangle around the valid region of the mask, rotated to be as small as possible, and subdivides that rectangle. The rotation angle of the box can be set manually and space can be trimmed or added from the sides of the box. Parameters ---------- n_shortside : integer (optional) The number of cells along the short side of the rectangle. Default is 3. n_longside : integer (optional) The number of cells along the long side of the rectangle. Default is 4. preview : bool (optional) Should the mask show you what this set of parameters looks like but not store the results? If preview == True, either a plot will be shown on your screen (no value given for save_plot) or saved to a file (save_plot specified). The image mask object will not keep the division information. If preview == False, which is the default, the values will be stored and a plot will only be made if save_plot is specified. rotation_angle : scalar (optional) Fixes the rotation angle that the mask's bounding box will be defined at with respect to the XY coordinate system of the mask. By default, the routine will choose the integer angle in degrees which minimizes the area of the bounding box. padding : 4 element array-like (optional) How many pixels extra to allow in the rotated coordinates of the bounding box. The order is [bottom, top, left, right]. Positive padding corresponds to moving the edges outwards and leaving extra room. Negative padding will move the bounding box inward and cut off part of the mask. only_show : integer (optional) If you only want to see the random points that fall into one of the cells in the subdivided mask, you set only_show to that cell number. This will only matter if you have preview=True or have specified save_plot. save_plot : string (optional) Name with path from '/' of the file where you would like the plot of the subdivided mask saved. If only_show is set to an integer that corresponds to a cell number, only the points in that cell will be shown. """ #Start by putting down a bunch of randoms. ra, dec, __ = self.generate_random_sample(5.e4) x1, y1=self.ra_dec_to_xy(ra, dec) #Set the padding on each side if padding: try: pad_bottom, pad_top, pad_left, pad_right=padding except: #If we can't unpack the padding, either raise an #informative error (if we're doing the subdivision #permanently) or just a warning if we're only previewing says_str = "subdivide_mask says: " message_str = ("You have given me something I can't use " "for padding. Padding must be a 4-element " "1D array in the format [bottom, top, left," " right].") if preview == True: print (says_str + "WARNING! " + message_str + " No padding used this time") pad_bottom, pad_top, pad_left, pad_right=[0,0,0,0] else: raise ValueError(says_str + "ERROR! " + message_str) else: pad_bottom, pad_top, pad_left, pad_right=[0,0,0,0] #If we don't have an already chosen angle, choose a bunch of angles #and transform the coordinates to rotated systems and get the areas #of the rectangle enclosing all the data points at this angle. Take #The angle with the minimum area for the enclosing rectangle. if rotation_angle is None: thetas=np.radians(np.arange(90, dtype=np.float)) areas=[] corners=[] for th in thetas: x2, y2= misc.rotate_coords(x1, y1, th) #Don't take padding into account to determine the angle. x2min=x2.min() x2max=x2.max() y2min=y2.min() y2max=y2.max() areas.append((x2.max()-x2.min()) * (y2.max()-y2.min())) areas=np.asarray(areas) which_theta=np.where(areas==areas.min())[0][0] use_theta=thetas[which_theta] #Else, use the given angle else: use_theta=np.radians(rotation_angle) #Define the edges of the regions x2, y2= misc.rotate_coords(x1, y1, use_theta) x2min=x2.min() - pad_left x2max=x2.max() + pad_right y2min=y2.min() - pad_bottom y2max=y2.max() + pad_top #Figure out the x2 and y2 bin divisions if (x2max-x2min) < (y2max-y2min): nx=n_shortside ny=n_longside else: ny=n_shortside nx=n_longside x2edges = np.linspace(x2min, x2max, nx+1) y2edges = np.linspace(y2min, y2max, ny+1) #-----------------# #- Make the plot -# #-----------------# if preview or save_plot: #Figure out what subregions we have subregions=self.return_subregions(ra, dec, theta=use_theta, rot_xedges=x2edges, rot_yedges=y2edges) outside = subregions == -1 inside= np.invert(outside) if only_show is not None: this_box = subregions==only_show inside = inside & this_box #Make a figure and plot the random points fig=plt.figure() ax=fig.add_subplot(111) ax.scatter(x1[outside], y1[outside], c='LightGray') ax.scatter(x1[inside], y1[inside], c='Blue') #Plot the vertical lines for ix in range(nx+1): x2=[x2edges[ix], x2edges[ix]] y2=[y2min, y2max] x1, y1=misc.rotate_coords(x2, y2, -use_theta) ax.plot(x1, y1, color='Red', lw=2) #Plot the horizontal lines for iy in range(ny+1): x2=[x2min, x2max] y2=[y2edges[iy], y2edges[iy]] x1, y1=misc.rotate_coords(x2, y2, -use_theta) ax.plot(x1, y1, color='Red', lw=2) #Figure out the dimensions of the boxes in angular space x2=[x2edges[0], x2edges[0], x2edges[1]] y2=[y2edges[0], y2edges[1], y2edges[0]] ra_box, dec_box=self.xy_to_ra_dec(x2, y2) y_side=misc.ang_sep(ra_box[0], dec_box[0], ra_box[1], dec_box[1], radians_in=False, radians_out=False) * 3600 x_side=misc.ang_sep(ra_box[0], dec_box[0], ra_box[2], dec_box[2], radians_in=False, radians_out=False) * 3600 #Print out the parameters ax.text(.05, .95, "theta= "+str(np.degrees(use_theta))[0:5], transform=ax.transAxes, fontsize=8) ax.text(.05, .925, "padding="+str(padding), transform=ax.transAxes, fontsize=8) ax.text(.05, .9, "n_longside="+str(n_longside), transform=ax.transAxes, fontsize=8) ax.text(.05, .875, "n_shortside="+str(n_shortside), transform=ax.transAxes, fontsize=8) ax.text(.5, .05, ("box size: "+str(x_side)[0:5]+" by "+ str(y_side)[0:5]+" arcsec"), transform=ax.transAxes, fontsize=8) #Label the subregions y_label_coord=.85 avg_ngals=float(len(ra[np.invert(outside)]))/(nx*ny) ax.text(0.8, 0.95, "N_bin/N_avg", transform=ax.transAxes, fontsize=12) ax.text(0.8, 0.9, "outside-> "+str(float(len(ra[outside]))/avg_ngals)[0:4], transform=ax.transAxes, fontsize=9) for ix in range(nx): for iy in range(ny): #What bin number is this? bin_number= nx*iy + ix #Where's the center of the box? text_x2=(x2edges[ix] + x2edges[ix+1])/2. text_y2=(y2edges[iy] + y2edges[iy+1])/2. text_x1, text_y1=misc.rotate_coords(text_x2, text_y2, -use_theta) #Print the bin number at the center of the box ax.text(text_x1, text_y1, str(bin_number), fontsize=20, color='Lime', horizontalalignment='center', verticalalignment='center') #Print the number of galaxies in the upper right corner thisbin= subregions==bin_number n_thisbin= float(len(ra[thisbin])) print ("bin "+str(bin_number)+" has "+str(n_thisbin)+ " randoms in it") display_string=('bin '+str(bin_number)+'-> '+ str(n_thisbin/avg_ngals)[0:4]) ax.text(0.85, y_label_coord, display_string, transform=ax.transAxes, fontsize=9) y_label_coord-=0.05 if save_plot: plt.savefig(save_plot, bbox_inches='tight') plt.close() else: plt.show() #If we want to store the information, do so if not preview: #Store the subregion information self.set_subregions(use_theta, x2edges, y2edges)
def return_subregions(self, ra, dec, theta=None, rot_xedges=None, rot_yedges=None): """ Returns the subregion number for each pair of RA and Dec given the parameters either stored or given as function parameters. Parameters ---------- ra : array-like A list of RAs to get subregion numbers for (in degrees) dec : array-like A list of Decs to get subregion numbers for (in degrees) theta : scalar (optional) Rotation angle that the mask's bounding box will be defined at with respect to the XY coordinate system of the mask. If not given, the routine will look for a stored theta. Units are degrees rot_xedges : array-like (optional) The x coordinates of the cell boundaries in the rotated coordinate system. If not given, the routine will look for a stored theta. rot_yedges : array-like (optional) The x coordinates of the cell boundaries in the rotated coordinate system. If not given, the routine will look for a stored theta. Returns ------- subregions : numpy ndarray The subregion number for each of the points input. The array shape is (len(ra),). The subregion -1 is outside the bounding box (this only happens if you've set negative padding somewhere or have asked for things outside the mask). """ #Check to make sure we have what we need, pull to local if we have #stored values but no given values if (theta is None): if (self._subregion_rotation is None): raise ValueError("ImageMask.return_subregions says: " "ERROR! I don't have the rotation " "angle. Please provide one.") else: theta=self._subregion_rotation if (rot_xedges is None): if (self._subregion_rotated_xedges is None): raise ValueError("ImageMask.return_subregions says: " "ERROR! I don't have rotated x edges." " Please provide them.") else: rot_xedges=self._subregion_rotated_xedges if (rot_yedges is None): if (self._subregion_rotated_yedges is None): raise ValueError("ImageMask.return_subregions says: " "ERROR! I don't have rotated y edges. " "Please provide them.") else: rot_yedges=self._subregion_rotated_yedges #Now that we know we have everything, put the ra and decs into x #and y coords x1, y1=self.ra_dec_to_xy(ra, dec) #Transform to the rotated coordinate system x2, y2=misc.rotate_coords(x1, y1, theta) #Now make masks for each row and column nx=len(rot_xedges)-1 ny=len(rot_yedges)-1 ymasks={} xmasks={} for i in range(nx): xmasks[i]=ma.masked_inside(x2, rot_xedges[i], rot_xedges[i+1]).mask for i in range(ny): ymasks[i]=ma.masked_inside(y2, rot_yedges[i], rot_yedges[i+1]).mask #Now use the masks to put numbers to each galaxy #No subregion defaults to -1 subregion=-np.ones(len(ra)) for ix in range(nx): for iy in range(ny): bin_number= nx*iy + ix thismask = xmasks[ix] & ymasks[iy] subregion[thismask]=bin_number return subregion