OSCR

Noise2Average: An iterative residual learning strategy for image denoising without clean data.

Code ↔ Paper

2 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 2 matches
  1. [1] § Experiments › Neural network implementation ↔ Code/s_N2A_cnnTrainN2A.py, lines 407 to 451 · score 0.65 · Adam optimizer, squared error, loss, epoch, CNNs, training · Report a mismatch
  2. [2] § Experiments › Data denoising › Noise2Average denoising for ultrahigh resolution data ↔ Code/s_N2A_cnnTrainN2A.py, lines 552 to 632 · score 0.52 · fine tuning, image block, predict, CNN, training, epochs · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 667 lines · 29 KB · no license · 2 matches

  1. # s_DtiNet_cnnTrain.py
  2. #
  3. # QT 2019
  4. # %% 在所有导入之前设置环境变量来解决内存碎片化
  5. import os
  6. import sys
  7. import time
  8. # 设置CUDA内存分配器为异步模式,解决内存碎片化
  9. os.environ['TF_GPU_ALLOCATOR'] = 'cuda_malloc_async'
  10. os.environ['TF_ENABLE_GPU_GARBAGE_COLLECTION'] = '1'
  11. os.environ['TF_GPU_THREAD_MODE'] = 'gpu_private'
  12. os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
  13. # 禁用TensorFlow的JIT编译,减少内存使用
  14. os.environ['TF_XLA_FLAGS'] = '--tf_xla_auto_jit=0'
  15. # %% load modules with delay
  16. import time
  17. import argparse
  18. import gc
  19. import scipy.io as sio
  20. import numpy as np
  21. import nibabel as nb
  22. import glob
  23. import tensorflow
  24. print(f"🚀 TensorFlow version: {tensorflow.__version__}")
  25. print("🔧 GPU memory optimization enabled: TF_GPU_ALLOCATOR=cuda_malloc_async")
  26. import munet_res
  27. from tensorflow.keras.optimizers import Adam
  28. from tensorflow.keras.callbacks import ModelCheckpoint
  29. from tensorflow.keras.models import load_model
  30. import qtlib as qtlib
  31. import tensorflow as tf
  32. from tensorflow.keras import backend as K
  33. # %% 添加命令行参数解析
  34. parser = argparse.ArgumentParser(description='Train Noise2Average model')
  35. parser.add_argument('--subject_idx', type=int, default=0,
  36. help='Subject index to train (0-9, default: 0)')
  37. parser.add_argument('--gpu_id', type=str, default='0',
  38. help='GPU ID to use (default: 0)')
  39. parser.add_argument('--gpu_memory_limit', type=int, default=16000,
  40. help='GPU memory limit in MB (default: None for growth mode)')
  41. args = parser.parse_args()
  42. # 设置GPU
  43. os.environ["CUDA_VISIBLE_DEVICES"] = args.gpu_id
  44. # %% 配置TensorFlow GPU显存使用 (适配TensorFlow 2.15.0)
  45. gpus = tf.config.list_physical_devices('GPU')
  46. if gpus:
  47. try:
  48. gpu = gpus[0]
  49. if args.gpu_memory_limit:
  50. # 方法1: 设置显存限制
  51. tf.config.experimental.set_memory_growth(gpu, False)
  52. tf.config.experimental.set_virtual_device_configuration(
  53. gpu,
  54. [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=args.gpu_memory_limit)]
  55. )
  56. print(f"✅ GPU memory limit set to {args.gpu_memory_limit} MB ({args.gpu_memory_limit/1024:.1f} GB)")
  57. else:
  58. # 方法2: 启用内存增长 (按需分配,推荐)
  59. tf.config.experimental.set_memory_growth(gpu, True)
  60. print("✅ GPU memory growth enabled - will allocate as needed")
  61. except RuntimeError as e:
  62. print(f"❌ GPU configuration error: {e}")
  63. print("Note: GPU configuration must be set before any operations")
  64. except Exception as e:
  65. print(f"⚠️ GPU configuration warning: {e}")
  66. print("Continuing with default GPU settings...")
  67. def print_gpu_status():
  68. """详细的GPU状态打印"""
  69. try:
  70. import subprocess
  71. result = subprocess.run(['nvidia-smi', '--query-gpu=name,memory.used,memory.total,memory.free,utilization.gpu,temperature.gpu',
  72. '--format=csv,nounits,noheader'],
  73. capture_output=True, text=True)
  74. if result.returncode == 0:
  75. lines = result.stdout.strip().split('\n')
  76. for i, line in enumerate(lines):
  77. values = [v.strip() for v in line.split(',')]
  78. if len(values) >= 6:
  79. name, mem_used, mem_total, mem_free, gpu_util, temp = values[:6]
  80. mem_used_int = int(mem_used)
  81. mem_total_int = int(mem_total)
  82. mem_free_int = int(mem_free)
  83. usage_percent = (mem_used_int/mem_total_int)*100
  84. print(f"🖥️ GPU {i}: {name}")
  85. print(f" 💾 Memory: {mem_used}/{mem_total}MB ({usage_percent:.1f}%) | Free: {mem_free}MB")
  86. print(f" ⚡ Util: {gpu_util}% | 🌡️ Temp: {temp}°C")
  87. # 内存碎片化和警告检测
  88. if mem_free_int < 2000 and usage_percent < 85:
  89. print(f" ⚠️ Possible memory fragmentation detected!")
  90. elif mem_free_int < 1000:
  91. print(f" 🚨 Low memory warning: {mem_free_int}MB free")
  92. except Exception as e:
  93. print(f"Could not get GPU status: {e}")
  94. def aggressive_memory_cleanup():
  95. """积极的内存清理策略"""
  96. print("🧹 Performing aggressive memory cleanup...")
  97. # 1. 清理Keras/TensorFlow缓存
  98. K.clear_session()
  99. tf.keras.backend.clear_session()
  100. # 2. Python垃圾回收
  101. collected = gc.collect()
  102. print(f" 🗑️ Garbage collected: {collected} objects")
  103. # 3. GPU内存统计重置
  104. if gpus:
  105. try:
  106. tf.config.experimental.reset_memory_stats(gpus[0])
  107. print(" 📊 GPU memory stats reset")
  108. except:
  109. pass
  110. # 4. 创建临时tensor触发内存重新分配(碎片整理)
  111. try:
  112. temp_tensor = tf.random.normal([200, 200])
  113. del temp_tensor
  114. gc.collect()
  115. print(" 🔧 Memory defragmentation performed")
  116. except:
  117. pass
  118. print(" ✅ Aggressive cleanup completed")
  119. def smart_memory_management(current_epoch, total_epochs):
  120. """智能内存管理策略"""
  121. try:
  122. # 获取当前内存使用情况
  123. import subprocess
  124. result = subprocess.run(['nvidia-smi', '--query-gpu=memory.used,memory.free,memory.total',
  125. '--format=csv,nounits,noheader'],
  126. capture_output=True, text=True)
  127. if result.returncode == 0:
  128. mem_used, mem_free, mem_total = result.stdout.strip().split(', ')
  129. mem_used_int = int(mem_used)
  130. mem_free_int = int(mem_free)
  131. mem_total_int = int(mem_total)
  132. usage_percent = (mem_used_int / mem_total_int) * 100
  133. print(f"💾 Memory Status: {mem_used_int}MB used / {mem_total_int}MB total ({usage_percent:.1f}%)")
  134. print(f" 🆓 Available: {mem_free_int}MB")
  135. # 策略1: 如果可用内存少于2GB,执行清理
  136. if mem_free_int < 2048:
  137. print(f"⚠️ Low free memory ({mem_free_int}MB), performing cleanup...")
  138. aggressive_memory_cleanup()
  139. return True
  140. # 策略2: 如果使用率超过85%,执行清理
  141. elif usage_percent > 85:
  142. print(f"⚠️ High memory usage ({usage_percent:.1f}%), performing cleanup...")
  143. aggressive_memory_cleanup()
  144. return True
  145. # 策略3: 每5个epoch进行预防性清理
  146. elif current_epoch > 0 and current_epoch % 5 == 0:
  147. print(f"🔧 Preventive memory cleanup at epoch {current_epoch}...")
  148. aggressive_memory_cleanup()
  149. return True
  150. return False
  151. except Exception as e:
  152. print(f"Memory management error: {e}")
  153. return False
  154. def emergency_memory_recovery():
  155. """紧急内存恢复策略"""
  156. print("🚨 Emergency memory recovery initiated...")
  157. # 1. 最激进的清理
  158. K.clear_session()
  159. tf.keras.backend.clear_session()
  160. # 2. 强制垃圾回收多次
  161. for i in range(3):
  162. collected = gc.collect()
  163. print(f" 🗑️ GC round {i+1}: {collected} objects")
  164. # 3. 重置GPU内存
  165. if gpus:
  166. try:
  167. tf.config.experimental.reset_memory_stats(gpus[0])
  168. # 尝试创建小tensor来触发内存重组
  169. for size in [50, 100, 150]:
  170. try:
  171. temp = tf.random.normal([size, size])
  172. del temp
  173. except:
  174. break
  175. gc.collect()
  176. print(" 🔧 Emergency memory defragmentation completed")
  177. except Exception as e:
  178. print(f" ⚠️ GPU reset failed: {e}")
  179. print(" ✅ Emergency recovery completed")
  180. # %% 优化的显存清理函数
  181. def clear_memory():
  182. """标准内存清理"""
  183. K.clear_session()
  184. tf.keras.backend.clear_session()
  185. gc.collect()
  186. # 如果使用GPU,清理GPU缓存
  187. if gpus:
  188. try:
  189. tf.config.experimental.reset_memory_stats(gpus[0])
  190. except:
  191. pass
  192. print(" 📝 Standard memory cleared")
  193. # %% 日志记录函数
  194. def write_log(log_file, message):
  195. """写入日志信息"""
  196. timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
  197. log_message = f"[{timestamp}] {message}\n"
  198. with open(log_file, 'a', encoding='utf-8') as f:
  199. f.write(log_message)
  200. print(log_message.strip())
  201. # %% set up path
  202. dpRoot = '../Data'
  203. os.chdir(dpRoot)
  204. # %% subjects
  205. subjects = [f'sub_001']
  206. # 检查subject索引是否有效
  207. if args.subject_idx >= len(subjects) or args.subject_idx < 0:
  208. raise ValueError(f"Subject index {args.subject_idx} is out of range. Valid range: 0-{len(subjects)-1}")
  209. print(f"🎯 Training subject {args.subject_idx}: {subjects[args.subject_idx]}")
  210. nbatch = 1
  211. nepoch = 20
  212. niter = 2
  213. shouldtrain = True
  214. expname = f'N2A-mem{args.gpu_memory_limit or "auto"}MB'
  215. # 显示初始GPU状态
  216. print("\n" + "="*60)
  217. print("🚀 INITIAL GPU STATUS")
  218. print("="*60)
  219. print_gpu_status()
  220. # %% load data with memory optimization
  221. if shouldtrain:
  222. for ch in range(0,1):
  223. # 只训练指定的subject
  224. ii = args.subject_idx
  225. sj = os.path.basename(subjects[ii])
  226. print(f"\n🔄 Processing subject: {sj}")
  227. # 创建日志文件路径
  228. dpSub = os.path.join(dpRoot, sj)
  229. dpCnn = os.path.join(dpSub, expname)
  230. if not os.path.exists(dpCnn):
  231. os.makedirs(dpCnn)
  232. print('📁 Created directory')
  233. log_file = os.path.join(dpCnn, f'training_log_ch{ch}.txt')
  234. # 初始化日志
  235. write_log(log_file, f"=== Training Started for {sj} Channel {ch} ===")
  236. write_log(log_file, f"GPU ID: {args.gpu_id}")
  237. write_log(log_file, f"GPU Memory Limit: {args.gpu_memory_limit or 'Auto-growth'}")
  238. write_log(log_file, f"TensorFlow Version: {tensorflow.__version__}")
  239. write_log(log_file, f"Memory Optimization: cuda_malloc_async enabled")
  240. write_log(log_file, f"Block Size: 80x80x80 (Large blocks - high memory usage expected)")
  241. write_log(log_file, f"Batch Size: {nbatch}, Epochs per Iteration: {nepoch}, Total Iterations: {niter}")
  242. # 数据文件路径
  243. fpImg1 = os.path.join(dpSub,'T1_noise1.nii.nii.gz')
  244. fpImg2 = os.path.join(dpSub,'T1_noise2.nii.nii.gz')
  245. fpMask = os.path.join(dpSub,'T1_mask.nii.gz')
  246. print("📂 Loading images...")
  247. write_log(log_file, "Loading data files...")
  248. # 监控内存使用
  249. print("💾 Before loading images:")
  250. print_gpu_status()
  251. img1 = nb.load(fpImg1).get_fdata()
  252. img2 = nb.load(fpImg2).get_fdata()
  253. mask = nb.load(fpMask).get_fdata()
  254. print("💾 After loading images:")
  255. print_gpu_status()
  256. if len(mask.shape)==3:
  257. mask = np.expand_dims(mask, 3)
  258. if len(img1.shape)==3:
  259. img1 = np.expand_dims(img1, 3)
  260. if len(img2.shape)==3:
  261. img2 = np.expand_dims(img2, 3)
  262. write_log(log_file, f"Image shapes: img1={img1.shape}, img2={img2.shape}, mask={mask.shape}")
  263. print(f"📐 Image shapes: img1={img1.shape}, img2={img2.shape}, mask={mask.shape}")
  264. # 使用80x80x80的块大小 - 内存使用量大
  265. ind_block, ind_brain = qtlib.block_ind(mask, sz_block=80, sz_pad=0)
  266. write_log(log_file, f"Block extraction: {len(ind_block)} blocks of size 80x80x80")
  267. print(f"🧮 Block extraction: {len(ind_block)} blocks of size 80x80x80")
  268. # get stats
  269. img1_mean = np.mean(img1[mask > 0.5])
  270. img1_std = np.std(img1[mask > 0.5])
  271. img2_mean = np.mean(img2[mask > 0.5])
  272. img2_std = np.std(img2[mask > 0.5])
  273. write_log(log_file, f"Image statistics - img1: mean={img1_mean:.4f}, std={img1_std:.4f}")
  274. write_log(log_file, f"Image statistics - img2: mean={img2_mean:.4f}, std={img2_std:.4f}")
  275. img1_norm = (img1 - img1_mean) / img1_std
  276. img2_norm = (img2 - img2_mean) / img2_std
  277. print("🧮 Extracting blocks...")
  278. img1_norm_block = qtlib.extract_block(img1_norm * mask, ind_block)
  279. img2_norm_block = qtlib.extract_block(img2_norm * mask, ind_block)
  280. mask_block = qtlib.extract_block(mask, ind_block)
  281. print("💾 After block extraction:")
  282. print_gpu_status()
  283. imgavg = (img1 + img2) / 2.0
  284. for jj in np.arange(0, niter):
  285. iter_start_time = time.time()
  286. print(f"\n🔄 Starting Iteration {jj}")
  287. write_log(log_file, f"=== Starting Iteration {jj} ===")
  288. # 在每次迭代开始时清理显存
  289. print(f"🧹 Before cleanup (Iteration {jj}):")
  290. print_gpu_status()
  291. aggressive_memory_cleanup()
  292. print(f"✅ After cleanup (Iteration {jj}):")
  293. print_gpu_status()
  294. imgavg_block = qtlib.extract_block(imgavg, ind_block)
  295. imgavg1_norm_block = (imgavg_block - img1_mean) / img1_std
  296. imgavg2_norm_block = (imgavg_block - img2_mean) / img2_std
  297. imgres1_block = (imgavg1_norm_block - img1_norm_block)
  298. imgres2_block = (imgavg2_norm_block - img2_norm_block)
  299. imgres1_block = np.concatenate((imgres1_block, mask_block), axis=-1)
  300. imgres2_block = np.concatenate((imgres2_block, mask_block), axis=-1)
  301. # 明确清理临时变量
  302. del imgavg_block, imgavg1_norm_block, imgavg2_norm_block
  303. gc.collect()
  304. img_block_all = 0 # clear
  305. imgres_block_all = 0
  306. mask_block_all = 0
  307. # get block for image1
  308. img_block_all = np.concatenate((img1_norm_block, img2_norm_block), axis=0)
  309. imgres_block_all = np.concatenate((imgres1_block, imgres2_block), axis=0)
  310. mask_block_all = np.concatenate((mask_block, mask_block), axis=0)
  311. # 清理临时变量
  312. del imgres1_block, imgres2_block
  313. gc.collect()
  314. # %% flip left right to augment data
  315. print("🔄 Applying data augmentation...")
  316. tmp = np.flip(img_block_all, 1)
  317. img_block_all = np.concatenate((img_block_all, tmp), axis=0)
  318. del tmp
  319. gc.collect()
  320. tmp = np.flip(imgres_block_all, 1)
  321. imgres_block_all = np.concatenate((imgres_block_all, tmp), axis=0)
  322. del tmp
  323. gc.collect()
  324. tmp = np.flip(mask_block_all, 1)
  325. mask_block_all = np.concatenate((mask_block_all, tmp), axis=0)
  326. del tmp
  327. gc.collect()
  328. write_log(log_file, f"Data preparation completed. Training blocks: {img_block_all.shape[0]}")
  329. print(f"📊 Training data prepared: {img_block_all.shape[0]} blocks")
  330. print("💾 After data augmentation:")
  331. print_gpu_status()
  332. # %% set up model with memory monitoring
  333. dtnet = 0 # clear
  334. fpCp = os.path.join('../PretrainModel', 'superv_lr1e4_ep20.h5')
  335. model_load_start = time.time()
  336. write_log(log_file, 'Loading pretrained model...')
  337. print("🏗️ Before model loading:")
  338. print_gpu_status()
  339. try:
  340. dtnet = load_model(fpCp, custom_objects={'mean_squared_error_weighted': qtlib.mean_squared_error_weighted})
  341. model_load_time = time.time() - model_load_start
  342. write_log(log_file, f'Model loaded successfully in {model_load_time:.2f} seconds')
  343. print(f"✅ Model loaded in {model_load_time:.2f}s")
  344. print("💾 After model loading:")
  345. print_gpu_status()
  346. except Exception as model_error:
  347. write_log(log_file, f'❌ Model loading failed: {model_error}')
  348. print(f"❌ Model loading failed: {model_error}")
  349. raise model_error
  350. # %% set up adam optimizer
  351. adam_opt = Adam(lr=0.00005, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
  352. dtnet.compile(loss = qtlib.mean_squared_error_weighted, optimizer = adam_opt)
  353. # train dtnet with enhanced memory management
  354. for mm in np.arange(0, nepoch):
  355. starttime = time.time()
  356. print(f"📚 Starting Epoch {mm}/{nepoch-1} (Iteration {jj})")
  357. write_log(log_file, f'--- Starting Epoch {mm} of Iteration {jj} ---')
  358. # 智能内存管理
  359. memory_cleaned = smart_memory_management(mm, nepoch)
  360. if memory_cleaned:
  361. print(" 🔧 Memory management performed")
  362. # 每5个epoch显示详细GPU状态
  363. if mm % 5 == 0 or mm == 0:
  364. print(f"📊 Detailed GPU status at epoch {mm}:")
  365. print_gpu_status()
  366. fnCp = 'n2a_iter' + str(jj) + '_lr1e5_ep' + str(mm) + f'_ch{ch}'
  367. fpCp = os.path.join(dpCnn, fnCp + '.h5')
  368. # 使用更节省内存的checkpoint策略
  369. checkpoint = ModelCheckpoint(
  370. fpCp,
  371. monitor='val_loss',
  372. save_best_only=True,
  373. save_weights_only=False, # 保存完整模型以便后续加载
  374. verbose=0
  375. )
  376. write_log(log_file, f'Training data shape: {img_block_all.shape}')
  377. try:
  378. # 训练前小清理
  379. if mm > 0:
  380. gc.collect()
  381. history = dtnet.fit(x = [img_block_all, mask_block_all],
  382. y = imgres_block_all,
  383. batch_size = nbatch,
  384. validation_split=0.2,
  385. epochs = 1,
  386. callbacks = [checkpoint],
  387. verbose = 1,
  388. shuffle = True)
  389. epochtime = time.time() - starttime
  390. # 记录训练时间和损失
  391. train_loss = history.history['loss'][0] if history.history['loss'] else 'N/A'
  392. val_loss = history.history['val_loss'][0] if history.history['val_loss'] else 'N/A'
  393. log_message = f"Iter {jj}, Epoch {mm} completed in {epochtime:.2f}s - Train Loss: {train_loss:.6f}, Val Loss: {val_loss:.6f}"
  394. write_log(log_file, log_message)
  395. print(f"✅ {log_message}")
  396. # save loss
  397. fpLoss = os.path.join(dpCnn, fnCp + '.mat')
  398. sio.savemat(fpLoss, {
  399. 'loss_train':history.history['loss'],
  400. 'loss_val':history.history['val_loss'],
  401. 'memory_optimization': 'cuda_malloc_async',
  402. 'block_size': 80,
  403. 'gpu_memory_limit': args.gpu_memory_limit
  404. })
  405. # 训练后立即清理
  406. del history
  407. gc.collect()
  408. except tf.errors.ResourceExhaustedError as e:
  409. write_log(log_file, f'❌ OOM Error at epoch {mm}: {str(e)}')
  410. print(f"❌ GPU OOM at epoch {mm} - attempting recovery...")
  411. # 紧急内存恢复
  412. emergency_memory_recovery()
  413. print("💾 After emergency recovery:")
  414. print_gpu_status()
  415. # 尝试重新训练该epoch
  416. try:
  417. print("🔄 Retrying training with emergency cleanup...")
  418. history = dtnet.fit(x = [img_block_all, mask_block_all],
  419. y = imgres_block_all,
  420. batch_size = nbatch,
  421. validation_split=0.2,
  422. epochs = 1,
  423. callbacks = [checkpoint],
  424. verbose = 1,
  425. shuffle = True)
  426. print("✅ Emergency retry successful!")
  427. write_log(log_file, f"Emergency retry successful for epoch {mm}")
  428. epochtime = time.time() - starttime
  429. train_loss = history.history['loss'][0] if history.history['loss'] else 'N/A'
  430. val_loss = history.history['val_loss'][0] if history.history['val_loss'] else 'N/A'
  431. log_message = f"Iter {jj}, Epoch {mm} (RETRY) completed in {epochtime:.2f}s - Train Loss: {train_loss:.6f}, Val Loss: {val_loss:.6f}"
  432. write_log(log_file, log_message)
  433. # save loss
  434. fpLoss = os.path.join(dpCnn, fnCp + '.mat')
  435. sio.savemat(fpLoss, {'loss_train':history.history['loss'], 'loss_val':history.history['val_loss']})
  436. del history
  437. gc.collect()
  438. except Exception as retry_error:
  439. write_log(log_file, f'❌ Emergency retry also failed: {retry_error}')
  440. print(f"❌ Emergency retry failed, skipping epoch {mm}")
  441. print("💡 Consider:")
  442. print(" 1. Reducing block size from 80 to 64 or 48")
  443. print(" 2. Increasing GPU memory limit")
  444. print(" 3. Using gradient accumulation")
  445. continue
  446. except Exception as e:
  447. write_log(log_file, f'❌ Training error at epoch {mm}: {e}')
  448. print(f"❌ Training error at epoch {mm}: {e}")
  449. raise e
  450. # 删除旧checkpoint文件
  451. if mm >= 1:
  452. fnCp_old = 'n2a_iter' + str(jj) + '_lr1e5_ep' + str(mm - 1) + f'_ch{ch}'
  453. fpCp_old = os.path.join(dpCnn, fnCp_old + '.h5')
  454. if os.path.exists(fpCp_old):
  455. os.remove(fpCp_old)
  456. write_log(log_file, f"🗑️ Removed old checkpoint: {fnCp_old}.h5")
  457. # %% apply fine tuned munet for evaluation
  458. if (mm==(nepoch-1)) or mm % 5 == 0:
  459. eval_start_time = time.time()
  460. print(f"📊 Starting evaluation for Epoch {mm}...")
  461. write_log(log_file, f"Starting evaluation for Epoch {mm}...")
  462. print(f"💾 Before evaluation (Epoch {mm}):")
  463. print_gpu_status()
  464. dpPred = os.path.join(dpSub, expname,'eval')
  465. if not os.path.exists(dpPred):
  466. os.makedirs(dpPred)
  467. write_log(log_file, '📁 Created evaluation directory')
  468. try:
  469. # Evaluation with memory management - predict in small batches
  470. print(" 🔍 Evaluating image1...")
  471. img_tmp = img1_norm_block
  472. mask_tmp = mask_block
  473. pred_tmp = np.zeros(img_tmp.shape)
  474. num_blocks = img_tmp.shape[0]
  475. for kk in range(num_blocks):
  476. if kk % 5 == 0: # Progress indicator every 5 blocks
  477. print(f" 📊 Block {kk+1}/{num_blocks} ({(kk+1)/num_blocks*100:.1f}%)")
  478. tmp = dtnet.predict([img_tmp[kk:kk+1, :, :, :, :], mask_tmp[kk:kk+1, :, :, :, :]], verbose=0)
  479. pred_tmp[kk:kk+1, :, :, :, :] = tmp[:, :, :, :, 0:1] + img_tmp[kk:kk+1, :, :, :, :]
  480. img1_block_pred = pred_tmp
  481. print(" 🔍 Evaluating image2...")
  482. img_tmp = img2_norm_block
  483. mask_tmp = mask_block
  484. pred_tmp = np.zeros(img_tmp.shape)
  485. for kk in range(num_blocks):
  486. tmp = dtnet.predict([img_tmp[kk:kk+1, :, :, :, :], mask_tmp[kk:kk+1, :, :, :, :]], verbose=0)
  487. pred_tmp[kk:kk+1, :, :, :, :] = tmp[:, :, :, :, 0:1] + img_tmp[kk:kk+1, :, :, :, :]
  488. img2_block_pred = pred_tmp
  489. print(" 🔄 Evaluating flipped image1...")
  490. img_tmp = np.flip(img1_norm_block, 1)
  491. mask_tmp = np.flip(mask_block, 1)
  492. pred_tmp = np.zeros(img_tmp.shape)
  493. for kk in range(num_blocks):
  494. tmp = dtnet.predict([img_tmp[kk:kk+1, :, :, :, :], mask_tmp[kk:kk+1, :, :, :, :]], verbose=0)
  495. pred_tmp[kk:kk+1, :, :, :, :] = tmp[:, :, :, :, 0:1] + img_tmp[kk:kk+1, :, :, :, :]
  496. img1_block_pred_flip = np.flip(pred_tmp, 1)
  497. print(" 🔄 Evaluating flipped image2...")
  498. img_tmp = np.flip(img2_norm_block, 1)
  499. mask_tmp = np.flip(mask_block, 1)
  500. pred_tmp = np.zeros(img_tmp.shape)
  501. for kk in range(num_blocks):
  502. tmp = dtnet.predict([img_tmp[kk:kk+1, :, :, :, :], mask_tmp[kk:kk+1, :, :, :, :]], verbose=0)
  503. pred_tmp[kk:kk+1, :, :, :, :] = tmp[:, :, :, :, 0:1] + img_tmp[kk:kk+1, :, :, :, :]
  504. img2_block_pred_flip = np.flip(pred_tmp, 1)
  505. print(" 🧮 Combining results...")
  506. img1_block_pred = (img1_block_pred + img1_block_pred_flip) / 2.0
  507. img2_block_pred = (img2_block_pred + img2_block_pred_flip) / 2.0
  508. img1_block_pred = (img1_block_pred * img1_std + img1_mean) * mask_block
  509. img2_block_pred = (img2_block_pred * img2_std + img2_mean) * mask_block
  510. img_block_pred = (img1_block_pred + img2_block_pred) / 2.0
  511. imgavg, vol_count = qtlib.block2brain(img_block_pred, ind_block, mask)
  512. fpPred = os.path.join(dpPred, 'n2a_iter' + str(jj) + '_lr1e5_ep' + str(mm) + f'_img_ch{ch}.nii.gz')
  513. qtlib.save_nii(fpPred, imgavg, fpImg1)
  514. eval_time = time.time() - eval_start_time
  515. write_log(log_file, f"✅ Evaluation completed in {eval_time:.2f}s, saved to: {fpPred}")
  516. print(f"✅ Evaluation completed in {eval_time:.2f}s")
  517. # 清理评估变量
  518. del img1_block_pred, img2_block_pred, img1_block_pred_flip, img2_block_pred_flip
  519. del img_block_pred
  520. gc.collect()
  521. except Exception as eval_error:
  522. write_log(log_file, f"❌ Evaluation failed: {eval_error}")
  523. print(f"❌ Evaluation failed: {eval_error}")
  524. if "OOM" in str(eval_error) or "out of memory" in str(eval_error).lower():
  525. print("💡 Evaluation OOM - consider reducing block size or batch evaluation")
  526. print(f"💾 After evaluation (Epoch {mm}):")
  527. print_gpu_status()
  528. # 记录迭代完成时间
  529. iter_time = time.time() - iter_start_time
  530. write_log(log_file, f"=== Iteration {jj} completed in {iter_time:.2f}s ===")
  531. # 清理模型和显存
  532. print("🧹 Cleaning up after iteration...")
  533. del dtnet
  534. aggressive_memory_cleanup()
  535. write_log(log_file, f"Memory cleared after iteration {jj}")
  536. print(f"💾 Final GPU status after Iteration {jj}:")
  537. print_gpu_status()
  538. write_log(log_file, f"=== Training completed for subject {sj} ===")
  539. print(f"\n🎉 Training completed for subject {args.subject_idx}: {subjects[args.subject_idx]}")
  540. print("="*60)
  541. print("🏁 FINAL GPU STATUS")
  542. print("="*60)
  543. print_gpu_status()

s_N2A_cnnTrainN2A.py at commit 93206bc, no license · at the source

Overview

Authors: Zihan Li1, Ziyu Li2, Berkin Bilgic3,4,5, Kui Ying6, David H. Salat3,4, Jonathan R. Polimeni3,4,5, Hongen Liao1, Susie Y. Huang3,4,5, Qiyuan Tian1
  1. School of Biomedical Engineering, Tsinghua University, Beijing, P.R. China
  2. Oxford Centre for Integrative Neuroimaging, FMRIB, Nuffield Department of Clinical Neurosciences, University of Oxford, Oxford, United Kingdom
  3. Athinoula A. Martinos Center for Biomedical Imaging, Department of Radiology, Massachusetts General Hospital, Charlestown, MA, United States
  4. Department of Radiology, Harvard Medical School, Boston, MA, United States
  5. Harvard-MIT Program in Health Sciences and Technology, Massachusetts Institute of Technology, Cambridge, MA, United States
  6. Department of Engineering Physics, Tsinghua University, Beijing, P.R. China
Journal: Imaging neuroscience (Cambridge, Mass.), volume 4, article IMAG.a.1163
Dates: received 31 July 2025; accepted 11 February 2026; published online 24 March 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1162/imag.a.1163 · PMID 41890245 · PMCID PMC13015446 · OpenAlex W7130306792
Open access: diamond, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: structural MRI / diffusion (modality), methods / tools (subfield)
Keywords: magnetic resonance imaging, diffusion tensor imaging, self-supervised learning, transfer learning
Topic: Advanced Neuroimaging Techniques and Applications (Radiology, Nuclear Medicine and Imaging, Medicine), according to OpenAlex
Funding: National Institute on Aging (R21-AG082377, U01-AG052564, K99-AG073506); U.S. Department of Health & Human Services | NIH | NIH Office of the Director (OD) (S10-OD036263); National Institute of Biomedical Imaging and Bioengineering (R01-EB034757, R01-EB032378, UH3-EB034875, R01-EB028797, P41-EB030006); National Natural Science Foundation of China (82302166); Tsinghua University (THU) (20241080026)
Citations: not cited yet (Europe PMC); 89 references in the paper

Abstract

Magnetic resonance imaging (MRI) is a widely adopted non-invasive imaging tool for both clinical diagnosis and neuroscientific research. Nonetheless, the quality of MRI is often hampered by noise. Supervised deep learning-based denoising has proven to outperform conventional methods but requires high-signal-to-noise ratio (SNR) reference data for supervising the training, which considerably reduces its practical feasibility. To address this challenge, we propose a new iterative residual learning strategy entitled “Noise2Average” for denoising MRI data with multiple repetitions, which can be combined with transfer learning for subject-specific self-supervised training. Noise2Average learns to map each noisy repetition to the average of all noisy repetitions by fine-tuning parameters of a pre-trained convolutional neural network (CNN) and recovers higher SNR by averaging all denoised results at the first iteration, and performs this supervised residual learning-based denoising process repeatedly with the denoising results from the previous iteration as the training target for several iterations. The efficacy of Noise2Average is systematically and comprehensively demonstrated on four types of commonly acquired MRI data, including two or more consecutively acquired highly accelerated T1-weighted (T1w) image volumes, two T1w image volumes acquired with different echo times, two diffusion-weighted image (DWI) volumes acquired with opposite phase encoding directions, and two DWI volumes synthesized using different sets of DWI volumes from a diffusion tensor imaging (DTI) scan. Quantitative evaluations show that Noise2Average preserves more image sharpness and textural details and produces more accurate quantitative microstructural metrics from DTI signal modeling than the classic Noise2Noise method and conventional benchmark denoising methods BM4D and AONLM, with denoising performance slightly inferior to that of supervised learning-based denoising method. By reducing the requirement for training data and scan time, Noise2Average substantially increases the feasibility and accessibility of deep learning-based denoising methods for MRI and potentially benefits a wider range of clinical and neuroscientific studies.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 2 matches between paragraphs and lines of code.

webpages.tuni.fi/foi/gcf-bm3d

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: the link answers
Software Heritage: not checked
Found in: “Data and Code Availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 30 September 2026: the link answers (HTTP 200)
  • 30 September 2026: the link answers (HTTP 200)

birthlab/Noise2Average

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 93206bcdd77309f7b592daf2f700fb5ec27c1bfb, 22 November 2025
Languages: Python (4)
Size: 13 files, 4 scripts
Software Heritage: not archived
Found in: “Data and Code Availability”
Holds: README, environment (requirements.txt)
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: Keras (4 files), TensorFlow (4 files), NiBabel (2 files), NumPy (2 files), SciPy (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
5 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 4 scripts, each with its path and the digest of its content;
  • 2 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data and Code Availability

T1w and diffusion MRI data from the Human Connectome Project, WU-Minn-Ox Consortium (sections 3.1.4 and 3.2.1) and MGH-USC Consortium (section 3.2.2), are publicly available (https://www.humanconnectome.org). Diffusion MRI data from the Lifespan Human Connectome Project in Aging (section 3.1.3) are publicly available (https://www.humanconnectome.org/study/hcp-lifespan-aging). OVGU 7T T1w data (section 3.1.5) are publicly available (http://open-science.ub.ovgu.de/xmlui/handle/684882692/61). MGH gSlider-SMS diffusion MRI data (section 3.1.6) are publicly available (https://datadryad.org/stash/dataset/doi:10.5061/dryad.nzs7h44q2 and https://datadryad.org/stash/dataset/doi:10.5061/dryad.rjdfn2z8g). MGH T1w Wave-MPRAGE data (section 3.1.1) and ME-MPRAGE data (section 3.1.2) are available from the corresponding author upon reasonable request. Comprehensive diffusion MRI dataset (CDMD) is publicly available (https://doi.org/10.6084/m9.figshare.16910290) (section 3.1.7). The source codes of BM4D are publicly available (https://webpages.tuni.fi/foi/GCF-BM3D/). Details of the MATLAB-based AONLM software package are available at https://www.nitrc.org/projects/mri-denoising. The package is accessible by contacting either the corresponding author or the developers (Manjón et al., 2010). The source codes of Noise2Noise and Noise2Average implemented using Keras application programming interface is publicly available (https://github.com/birthlab/Noise2Average).

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 9 authors, 4 keywords, 5 funders, 86 references.

Cite

This paper

Li, Z., Li, Z., Bilgic, B., Ying, K., Salat, D. H., Polimeni, J. R., Liao, H., Huang, S. Y., & Tian, Q. (2026). Noise2Average: An iterative residual learning strategy for image denoising without clean data. Imaging neuroscience (Cambridge, Mass.), 4, IMAG.a.1163. https://doi.org/10.1162/imag.a.1163

BibTeX

@article{li2026noise2average,
author = {Li, Zihan and Li, Ziyu and Bilgic, Berkin and Ying, Kui and Salat, David H. and Polimeni, Jonathan R. and Liao, Hongen and Huang, Susie Y. and Tian, Qiyuan},
title = {{Noise2Average: An iterative residual learning strategy for image denoising without clean data}},
journal = {Imaging neuroscience (Cambridge, Mass.)},
year = {2026},
month = mar,
volume = {4},
pages = {IMAG.a.1163},
publisher = {MIT Press},
issn = {2837-6056},
doi = {10.1162/imag.a.1163},
url = {https://doi.org/10.1162/imag.a.1163},
pmid = {41890245},
pmcid = {PMC13015446}
}

RIS

TY - JOUR
AU - Li, Zihan
AU - Li, Ziyu
AU - Bilgic, Berkin
AU - Ying, Kui
AU - Salat, David H.
AU - Polimeni, Jonathan R.
AU - Liao, Hongen
AU - Huang, Susie Y.
AU - Tian, Qiyuan
TI - Noise2Average: An iterative residual learning strategy for image denoising without clean data
T2 - Imaging neuroscience (Cambridge, Mass.)
J2 - Imaging Neurosci (Camb)
PY - 2026
DA - 2026/03/24
VL - 4
SP - IMAG.a.1163
SN - 2837-6056
PB - MIT Press
DO - 10.1162/imag.a.1163
UR - https://doi.org/10.1162/imag.a.1163
LA - en
ER -

CSL-JSON

{
"id": "10.1162/imag.a.1163",
"type": "article-journal",
"title": "Noise2Average: An iterative residual learning strategy for image denoising without clean data",
"container-title": "Imaging neuroscience (Cambridge, Mass.)",
"author": [
{
"family": "Li",
"given": "Zihan"
},
{
"family": "Li",
"given": "Ziyu"
},
{
"family": "Bilgic",
"given": "Berkin"
},
{
"family": "Ying",
"given": "Kui"
},
{
"family": "Salat",
"given": "David H."
},
{
"family": "Polimeni",
"given": "Jonathan R."
},
{
"family": "Liao",
"given": "Hongen"
},
{
"family": "Huang",
"given": "Susie Y."
},
{
"family": "Tian",
"given": "Qiyuan"
}
],
"container-title-short": "Imaging Neurosci (Camb)",
"volume": "4",
"page": "IMAG.a.1163",
"DOI": "10.1162/imag.a.1163",
"PMID": "41890245",
"PMCID": "PMC13015446",
"ISSN": "2837-6056",
"publisher": "MIT Press",
"URL": "https://doi.org/10.1162/imag.a.1163",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
24
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1002/advs.202307965 [code]
DIMOND: DIffusion Model OptimizatioN with Deep Learning.
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)
In common: NiBabel, SciPy, NumPy, structural MRI / diffusion, 10 references, 2 authors
[2] doi:10.1162/imag.a.1089 [code]
Effects of diffusion MRI spatial resolution on human brain short-range association fiber reconstruction and structural connectivity estimation.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: NiBabel, SciPy, NumPy, structural MRI / diffusion, 8 references, 2 authors
[3] doi:
Submillimeter diffusion MRI using an in-plane segmented 3D multi-slab acquisition and denoiser-regularized reconstruction
Journal: Medical image analysis
In common: structural MRI / diffusion, 13 references
[4] doi:10.1038/s41551-025-01457-x [code]
Ultra-high gradient connectomics and microstructure MRI scanner for imaging of human brain circuits across scales.
Journal: Nature biomedical engineering
In common: structural MRI / diffusion, 10 references, author Susie Y. Huang
[5] doi:10.1038/s41597-025-05640-2 [code]
Diff5T: Benchmarking human brain diffusion MRI with an extensive 5.0 Tesla k-space and spatial dataset.
Journal: Scientific data
In common: NiBabel, SciPy, NumPy, methods / tools, structural MRI / diffusion, 13 references
[6] doi:10.1038/s41586-025-09708-2 [code]
A probabilistic histological atlas of the human brain for MRI segmentation.
Journal: Nature
In common: Keras, TensorFlow, NiBabel, 2 other tools, humanconnectome.org/study/hcp-lifespan-aging, methods / tools, structural MRI / diffusion, 5 references
[7] doi:10.1162/imag_a_00060 [code]
Denoising diffusion MRI: Considerations and implications for analysis.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: NiBabel, SciPy, NumPy, methods / tools, structural MRI / diffusion, 10 references
[8] doi:10.1177/0271678x251325399 [code]
Sex-specific effects of intensity and dose of physical activity on BOLD-fMRI cerebrovascular reactivity and cerebral pulsatility.
Journal: Journal of cerebral blood flow and metabolism : official journal of the International Society of Cerebral Blood Flow and Metabolism
In common: Keras, TensorFlow, NiBabel, 2 other tools, humanconnectome.org/study/hcp-lifespan-aging, 6 references
[9] doi:10.1038/s41597-025-05350-9 [code]
In vivo submillimeter diffusion MRI dataset of 9 macaque brains curated for tractography.
Journal: Scientific data
In common: Keras, TensorFlow, NiBabel, 2 other tools, methods / tools, structural MRI / diffusion, 6 references
[10] doi:10.1016/j.neuroimage.2023.120168 [code]
High-fidelity mesoscale in-vivo diffusion MRI through gSlider-BUDA and circular EPI with S-LORAKS reconstruction.
Journal: NeuroImage
In common: structural MRI / diffusion, 10 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1162/imag.a.1163.svg)](https://openscicode.org/badge/doi/10.1162/imag.a.1163)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1162/imag.a.1163.svg
   :target: https://openscicode.org/badge/doi/10.1162/imag.a.1163
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1162/imag.a.1163"><img src="https://openscicode.org/badge/doi/10.1162/imag.a.1163.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.