def mode_estimate(self,
                      dist,
                      mode_dists,
                      distance_method='euclidean',
                      metric='pcd'):
        """---------------------------------------------------------------------------------------
		Given the tonic (or candidate tonic), compares the piece's distribution using the candidate
		modes and returns the resultant distance vector to higher level functions.
		---------------------------------------------------------------------------------------"""

        if (metric == 'pcd'):
            distance_vector = np.array(
                mf.generate_distance_matrix(dist, [0],
                                            mode_dists,
                                            method=distance_method))

        elif (metric == 'pd'):
            distance_vector = np.zeros(len(mode_dists))
            for i in range(len(mode_dists)):
                trial = p_d.PitchDistribution(dist.bins,
                                              dist.vals,
                                              kernel_width=dist.kernel_width,
                                              source=dist.source,
                                              ref_freq=dist.ref_freq,
                                              segment=dist.segmentation)
                trial, mode_trial = mf.pd_zero_pad(trial,
                                                   mode_dists[i],
                                                   cent_ss=self.cent_ss)
                distance_vector[i] = mf.distance(trial,
                                                 mode_trial,
                                                 method=distance_method)
        return distance_vector
	def mode_estimate(self, dist, mode_dists, distance_method='euclidean', metric='pcd'):
		"""---------------------------------------------------------------------------------------
		Given the tonic (or candidate tonic), compares the piece's distribution using the candidate
		modes and returns the resultant distance vector to higher level functions.
		---------------------------------------------------------------------------------------"""

		if(metric=='pcd'):
			distance_vector = np.array(mf.generate_distance_matrix(dist, [0], mode_dists, method=distance_method))

		elif(metric=='pd'):
			distance_vector = np.zeros(len(mode_dists))
			for i in range(len(mode_dists)):
				trial = p_d.PitchDistribution(dist.bins, dist.vals, kernel_width=dist.kernel_width, source=dist.source, ref_freq=dist.ref_freq, segment=dist.segmentation)
				trial, mode_trial = mf.pd_zero_pad(trial, mode_dists[i], cent_ss=self.cent_ss)
				distance_vector[i] = mf.distance(trial, mode_trial, method=distance_method)
		return distance_vector
    def tonic_estimate(self,
                       dist,
                       peak_idxs,
                       mode_dist,
                       distance_method="euclidean",
                       metric='pcd'):
        """---------------------------------------------------------------------------------------
		Given the mode (or candidate mode), compares the piece's distribution using the candidate
		tonics and returns the resultant distance vector to higher level functions.
		---------------------------------------------------------------------------------------"""
        ### Mode is known, tonic is estimated.
        ### Piece's distributon is generated

        if (metric == 'pcd'):
            return np.array(
                mf.generate_distance_matrix(dist,
                                            peak_idxs, [mode_dist],
                                            method=distance_method))[:, 0]

        elif (metric == 'pd'):
            temp = p_d.PitchDistribution(dist.bins,
                                         dist.vals,
                                         kernel_width=dist.kernel_width,
                                         source=dist.source,
                                         ref_freq=dist.ref_freq,
                                         segment=dist.segmentation)
            temp, mode_dist = mf.pd_zero_pad(temp,
                                             mode_dist,
                                             cent_ss=self.cent_ss)

            ### Filling both sides of vals with zeros, to make sure that the shifts won't drop any non-zero values
            temp.vals = np.concatenate(
                (np.zeros(abs(max(peak_idxs))), temp.vals,
                 np.zeros(abs(min(peak_idxs)))))
            mode_dist.vals = np.concatenate(
                (np.zeros(abs(max(peak_idxs))), mode_dist.vals,
                 np.zeros(abs(min(peak_idxs)))))

            return np.array(
                mf.generate_distance_matrix(temp,
                                            peak_idxs, [mode_dist],
                                            method=distance_method))[:, 0]
	def tonic_estimate(self, dist, peak_idxs, mode_dist, distance_method="euclidean", metric='pcd'):
		"""---------------------------------------------------------------------------------------
		Given the mode (or candidate mode), compares the piece's distribution using the candidate
		tonics and returns the resultant distance vector to higher level functions.
		---------------------------------------------------------------------------------------"""
		### Mode is known, tonic is estimated.
		### Piece's distributon is generated
		
		if(metric=='pcd'):
			return np.array(mf.generate_distance_matrix(dist, peak_idxs, [mode_dist], method=distance_method))[:,0]

		elif(metric=='pd'):
			temp = p_d.PitchDistribution(dist.bins, dist.vals, kernel_width=dist.kernel_width, source=dist.source, ref_freq=dist.ref_freq, segment=dist.segmentation)
			temp, mode_dist = mf.pd_zero_pad(temp, mode_dist, cent_ss=self.cent_ss)

			### Filling both sides of vals with zeros, to make sure that the shifts won't drop any non-zero values
			temp.vals = np.concatenate((np.zeros(abs(max(peak_idxs))), temp.vals, np.zeros(abs(min(peak_idxs)))))
			mode_dist.vals = np.concatenate((np.zeros(abs(max(peak_idxs))), mode_dist.vals, np.zeros(abs(min(peak_idxs)))))
			
			return np.array(mf.generate_distance_matrix(temp, peak_idxs, [mode_dist], method=distance_method))[:,0]
    def estimate(self,
                 pitch_track,
                 mode_names=[],
                 mode_name='',
                 mode_dir='./',
                 est_tonic=True,
                 est_mode=True,
                 rank=1,
                 distance_method="euclidean",
                 metric='pcd',
                 ref_freq=440):
        ### Preliminaries before the estimations
        cent_track = mf.hz_to_cent(pitch_track, ref_freq)
        dist = mf.generate_pd(cent_track,
                              ref_freq=ref_freq,
                              smooth_factor=self.smooth_factor,
                              cent_ss=self.cent_ss)
        dist = mf.generate_pcd(dist) if (metric == 'pcd') else dist
        mode_collections = [
            self.load_collection(mode, metric, dist_dir=mode_dir)
            for mode in mode_names
        ]
        mode_dists = [dist for col in mode_collections for dist in col]
        mode_dist = self.load_collection(
            mode_name, metric,
            dist_dir=mode_dir) if (mode_name != '') else None
        tonic_list = np.zeros(rank)
        mode_list = ['' for x in range(rank)]

        if (est_tonic):
            if (metric == 'pcd'):
                ### Shifting to the global minima to prevent wrong detection of peaks
                shift_factor = dist.vals.tolist().index(min(dist.vals))
                dist = dist.shift(shift_factor)
                anti_freq = mf.cent_to_hz([dist.bins[shift_factor]],
                                          ref_freq=ref_freq)[0]
                peak_idxs, peak_vals = dist.detect_peaks()
            elif (metric == 'pd'):
                peak_idxs, peak_vals = dist.detect_peaks()
                origin = np.where(dist.bins == 0)[0][0]
                shift_idxs = [(idx - origin) for idx in peak_idxs]

        ### Call to actual estimation functions
        if (est_tonic and est_mode):
            if (metric == 'pcd'):
                dist_mat = mf.generate_distance_matrix(dist,
                                                       peak_idxs,
                                                       mode_dists,
                                                       method=distance_method)
                for r in range(rank):
                    min_row = np.where((dist_mat == np.amin(dist_mat)))[0][0]
                    min_col = np.where((dist_mat == np.amin(dist_mat)))[1][0]
                    tonic_list[r] = mf.cent_to_hz(
                        [dist.bins[peak_idxs[min_row]]], anti_freq)[0]
                    mode_list[r] = (mode_dists[min_col].source,
                                    mode_dists[min_col].segmentation)
                    dist_mat[min_row][min_col] = (np.amax(dist_mat) + 1)
                return mode_list, tonic_list

            elif (metric == 'pd'):
                dist_mat = np.zeros((len(shift_idxs), len(mode_dists)))
                for m in range(len(mode_dists)):
                    dist_mat[:, m] = self.tonic_estimate(
                        dist,
                        shift_idxs,
                        mode_dists[m],
                        distance_method=distance_method,
                        metric=metric)
                for r in range(rank):
                    min_row = np.where((dist_mat == np.amin(dist_mat)))[0][0]
                    min_col = np.where((dist_mat == np.amin(dist_mat)))[1][0]
                    tonic_list[r] = mf.cent_to_hz(
                        [shift_idxs[min_row] * self.cent_ss], ref_freq)[0]
                    mode_list[r] = (mode_dists[min_col].source,
                                    mode_dists[min_col].segmentation)
                    dist_mat[min_row][min_col] = (np.amax(dist_mat) + 1)
                return mode_list, tonic_list

        elif (est_tonic):
            if (metric == 'pcd'):
                dist_mat = [(np.array(
                    mf.generate_distance_matrix(dist,
                                                peak_idxs, [d],
                                                method=distance_method))[:, 0])
                            for d in mode_dist]

            elif (metric == 'pd'):
                peak_idxs = shift_idxs
                temp = p_d.PitchDistribution(dist.bins,
                                             dist.vals,
                                             kernel_width=dist.kernel_width,
                                             source=dist.source,
                                             ref_freq=dist.ref_freq,
                                             segment=dist.segmentation)
                dist_mat = []
                for d in mode_dist:
                    temp, d = mf.pd_zero_pad(temp, d, cent_ss=self.cent_ss)

                    ### Filling both sides of vals with zeros, to make sure that the shifts won't drop any non-zero values
                    temp.vals = np.concatenate(
                        (np.zeros(abs(max(peak_idxs))), temp.vals,
                         np.zeros(abs(min(peak_idxs)))))
                    d.vals = np.concatenate(
                        (np.zeros(abs(max(peak_idxs))), d.vals,
                         np.zeros(abs(min(peak_idxs)))))
                    cur_vector = np.array(
                        mf.generate_distance_matrix(temp,
                                                    peak_idxs, [d],
                                                    method=distance_method))[:,
                                                                             0]
                    dist_mat.append(cur_vector)

            for r in range(rank):
                min_row = np.where((dist_mat == np.amin(dist_mat)))[0][0]
                min_col = np.where((dist_mat == np.amin(dist_mat)))[1][0]
                tonic_list[r] = mf.cent_to_hz([dist.bins[peak_idxs[min_row]]],
                                              anti_freq)[0]
                dist_mat[min_row][min_col] = (np.amax(dist_mat) + 1)
            return tonic_list

        elif (est_mode):
            distance_vector = self.mode_estimate(
                dist,
                mode_dists,
                distance_method=distance_method,
                metric=metric)
            for r in range(rank):
                idx = np.argmin(distance_vector)
                mode_list[r] = (mode_dists[idx].source,
                                mode_dists[idx].segmentation)
                distance_vector[idx] = (np.amax(distance_vector) + 1)
            return mode_list

        else:
            return 0