hicres.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import cooler
  2. import pandas as pd
  3. import numpy as np
  4. import argparse
  5. from concurrent.futures import ThreadPoolExecutor, as_completed
  6. def calculate_valid_bins_percentage(file_path, threshold=1000, num_threads=1, chunk_size=100000, resolutions=None):
  7. if file_path.endswith('.mcool'):
  8. return calculate_mcool_valid_bins_percentage(file_path, threshold, num_threads, chunk_size, resolutions)
  9. else:
  10. return calculate_cool_valid_bins_percentage(file_path, threshold, chunk_size)
  11. def calculate_cool_valid_bins_percentage(file_path, threshold=1000, chunk_size=100000):
  12. clr = cooler.Cooler(file_path)
  13. return {
  14. 'File': file_path,
  15. **calculate_bin_statistics(clr, threshold, chunk_size)
  16. }
  17. def iter_bin_contact_sums(clr, chunk_size=100000):
  18. """Yield raw contact coverage, counting each matrix row exactly once.
  19. Cooler reconstructs symmetric-upper storage automatically. Read every
  20. column, including contacts outside the row batch, and count the diagonal
  21. once. Bound internal batches to keep sparse selections manageable.
  22. """
  23. if chunk_size <= 0:
  24. raise ValueError('chunk_size must be positive')
  25. row_batch_size = min(chunk_size, 4096)
  26. total_bins = clr.info['nbins']
  27. selector = clr.matrix(balance=False, sparse=True)
  28. for start in range(0, total_bins, row_batch_size):
  29. end = min(start + row_batch_size, total_bins)
  30. matrix = selector[start:end, :]
  31. # Accumulate fractional input counts in float64, not float32.
  32. yield np.asarray(matrix.sum(axis=1, dtype=np.float64)).ravel()
  33. def calculate_bin_statistics(clr, threshold=1000, chunk_size=100000):
  34. total_bins = clr.info['nbins']
  35. total_valid_bins = sum(
  36. int(np.count_nonzero(bin_sums >= threshold))
  37. for bin_sums in iter_bin_contact_sums(clr, chunk_size)
  38. )
  39. return {
  40. 'Valid Bin Percentage': (total_valid_bins / total_bins) * 100 if total_bins else 0,
  41. 'Total Valid Bins': total_valid_bins,
  42. 'Total Bins': total_bins
  43. }
  44. def calculate_mcool_valid_bins_percentage(file_path, threshold=1000, num_threads=1, chunk_size=100000, resolutions=None):
  45. available_resolutions = cooler.fileops.list_coolers(file_path)
  46. print(f"Available resolutions in {file_path}: {available_resolutions}")
  47. if resolutions:
  48. resolutions = [f'/resolutions/{res}' for res in resolutions.split(',') if f'/resolutions/{res}' in available_resolutions]
  49. print(resolutions)
  50. if not resolutions:
  51. raise ValueError(f"No matching resolutions found in the .mcool file for specified resolutions.")
  52. else:
  53. resolutions = available_resolutions
  54. results = []
  55. with ThreadPoolExecutor(max_workers=num_threads) as executor:
  56. future_to_resolution = {
  57. executor.submit(process_resolution, file_path, resolution_path, threshold, chunk_size): resolution_path
  58. for resolution_path in resolutions
  59. }
  60. for future in as_completed(future_to_resolution):
  61. resolution_path = future_to_resolution[future]
  62. try:
  63. result = future.result()
  64. if result is not None:
  65. results.append(result)
  66. except Exception as e:
  67. print(f"Error processing resolution {resolution_path}: {e}")
  68. return {
  69. 'File': file_path,
  70. 'Results': results
  71. }
  72. def process_resolution(file_path, resolution_path, threshold, chunk_size):
  73. clr_res = cooler.Cooler(f"{file_path}::{resolution_path}")
  74. print(f"Processing resolution: {resolution_path}")
  75. return {
  76. 'Resolution': resolution_path.split('/')[-1],
  77. **calculate_bin_statistics(clr_res, threshold, chunk_size)
  78. }
  79. def save_results_to_csv(results, output_path):
  80. if 'Results' in results:
  81. df = pd.DataFrame(results['Results'])
  82. df.insert(0, 'File', results['File'])
  83. else:
  84. df = pd.DataFrame([results])
  85. df.to_csv(output_path, index=False)
  86. print(f"Results saved to {output_path}")
  87. def main():
  88. parser = argparse.ArgumentParser(description="Calculate valid bins percentage from .cool or .mcool files.")
  89. parser.add_argument("file_path", type=str, help="Path to the .cool or .mcool file")
  90. parser.add_argument("--output", type=str, required=True, help="Path to save the results as CSV file")
  91. parser.add_argument("--threads", type=int, default=1, help="Number of threads to use for processing")
  92. parser.add_argument("--chunk_size", type=int, default=100000, help="Maximum rows per sparse batch (internally capped at 4096; does not affect counts)")
  93. parser.add_argument("--resolutions", type=str, help="Comma-separated list of resolutions to process in the .mcool file")
  94. args = parser.parse_args()
  95. try:
  96. results = calculate_valid_bins_percentage(args.file_path, num_threads=args.threads, chunk_size=args.chunk_size, resolutions=args.resolutions)
  97. save_results_to_csv(results, args.output)
  98. except Exception as e:
  99. print(f"Error processing file {args.file_path}: {e}")
  100. if __name__ == "__main__":
  101. main()