def duplicate_filter(maps, glons, glats, separation = 0):
	"""
	Removes duplicate entries of sources in a single wavelength
	catalogue. Separation is 0 by default (only exact duplicates)
	but can be set to a non-zero value [arcseconds] to also remove
	sources with centroids closer together than this separation.
	"""
	source_count = len(maps)
	for x in range(source_count):
		if glons[x] != 9000:
			# arrays containing copies of ONE reference coordinate to check
			temp_glon = np.empty(source_count)
			temp_glat = np.empty(source_count)
			temp_glon.fill(glons[x])
			temp_glat.fill(glats[x])
			# arrays containing the full set of reference sources to compare to
			other_glons = glons
			other_glats = glats
			dist = 3600*coord_tools.ang_sep(temp_glon, temp_glat, other_glons, other_glats)
			# remove all sources subject to same_wl_beam
			same_source = [ind for ind,v in enumerate(dist) if v<=separation]
			num_same_source = len(same_source)
			# will always return at least 1 (compared to self in other_source_glon)
			if num_same_source >= 2:
				# this is a duplicate source, set to invalid coordinates
				# new: randomise which source is kept
				keep_source = random.choice(same_source)
				for index in same_source:
					if index != keep_source:
						# remove all other duplicates
						glons[index] = 9000
						glats[index] = 9000

	# filter out the marked duplicate coordinates
	not_a_duplicate = [i for i,v in enumerate(glons) if v != 9000]

	# edit arrays to only contain useful sources
	glons = glons[not_a_duplicate]
	glats = glats[not_a_duplicate]
	maps = [maps[i] for i in not_a_duplicate]

	return maps, glons, glats
def find_counterparts(single_wl_maps, glon, glat, wavelengths, wavelengths_required, wavelengths_excluded, reference_wavelength, beam):
    """
    Associates counterparts to sources in single wavelength catalogues
    that are passed in as map, glon and glat dictionaries with
    wavelength values as keys. All sources with at least one
    counterpart in all wavelengths_required listed, and no counterparts
    in any wavelengths_excluded.
    Counterparts are associated to a source in the reference_wavelength
    if the centroid is within 1 beam of the reference source centroid.
    Returns candidates with all requirements met by a map list,
    glon in each required wavelength, glat in each required wavelength
    and the distance to an associated centroid in each wavelength.
    """
    total_wavelengths = len(wavelengths)
    total_sources = len(single_wl_maps[reference_wavelength])
    total_required = len(wavelengths_required)

    # set up arrays for storing sources + counterparts
    source_glons = np.zeros((total_wavelengths, total_sources))
    source_glats = np.zeros((total_wavelengths, total_sources))
    source_dists = np.zeros((total_wavelengths, total_sources))
    source_all_wl = np.zeros((total_wavelengths, total_sources))

    # for all wavelengths required to be considered a candidate source
    for i,j in enumerate(wavelengths):
        print 'Associating sources in ' + str(j) + ' microns... \n'
    	if j == reference_wavelength:
    		source_all_wl[i,:] = 1
    		source_glons[i,:] = glon[j]
    		source_glats[i,:] = glat[j]
    	else:
    		for x in range(total_sources):
    			other_chan = i
    			total_other_wl_sources = len(single_wl_maps[j])
    			# make arrays of current 160 source to compare to all other sources
    			temp_glon = np.empty(total_other_wl_sources)
    			temp_glat = np.empty(total_other_wl_sources)
    			temp_glon.fill(glon[reference_wavelength][x])
    			temp_glat.fill(glat[reference_wavelength][x])
    			# make arrays of other sources
    			other_source_glon = glon[j]
    			other_source_glat = glat[j]
    			# find distances
    			dist = 3600*coord_tools.ang_sep(temp_glon, temp_glat, other_source_glon, other_source_glat)
    			# find indicies of all sources within set distance from ref centroid
    			same_source = [ind for ind, v in enumerate(dist) if v<=beam]
    			num_associated_sources = len(same_source)
    			if num_associated_sources != 0:
    				if j in wavelengths_required:
    					# is useful, save details
    					source_all_wl[other_chan, x] = 1
    					if num_associated_sources == 1:
    						# save coordinates of counterpart
    						source_glons[other_chan, x] = other_source_glon[same_source[0]]
    						source_glats[other_chan, x] = other_source_glat[same_source[0]]
    						source_dists[other_chan, x] = dist[same_source[0]]
    					else:
    						# pick closest counterpart and save coordinates
    						closest_source = [ind for ind,v in enumerate(dist) if v == np.amin(dist[same_source])]
    						source_glons[other_chan, x] = other_source_glon[closest_source[0]]
    						source_glats[other_chan, x] = other_source_glat[closest_source[0]]
    						source_dists[other_chan, x] = dist[closest_source[0]]
    				elif j in wavelengths_excluded:
    					# make sure source does not appear in candidates list
    					source_all_wl[other_chan, x] = 800
    				#else:
    					# ignore, not interested in this wavelength
    					# can put extra code here to do something with sources
    					# detected in additional (but not required) wavelengths
    			#else:
    				# do not change entry in indicator array


    # filter lists to contain only sources detected in all wavelengths
    total_wavelengths_observed = source_all_wl.sum(axis=0)
    candidate_indices = [i for i,v in enumerate(total_wavelengths_observed) if v==total_required]
    candidate_indices = np.asarray(candidate_indices)
    # the map assigned to each source is the map of the reference wavelength source
    candidate_maps = [single_wl_maps[reference_wavelength][i] for i in candidate_indices]
    candidate_glons = {}
    candidate_glats = {}
    candidate_dists= {}
    for x in range(total_wavelengths):
    	candidate_glons[wavelengths[x]] = source_glons[x,candidate_indices]
    	candidate_glats[wavelengths[x]] = source_glats[x, candidate_indices]
    	candidate_dists[wavelengths[x]] = source_dists[x, candidate_indices]

    return candidate_maps, candidate_glons, candidate_glats, candidate_dists