| |
| |
| |
| |
| |
|
|
| import numpy as np |
| from scipy.ndimage import correlate |
| from scipy.stats import poisson |
|
|
| def donut_kernel(R1, R2): |
|
|
| kernel = np.ones((R1*2+1, R1*2+1)) |
| center = (R1, R1) |
| |
| kernel[center[0] - R2 : center[0]+R2+1, center[1] - R2 : center[1]+R2+1] = 0 |
| kernel[center[0], :] = 0 |
| kernel[:, center[1]] = 0 |
|
|
| return kernel |
|
|
|
|
| def lowerleft_kernel(R1, R2): |
|
|
| kernel = np.ones((R1*2+1, R1*2+1)) |
| center = (R1, R1) |
| |
| kernel[center[0] - R2 : center[0]+R2+1, center[1] - R2 : center[1]+R2+1] = 0 |
| kernel[:center[0]+1, :] = 0 |
| kernel[:, center[1]:] = 0 |
|
|
| return kernel |
|
|
|
|
| def horizontal_kernel(R1, R2): |
| |
| kernel = np.zeros((3, R1*2+1)) |
| center = (1, R1) |
|
|
| kernel[ : , : center[1] - R2 ] = 1 |
| kernel[ : , center[1] + R2 + 1 : ] = 1 |
| |
| return kernel |
|
|
| def vertical_kernel(R1, R2): |
|
|
| kernel = np.zeros((R1*2+1, 3)) |
| center = (R1, 1) |
|
|
| kernel[ : center[0] - R2, : ] = 1 |
| kernel[ center[0] + R2 + 1 : , : ] = 1 |
|
|
| return kernel |
|
|
|
|
| class HiCCUPS: |
| def __init__(self, |
| donut_size = 5, |
| peak_size = 2, |
| lambda_step = 2**(1/3), |
| distance_lower_bound = 0, |
| distance_upper_bound = 100000, |
| ratio_cutoff = [1.75/0.8, 1.75/0.8, 1.5/0.8, 1.5/0.8, 2/0.8], |
| eps = 1e-3) -> None: |
| self.donut_size = donut_size |
| self.peak_size = peak_size |
| self.lambda_step = lambda_step |
| self.distance_lower_bound = distance_lower_bound |
| self.distance_upper_bound = distance_upper_bound |
| self.ratio_cutoff = ratio_cutoff |
| self.eps = eps |
| def __call__(self, data): |
| full_matrix = data['hic'] |
| full_norm = data['norm'] |
| kernels = [donut_kernel(self.donut_size, self.peak_size), |
| lowerleft_kernel(self.donut_size, self.peak_size), |
| horizontal_kernel(self.donut_size, self.peak_size), |
| vertical_kernel(self.donut_size, self.peak_size)] |
| |
| l = full_matrix.shape[0] |
|
|
| B = min(self.distance_upper_bound, l) |
| window_size = min(2*B, l) |
|
|
| expect_vector = [] |
| for d in range(l): |
| expect_vector.append(np.trace(full_matrix, d)/(l-d)) |
|
|
| upper_triangle = np.triu(np.ones((window_size, window_size)), 0) |
| |
| expect = np.zeros((window_size, window_size)) |
| for i in range(window_size): |
| for j in range(window_size): |
| if abs(i-j) < len(expect_vector): |
| expect[i][j] = expect_vector[abs(i-j)] |
| |
| esums = [] |
| for kernel in kernels: |
| esum = correlate(expect, kernel, mode='constant') + self.eps |
| esums.append(esum) |
| |
| qvalues = np.tile(np.ones_like(full_matrix), (len(kernels), 1,1)).astype(float) |
| ratios = np.zeros_like(qvalues).astype(float) |
|
|
| for s0 in range(0, l, B): |
| s = min(s0, l-window_size) |
| matrix = full_matrix[s:s+window_size, s:s+window_size] |
| norm = full_norm [s:s+window_size] |
|
|
| norm_mat = np.outer(norm, norm) |
|
|
| observed = matrix * norm_mat |
|
|
| observed = (np.rint(observed)).astype(int) |
|
|
| log_lambda_step = np.log(self.lambda_step) |
|
|
| for kid, kernel in enumerate(kernels): |
| msum = correlate(matrix, kernel, mode='constant') |
| esum = esums[kid] |
|
|
| Ek = msum/esum*expect |
| Ek = Ek * norm_mat + self.eps |
|
|
| |
| |
| |
|
|
| ratios[kid, s:s+window_size, s:s+window_size] = observed/Ek |
| |
| |
|
|
| logEk = np.log(Ek) |
|
|
| bin_id = np.ceil(np.maximum(0, logEk)/log_lambda_step).astype(int) |
| pvalues = poisson.sf(observed, np.exp(bin_id*log_lambda_step)) |
|
|
| max_bin = bin_id.max()+1 |
| |
| for id in range(max_bin): |
| bin_pos = np.where((bin_id == id) & (upper_triangle == 1)) |
| p = pvalues[bin_pos] |
|
|
| bin = sorted(zip(p.tolist(), bin_pos[0].tolist(), bin_pos[1].tolist())) |
| size = len(bin) |
|
|
| qvalue = 1 |
| for rank in range(len(bin), 0, -1): |
| pvalue, i, j = bin[rank-1] |
| qvalue = min(qvalue, pvalue /(rank / size)) |
|
|
| qvalues[kid, i+s, j+s] = qvalues[kid, j+s, i+s] = qvalue |
| |
| extra_ratio = np.maximum(ratios[0], ratios[1]) |
| extra_ratio = np.expand_dims(extra_ratio, 0) |
| ratios = np.concatenate([ratios, extra_ratio], axis = 0) |
| for kid, cutoff in enumerate(self.ratio_cutoff): |
| ratios[kid] = np.minimum(ratios[kid], cutoff) |
| ratios[kid] /= cutoff |
| max_qvalue = np.max(qvalues, axis=0, keepdims=True) |
| min_ratio = np.min(ratios, axis=0, keepdims=True) |
| out = np.concatenate([max_qvalue, min_ratio], axis = 0) |
| return out |
|
|