<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def _bird_core(X, scales, n_runs, Lambda_W, max_iter=100, stop_crit=np.mean, selection_rule=np.sum, n_jobs=1, indep=True, random_state=None, memory=Memory(None), verbose=False):
 """Automatically detect when noise zone has been reached and stop MP at this point Parameters X : array, shape (n_channels, n_times) The numpy n_channels-vy-N array to be denoised where n_channels is number of sensors and N the dimension scales : list The list of MDCT scales that will be used to built the dictionary Phi n_runs : int the number of runs (n_runs in the paper) Lambda_W : float bound for lambda under which a run will be stopped max_iter : int Maximum number of iterations (serves as alternate stopping criterion) stop_crit : function controls the calculation of Lambda selection_rule : callable controls the way multiple channel projections are combined for atom selection only used if indep=False n_jobs : int number of jobs to run in parallel indep : bool True for BIRD (independent processing of each channel, False for S-BIRD (structured sparsity seeked) random_state : None | int | np.random.RandomState To specify the random generator state (seed). memory : instance of Memory The object to use to cache some computations. If cachedir is None, no caching is performed. verbose : bool verbose mode Returns ------- X_denoise : array, shape (n_channels, n_times) denoised array of same shape as X """

    Phi = MDCT(scales)
    pad = int(1.5 * max(scales))
    X_denoise = np.zeros_like(X)
    approx = []
    rng = check_random_state(random_state)
    seeds = rng.randint(4294967295, size=n_runs)  # < max seed value

    if n_jobs <= 0:
        n_cores = multiprocessing.cpu_count()
        n_jobs = min(n_cores + n_jobs + 1, n_cores)

    if indep:
        # Independent treat of each channel (plain BIRD)
        for r, x in zip(X_denoise, X):
            this_approx = Parallel(n_jobs=n_jobs)(
                        delayed(_denoise)(this_seeds, x, Phi, Lambda_W,
                                          max_iter, pad=pad, verbose=verbose,
                                          indep=True, memory=memory)
                                          for this_seeds in
                                          np.array_split(seeds, n_jobs))
            this_approx = sum(this_approx[1:], this_approx[0])
            r[:] = sum([a[pad:-pad] for a in this_approx])
            approx.append(this_approx)
    else:
        # data need to be processed jointly
        this_approx = Parallel(n_jobs=n_jobs)(
                        delayed(_denoise)(this_seeds, X, Phi, Lambda_W,
                                          max_iter, pad=pad, verbose=verbose,
                                          selection_rule=selection_rule,
                                          indep=False, memory=memory,
                                          stop_crit=stop_crit)
                                          for this_seeds in
                                          np.array_split(seeds, n_jobs))

        # reconstruction by averaging
        for jidx in range(len(this_approx)):
            for ridx in range(len(this_approx[jidx])):
                X_denoise += this_approx[jidx][ridx]

    X_denoise /= float(n_runs)
    return X_denoise