OSCR

Memory efficient training for 3D brain image registration networks using PatchMorph.

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] § Method › PatchMorph › Baseline CNN architecture ↔ NICE-Net/ext/neuron/models.py, lines 805 to 952 · score 0.61 · batch norm, ReLu, CNN, convolution, dense, stride · Report a mismatch
  2. [2] § Experiments › PatchMorph architecture › PatchMorph training ↔ libs/codes.py, lines 898 to 1025 · score 0.54 · AdamW, NCC loss, PyTorch, optimizer, batch, training · 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 · 1,009 lines · 36 KB · MIT · 1 match

  1. """
  2. tensorflow/keras utilities for the neuron project
  3. If you use this code, please cite
  4. Dalca AV, Guttag J, Sabuncu MR
  5. Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation,
  6. CVPR 2018
  7. Contact: adalca [at] csail [dot] mit [dot] edu
  8. License: GPLv3
  9. """
  10. import sys
  11. from . import layers
  12. # third party
  13. import numpy as np
  14. import tensorflow as tf
  15. import keras
  16. import keras.layers as KL
  17. from keras.models import Model
  18. import keras.backend as K
  19. from keras.constraints import maxnorm
  20. def dilation_net(nb_features,
  21. input_shape, # input layer shape, vector of size ndims + 1(nb_channels)
  22. nb_levels,
  23. conv_size,
  24. nb_labels,
  25. name='dilation_net',
  26. prefix=None,
  27. feat_mult=1,
  28. pool_size=2,
  29. use_logp=True,
  30. padding='same',
  31. dilation_rate_mult=1,
  32. activation='elu',
  33. use_residuals=False,
  34. final_pred_activation='softmax',
  35. nb_conv_per_level=1,
  36. add_prior_layer=False,
  37. add_prior_layer_reg=0,
  38. layer_nb_feats=None,
  39. batch_norm=None):
  40. return unet(nb_features,
  41. input_shape, # input layer shape, vector of size ndims + 1(nb_channels)
  42. nb_levels,
  43. conv_size,
  44. nb_labels,
  45. name='unet',
  46. prefix=None,
  47. feat_mult=1,
  48. pool_size=2,
  49. use_logp=True,
  50. padding='same',
  51. activation='elu',
  52. use_residuals=False,
  53. dilation_rate_mult=dilation_rate_mult,
  54. final_pred_activation='softmax',
  55. nb_conv_per_level=1,
  56. add_prior_layer=False,
  57. add_prior_layer_reg=0,
  58. layer_nb_feats=None,
  59. batch_norm=None)
  60. def unet(nb_features,
  61. input_shape, # input layer shape, vector of size ndims + 1(nb_channels)
  62. nb_levels,
  63. conv_size,
  64. nb_labels,
  65. name='unet',
  66. prefix=None,
  67. feat_mult=1,
  68. pool_size=2,
  69. use_logp=True,
  70. padding='same',
  71. dilation_rate_mult=1,
  72. activation='elu',
  73. use_residuals=False,
  74. final_pred_activation='softmax',
  75. nb_conv_per_level=1,
  76. add_prior_layer=False,
  77. add_prior_layer_reg=0,
  78. layer_nb_feats=None,
  79. conv_dropout=0,
  80. batch_norm=None):
  81. """
  82. unet-style model with an overdose of parametrization
  83. for U-net like architecture, we need to use Deconvolution3D.
  84. However, this is not yet available (maybe soon, it's on a dev branch in github I believe)
  85. Until then, we'll upsample and convolve.
  86. TODO: Need to check that UpSampling3D actually does NN-upsampling!
  87. """
  88. # naming
  89. model_name = name
  90. if prefix is None:
  91. prefix = model_name
  92. # volume size data
  93. ndims = len(input_shape) - 1
  94. if isinstance(pool_size, int):
  95. pool_size = (pool_size,) * ndims
  96. # get encoding model
  97. enc_model = conv_enc(nb_features,
  98. input_shape,
  99. nb_levels,
  100. conv_size,
  101. name=model_name,
  102. prefix=prefix,
  103. feat_mult=feat_mult,
  104. pool_size=pool_size,
  105. padding=padding,
  106. dilation_rate_mult=dilation_rate_mult,
  107. activation=activation,
  108. use_residuals=use_residuals,
  109. nb_conv_per_level=nb_conv_per_level,
  110. layer_nb_feats=layer_nb_feats,
  111. conv_dropout=conv_dropout,
  112. batch_norm=batch_norm)
  113. # get decoder
  114. # use_skip_connections=1 makes it a u-net
  115. lnf = layer_nb_feats[(nb_levels * nb_conv_per_level):] if layer_nb_feats is not None else None
  116. dec_model = conv_dec(nb_features,
  117. None,
  118. nb_levels,
  119. conv_size,
  120. nb_labels,
  121. name=model_name,
  122. prefix=prefix,
  123. feat_mult=feat_mult,
  124. pool_size=pool_size,
  125. use_skip_connections=1,
  126. padding=padding,
  127. dilation_rate_mult=dilation_rate_mult,
  128. activation=activation,
  129. use_residuals=use_residuals,
  130. final_pred_activation='linear' if add_prior_layer else final_pred_activation,
  131. nb_conv_per_level=nb_conv_per_level,
  132. batch_norm=batch_norm,
  133. layer_nb_feats=lnf,
  134. conv_dropout=conv_dropout,
  135. input_model=enc_model)
  136. final_model = dec_model
  137. if add_prior_layer:
  138. final_model = add_prior(dec_model,
  139. [*input_shape[:-1], nb_labels],
  140. name=model_name + '_prior',
  141. use_logp=use_logp,
  142. final_pred_activation=final_pred_activation,
  143. add_prior_layer_reg=add_prior_layer_reg)
  144. return final_model
  145. def ae(nb_features,
  146. input_shape,
  147. nb_levels,
  148. conv_size,
  149. nb_labels,
  150. enc_size,
  151. name='ae',
  152. prefix=None,
  153. feat_mult=1,
  154. pool_size=2,
  155. padding='same',
  156. activation='elu',
  157. use_residuals=False,
  158. nb_conv_per_level=1,
  159. batch_norm=None,
  160. enc_batch_norm=None,
  161. ae_type='conv', # 'dense', or 'conv'
  162. enc_lambda_layers=None,
  163. add_prior_layer=False,
  164. add_prior_layer_reg=0,
  165. use_logp=True,
  166. conv_dropout=0,
  167. include_mu_shift_layer=False,
  168. single_model=False, # whether to return a single model, or a tuple of models that can be stacked.
  169. final_pred_activation='softmax',
  170. do_vae=False):
  171. """
  172. Convolutional Auto-Encoder.
  173. Optionally Variational.
  174. Optionally Dense middle layer
  175. "Mostly" in that the inner encoding can be (optionally) constructed via dense features.
  176. Parameters:
  177. do_vae (bool): whether to do a variational auto-encoder or not.
  178. enc_lambda_layers functions to try:
  179. K.softsign
  180. a = 1
  181. longtanh = lambda x: K.tanh(x) * K.log(2 + a * abs(x))
  182. """
  183. # naming
  184. model_name = name
  185. # volume size data
  186. ndims = len(input_shape) - 1
  187. if isinstance(pool_size, int):
  188. pool_size = (pool_size,) * ndims
  189. # get encoding model
  190. enc_model = conv_enc(nb_features,
  191. input_shape,
  192. nb_levels,
  193. conv_size,
  194. name=model_name,
  195. feat_mult=feat_mult,
  196. pool_size=pool_size,
  197. padding=padding,
  198. activation=activation,
  199. use_residuals=use_residuals,
  200. nb_conv_per_level=nb_conv_per_level,
  201. conv_dropout=conv_dropout,
  202. batch_norm=batch_norm)
  203. # middle AE structure
  204. if single_model:
  205. in_input_shape = None
  206. in_model = enc_model
  207. else:
  208. in_input_shape = enc_model.output.shape.as_list()[1:]
  209. in_model = None
  210. mid_ae_model = single_ae(enc_size,
  211. in_input_shape,
  212. conv_size=conv_size,
  213. name=model_name,
  214. ae_type=ae_type,
  215. input_model=in_model,
  216. batch_norm=enc_batch_norm,
  217. enc_lambda_layers=enc_lambda_layers,
  218. include_mu_shift_layer=include_mu_shift_layer,
  219. do_vae=do_vae)
  220. # decoder
  221. if single_model:
  222. in_input_shape = None
  223. in_model = mid_ae_model
  224. else:
  225. in_input_shape = mid_ae_model.output.shape.as_list()[1:]
  226. in_model = None
  227. dec_model = conv_dec(nb_features,
  228. in_input_shape,
  229. nb_levels,
  230. conv_size,
  231. nb_labels,
  232. name=model_name,
  233. feat_mult=feat_mult,
  234. pool_size=pool_size,
  235. use_skip_connections=False,
  236. padding=padding,
  237. activation=activation,
  238. use_residuals=use_residuals,
  239. final_pred_activation='linear',
  240. nb_conv_per_level=nb_conv_per_level,
  241. batch_norm=batch_norm,
  242. conv_dropout=conv_dropout,
  243. input_model=in_model)
  244. if add_prior_layer:
  245. dec_model = add_prior(dec_model,
  246. [*input_shape[:-1],nb_labels],
  247. name=model_name,
  248. prefix=model_name + '_prior',
  249. use_logp=use_logp,
  250. final_pred_activation=final_pred_activation,
  251. add_prior_layer_reg=add_prior_layer_reg)
  252. if single_model:
  253. return dec_model
  254. else:
  255. return (dec_model, mid_ae_model, enc_model)
  256. def conv_enc(nb_features,
  257. input_shape,
  258. nb_levels,
  259. conv_size,
  260. name=None,
  261. prefix=None,
  262. feat_mult=1,
  263. pool_size=2,
  264. dilation_rate_mult=1,
  265. padding='same',
  266. activation='elu',
  267. layer_nb_feats=None,
  268. use_residuals=False,
  269. nb_conv_per_level=2,
  270. conv_dropout=0,
  271. batch_norm=None):
  272. """
  273. Fully Convolutional Encoder
  274. """
  275. # naming
  276. model_name = name
  277. if prefix is None:
  278. prefix = model_name
  279. # volume size data
  280. ndims = len(input_shape) - 1
  281. input_shape = tuple(input_shape)
  282. if isinstance(pool_size, int):
  283. pool_size = (pool_size,) * ndims
  284. # prepare layers
  285. convL = getattr(KL, 'Conv%dD' % ndims)
  286. conv_kwargs = {'padding': padding, 'activation': activation}
  287. maxpool = getattr(KL, 'MaxPooling%dD' % ndims)
  288. # first layer: input
  289. name = '%s_input' % prefix
  290. last_tensor = KL.Input(shape=input_shape, name=name)
  291. input_tensor = last_tensor
  292. # down arm:
  293. # add nb_levels of conv + ReLu + conv + ReLu. Pool after each of first nb_levels - 1 layers
  294. lfidx = 0
  295. for level in range(nb_levels):
  296. lvl_first_tensor = last_tensor
  297. nb_lvl_feats = np.round(nb_features*feat_mult**level).astype(int)
  298. conv_kwargs['dilation_rate'] = dilation_rate_mult**level
  299. for conv in range(nb_conv_per_level):
  300. if layer_nb_feats is not None:
  301. nb_lvl_feats = layer_nb_feats[lfidx]
  302. lfidx += 1
  303. name = '%s_conv_downarm_%d_%d' % (prefix, level, conv)
  304. if conv < (nb_conv_per_level-1) or (not use_residuals):
  305. last_tensor = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(last_tensor)
  306. else: # no activation
  307. last_tensor = convL(nb_lvl_feats, conv_size, padding=padding, name=name)(last_tensor)
  308. if conv_dropout > 0:
  309. # conv dropout along feature space only
  310. name = '%s_dropout_downarm_%d_%d' % (prefix, level, conv)
  311. noise_shape = [None, *[1]*ndims, nb_lvl_feats]
  312. last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor)
  313. if use_residuals:
  314. convarm_layer = last_tensor
  315. # the "add" layer is the original input
  316. # However, it may not have the right number of features to be added
  317. nb_feats_in = lvl_first_tensor.get_shape()[-1]
  318. nb_feats_out = convarm_layer.get_shape()[-1]
  319. add_layer = lvl_first_tensor
  320. if nb_feats_in > 1 and nb_feats_out > 1 and (nb_feats_in != nb_feats_out):
  321. name = '%s_expand_down_merge_%d' % (prefix, level)
  322. last_tensor = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(lvl_first_tensor)
  323. add_layer = last_tensor
  324. if conv_dropout > 0:
  325. name = '%s_dropout_down_merge_%d_%d' % (prefix, level, conv)
  326. noise_shape = [None, *[1]*ndims, nb_lvl_feats]
  327. last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor)
  328. name = '%s_res_down_merge_%d' % (prefix, level)
  329. last_tensor = KL.add([add_layer, convarm_layer], name=name)
  330. name = '%s_res_down_merge_act_%d' % (prefix, level)
  331. last_tensor = KL.Activation(activation, name=name)(last_tensor)
  332. if batch_norm is not None:
  333. name = '%s_bn_down_%d' % (prefix, level)
  334. last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
  335. # max pool if we're not at the last level
  336. if level < (nb_levels - 1):
  337. name = '%s_maxpool_%d' % (prefix, level)
  338. last_tensor = maxpool(pool_size=pool_size, name=name, padding=padding)(last_tensor)
  339. # create the model and return
  340. model = Model(inputs=input_tensor, outputs=[last_tensor], name=model_name)
  341. return model
  342. def conv_dec(nb_features,
  343. input_shape,
  344. nb_levels,
  345. conv_size,
  346. nb_labels,
  347. name=None,
  348. prefix=None,
  349. feat_mult=1,
  350. pool_size=2,
  351. use_skip_connections=False,
  352. padding='same',
  353. dilation_rate_mult=1,
  354. activation='elu',
  355. use_residuals=False,
  356. final_pred_activation='softmax',
  357. nb_conv_per_level=2,
  358. layer_nb_feats=None,
  359. batch_norm=None,
  360. conv_dropout=0,
  361. input_model=None):
  362. """
  363. Fully Convolutional Decoder
  364. Parameters:
  365. ...
  366. use_skip_connections (bool): if true, turns an Enc-Dec to a U-Net.
  367. If true, input_tensor and tensors are required.
  368. It assumes a particular naming of layers. conv_enc...
  369. """
  370. # naming
  371. model_name = name
  372. if prefix is None:
  373. prefix = model_name
  374. # if using skip connections, make sure need to use them.
  375. if use_skip_connections:
  376. assert input_model is not None, "is using skip connections, tensors dictionary is required"
  377. # first layer: input
  378. input_name = '%s_input' % prefix
  379. if input_model is None:
  380. input_tensor = KL.Input(shape=input_shape, name=input_name)
  381. last_tensor = input_tensor
  382. else:
  383. input_tensor = input_model.input
  384. last_tensor = input_model.output
  385. input_shape = last_tensor.shape.as_list()[1:]
  386. # vol size info
  387. ndims = len(input_shape) - 1
  388. input_shape = tuple(input_shape)
  389. if isinstance(pool_size, int):
  390. pool_size = (pool_size,) * ndims
  391. # prepare layers
  392. convL = getattr(KL, 'Conv%dD' % ndims)
  393. conv_kwargs = {'padding': padding, 'activation': activation}
  394. upsample = getattr(KL, 'UpSampling%dD' % ndims)
  395. # up arm:
  396. # nb_levels - 1 layers of Deconvolution3D
  397. # (approx via up + conv + ReLu) + merge + conv + ReLu + conv + ReLu
  398. lfidx = 0
  399. for level in range(nb_levels - 1):
  400. nb_lvl_feats = np.round(nb_features*feat_mult**(nb_levels-2-level)).astype(int)
  401. conv_kwargs['dilation_rate'] = dilation_rate_mult**(nb_levels-2-level)
  402. # upsample matching the max pooling layers size
  403. name = '%s_up_%d' % (prefix, nb_levels + level)
  404. last_tensor = upsample(size=pool_size, name=name)(last_tensor)
  405. up_tensor = last_tensor
  406. # merge layers combining previous layer
  407. # TODO: add Cropping3D or Cropping2D if 'valid' padding
  408. if use_skip_connections:
  409. conv_name = '%s_conv_downarm_%d_%d' % (prefix, nb_levels - 2 - level, nb_conv_per_level - 1)
  410. cat_tensor = input_model.get_layer(conv_name).output
  411. name = '%s_merge_%d' % (prefix, nb_levels + level)
  412. last_tensor = KL.concatenate([cat_tensor, last_tensor], axis=ndims+1, name=name)
  413. # convolution layers
  414. for conv in range(nb_conv_per_level):
  415. if layer_nb_feats is not None:
  416. nb_lvl_feats = layer_nb_feats[lfidx]
  417. lfidx += 1
  418. name = '%s_conv_uparm_%d_%d' % (prefix, nb_levels + level, conv)
  419. if conv < (nb_conv_per_level-1) or (not use_residuals):
  420. last_tensor = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(last_tensor)
  421. else:
  422. last_tensor = convL(nb_lvl_feats, conv_size, padding=padding, name=name)(last_tensor)
  423. if conv_dropout > 0:
  424. name = '%s_dropout_uparm_%d_%d' % (prefix, level, conv)
  425. noise_shape = [None, *[1]*ndims, nb_lvl_feats]
  426. last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor)
  427. # residual block
  428. if use_residuals:
  429. # the "add" layer is the original input
  430. # However, it may not have the right number of features to be added
  431. add_layer = up_tensor
  432. nb_feats_in = add_layer.get_shape()[-1]
  433. nb_feats_out = last_tensor.get_shape()[-1]
  434. if nb_feats_in > 1 and nb_feats_out > 1 and (nb_feats_in != nb_feats_out):
  435. name = '%s_expand_up_merge_%d' % (prefix, level)
  436. add_layer = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(add_layer)
  437. if conv_dropout > 0:
  438. name = '%s_dropout_up_merge_%d_%d' % (prefix, level, conv)
  439. noise_shape = [None, *[1]*ndims, nb_lvl_feats]
  440. last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor)
  441. name = '%s_res_up_merge_%d' % (prefix, level)
  442. last_tensor = KL.add([last_tensor, add_layer], name=name)
  443. name = '%s_res_up_merge_act_%d' % (prefix, level)
  444. last_tensor = KL.Activation(activation, name=name)(last_tensor)
  445. if batch_norm is not None:
  446. name = '%s_bn_up_%d' % (prefix, level)
  447. last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
  448. # Compute likelyhood prediction (no activation yet)
  449. name = '%s_likelihood' % prefix
  450. last_tensor = convL(nb_labels, 1, activation=None, name=name)(last_tensor)
  451. like_tensor = last_tensor
  452. # output prediction layer
  453. # we use a softmax to compute P(L_x|I) where x is each location
  454. if final_pred_activation == 'softmax':
  455. print("using final_pred_activation %s for %s" % (final_pred_activation, model_name))
  456. name = '%s_prediction' % prefix
  457. softmax_lambda_fcn = lambda x: keras.activations.softmax(x, axis=ndims + 1)
  458. pred_tensor = KL.Lambda(softmax_lambda_fcn, name=name)(last_tensor)
  459. # otherwise create a layer that does nothing.
  460. else:
  461. name = '%s_prediction' % prefix
  462. pred_tensor = KL.Activation('linear', name=name)(like_tensor)
  463. # create the model and retun
  464. model = Model(inputs=input_tensor, outputs=pred_tensor, name=model_name)
  465. return model
  466. def add_prior(input_model,
  467. prior_shape,
  468. name='prior_model',
  469. prefix=None,
  470. use_logp=True,
  471. final_pred_activation='softmax',
  472. add_prior_layer_reg=0):
  473. """
  474. Append post-prior layer to a given model
  475. """
  476. # naming
  477. model_name = name
  478. if prefix is None:
  479. prefix = model_name
  480. # prior input layer
  481. prior_input_name = '%s-input' % prefix
  482. prior_tensor = KL.Input(shape=prior_shape, name=prior_input_name)
  483. prior_tensor_input = prior_tensor
  484. like_tensor = input_model.output
  485. # operation varies depending on whether we log() prior or not.
  486. if use_logp:
  487. # name = '%s-log' % prefix
  488. # prior_tensor = KL.Lambda(_log_layer_wrap(add_prior_layer_reg), name=name)(prior_tensor)
  489. print("Breaking change: use_logp option now requires log input!", file=sys.stderr)
  490. merge_op = KL.add
  491. else:
  492. # using sigmoid to get the likelihood values between 0 and 1
  493. # note: they won't add up to 1.
  494. name = '%s_likelihood_sigmoid' % prefix
  495. like_tensor = KL.Activation('sigmoid', name=name)(like_tensor)
  496. merge_op = KL.multiply
  497. # merge the likelihood and prior layers into posterior layer
  498. name = '%s_posterior' % prefix
  499. post_tensor = merge_op([prior_tensor, like_tensor], name=name)
  500. # output prediction layer
  501. # we use a softmax to compute P(L_x|I) where x is each location
  502. pred_name = '%s_prediction' % prefix
  503. if final_pred_activation == 'softmax':
  504. assert use_logp, 'cannot do softmax when adding prior via P()'
  505. print("using final_pred_activation %s for %s" % (final_pred_activation, model_name))
  506. softmax_lambda_fcn = lambda x: keras.activations.softmax(x, axis=-1)
  507. pred_tensor = KL.Lambda(softmax_lambda_fcn, name=pred_name)(post_tensor)
  508. else:
  509. pred_tensor = KL.Activation('linear', name=pred_name)(post_tensor)
  510. # create the model
  511. model_inputs = [*input_model.inputs, prior_tensor_input]
  512. model = Model(inputs=model_inputs, outputs=[pred_tensor], name=model_name)
  513. # compile
  514. return model
  515. def single_ae(enc_size,
  516. input_shape,
  517. name='single_ae',
  518. prefix=None,
  519. ae_type='dense', # 'dense', or 'conv'
  520. conv_size=None,
  521. input_model=None,
  522. enc_lambda_layers=None,
  523. batch_norm=True,
  524. padding='same',
  525. activation=None,
  526. include_mu_shift_layer=False,
  527. do_vae=False):
  528. """single-layer Autoencoder (i.e. input - encoding - output"""
  529. # naming
  530. model_name = name
  531. if prefix is None:
  532. prefix = model_name
  533. if enc_lambda_layers is None:
  534. enc_lambda_layers = []
  535. # prepare input
  536. input_name = '%s_input' % prefix
  537. if input_model is None:
  538. assert input_shape is not None, 'input_shape of input_model is necessary'
  539. input_tensor = KL.Input(shape=input_shape, name=input_name)
  540. last_tensor = input_tensor
  541. else:
  542. input_tensor = input_model.input
  543. last_tensor = input_model.output
  544. input_shape = last_tensor.shape.as_list()[1:]
  545. input_nb_feats = last_tensor.shape.as_list()[-1]
  546. # prepare conv type based on input
  547. if ae_type == 'conv':
  548. ndims = len(input_shape) - 1
  549. convL = getattr(KL, 'Conv%dD' % ndims)
  550. assert conv_size is not None, 'with conv ae, need conv_size'
  551. conv_kwargs = {'padding': padding, 'activation': activation}
  552. # if want to go through a dense layer in the middle of the U, need to:
  553. # - flatten last layer if not flat
  554. # - do dense encoding and decoding
  555. # - unflatten (rehsape spatially) at end
  556. if ae_type == 'dense' and len(input_shape) > 1:
  557. name = '%s_ae_%s_down_flat' % (prefix, ae_type)
  558. last_tensor = KL.Flatten(name=name)(last_tensor)
  559. # recall this layer
  560. pre_enc_layer = last_tensor
  561. # encoding layer
  562. if ae_type == 'dense':
  563. assert len(enc_size) == 1, "enc_size should be of length 1 for dense layer"
  564. enc_size_str = ''.join(['%d_' % d for d in enc_size])[:-1]
  565. name = '%s_ae_mu_enc_dense_%s' % (prefix, enc_size_str)
  566. last_tensor = KL.Dense(enc_size[0], name=name)(pre_enc_layer)
  567. else: # convolution
  568. # convolve then resize. enc_size should be [nb_dim1, nb_dim2, ..., nb_feats]
  569. assert len(enc_size) == len(input_shape), \
  570. "encoding size does not match input shape %d %d" % (len(enc_size), len(input_shape))
  571. if list(enc_size)[:-1] != list(input_shape)[:-1] and \
  572. all([f is not None for f in input_shape[:-1]]) and \
  573. all([f is not None for f in enc_size[:-1]]):
  574. assert len(enc_size) - 1 == 2, "Sorry, I have not yet implemented non-2D resizing -- need to check out interpn!"
  575. name = '%s_ae_mu_enc_conv' % (prefix)
  576. last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
  577. name = '%s_ae_mu_enc' % (prefix)
  578. resize_fn = lambda x: tf.image.resize_bilinear(x, enc_size[:-1])
  579. last_tensor = KL.Lambda(resize_fn, name=name)(last_tensor)
  580. elif enc_size[-1] is None: # convolutional, but won't tell us bottleneck
  581. name = '%s_ae_mu_enc' % (prefix)
  582. last_tensor = KL.Lambda(lambda x: x, name=name)(pre_enc_layer)
  583. else:
  584. name = '%s_ae_mu_enc' % (prefix)
  585. last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
  586. if include_mu_shift_layer:
  587. # shift
  588. name = '%s_ae_mu_shift' % (prefix)
  589. last_tensor = layers.LocalBias(name=name)(last_tensor)
  590. # encoding clean-up layers
  591. for layer_fcn in enc_lambda_layers:
  592. lambda_name = layer_fcn.__name__
  593. name = '%s_ae_mu_%s' % (prefix, lambda_name)
  594. last_tensor = KL.Lambda(layer_fcn, name=name)(last_tensor)
  595. if batch_norm is not None:
  596. name = '%s_ae_mu_bn' % (prefix)
  597. last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
  598. # have a simple layer that does nothing to have a clear name before sampling
  599. name = '%s_ae_mu' % (prefix)
  600. last_tensor = KL.Lambda(lambda x: x, name=name)(last_tensor)
  601. # if doing variational AE, will need the sigma layer as well.
  602. if do_vae:
  603. mu_tensor = last_tensor
  604. # encoding layer
  605. if ae_type == 'dense':
  606. name = '%s_ae_sigma_enc_dense_%s' % (prefix, enc_size_str)
  607. last_tensor = KL.Dense(enc_size[0], name=name)(pre_enc_layer)
  608. else:
  609. if list(enc_size)[:-1] != list(input_shape)[:-1] and \
  610. all([f is not None for f in input_shape[:-1]]) and \
  611. all([f is not None for f in enc_size[:-1]]):
  612. assert len(enc_size) - 1 == 2, "Sorry, I have not yet implemented non-2D resizing..."
  613. name = '%s_ae_sigma_enc_conv' % (prefix)
  614. last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
  615. name = '%s_ae_sigma_enc' % (prefix)
  616. resize_fn = lambda x: tf.image.resize_bilinear(x, enc_size[:-1])
  617. last_tensor = KL.Lambda(resize_fn, name=name)(last_tensor)
  618. elif enc_size[-1] is None: # convolutional, but won't tell us bottleneck
  619. name = '%s_ae_sigma_enc' % (prefix)
  620. last_tensor = convL(pre_enc_layer.shape.as_list()[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
  621. # cannot use lambda, then mu and sigma will be same layer.
  622. # last_tensor = KL.Lambda(lambda x: x, name=name)(pre_enc_layer)
  623. else:
  624. name = '%s_ae_sigma_enc' % (prefix)
  625. last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
  626. # encoding clean-up layers
  627. for layer_fcn in enc_lambda_layers:
  628. lambda_name = layer_fcn.__name__
  629. name = '%s_ae_sigma_%s' % (prefix, lambda_name)
  630. last_tensor = KL.Lambda(layer_fcn, name=name)(last_tensor)
  631. if batch_norm is not None:
  632. name = '%s_ae_sigma_bn' % (prefix)
  633. last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
  634. # have a simple layer that does nothing to have a clear name before sampling
  635. name = '%s_ae_sigma' % (prefix)
  636. last_tensor = KL.Lambda(lambda x: x, name=name)(last_tensor)
  637. logvar_tensor = last_tensor
  638. # VAE sampling
  639. sampler = _VAESample().sample_z
  640. name = '%s_ae_sample' % (prefix)
  641. last_tensor = KL.Lambda(sampler, name=name)([mu_tensor, logvar_tensor])
  642. if include_mu_shift_layer:
  643. # shift
  644. name = '%s_ae_sample_shift' % (prefix)
  645. last_tensor = layers.LocalBias(name=name)(last_tensor)
  646. # decoding layer
  647. if ae_type == 'dense':
  648. name = '%s_ae_%s_dec_flat_%s' % (prefix, ae_type, enc_size_str)
  649. last_tensor = KL.Dense(np.prod(input_shape), name=name)(last_tensor)
  650. # unflatten if dense method
  651. if len(input_shape) > 1:
  652. name = '%s_ae_%s_dec' % (prefix, ae_type)
  653. last_tensor = KL.Reshape(input_shape, name=name)(last_tensor)
  654. else:
  655. if list(enc_size)[:-1] != list(input_shape)[:-1] and \
  656. all([f is not None for f in input_shape[:-1]]) and \
  657. all([f is not None for f in enc_size[:-1]]):
  658. name = '%s_ae_mu_dec' % (prefix)
  659. resize_fn = lambda x: tf.image.resize_bilinear(x, input_shape[:-1])
  660. last_tensor = KL.Lambda(resize_fn, name=name)(last_tensor)
  661. name = '%s_ae_%s_dec' % (prefix, ae_type)
  662. last_tensor = convL(input_nb_feats, conv_size, name=name, **conv_kwargs)(last_tensor)
  663. if batch_norm is not None:
  664. name = '%s_bn_ae_%s_dec' % (prefix, ae_type)
  665. last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
  666. # create the model and retun
  667. model = Model(inputs=input_tensor, outputs=[last_tensor], name=model_name)
  668. return model
  669. def design_dnn(nb_features, input_shape, nb_levels, conv_size, nb_labels,
  670. feat_mult=1,
  671. pool_size=2,
  672. padding='same',
  673. activation='elu',
  674. final_layer='dense-sigmoid',
  675. conv_dropout=0,
  676. conv_maxnorm=0,
  677. nb_input_features=1,
  678. batch_norm=False,
  679. name=None,
  680. prefix=None,
  681. use_strided_convolution_maxpool=True,
  682. nb_conv_per_level=2):
  683. """
  684. "deep" cnn with dense or global max pooling layer @ end...
  685. Could use sequential...
  686. """
  687. model_name = name
  688. if model_name is None:
  689. model_name = 'model_1'
  690. if prefix is None:
  691. prefix = model_name
  692. ndims = len(input_shape)
  693. input_shape = tuple(input_shape)
  694. convL = getattr(KL, 'Conv%dD' % ndims)
  695. maxpool = KL.MaxPooling3D if len(input_shape) == 3 else KL.MaxPooling2D
  696. if isinstance(pool_size, int):
  697. pool_size = (pool_size,) * ndims
  698. # kwargs for the convolution layer
  699. conv_kwargs = {'padding': padding, 'activation': activation}
  700. if conv_maxnorm > 0:
  701. conv_kwargs['kernel_constraint'] = maxnorm(conv_maxnorm)
  702. # initialize a dictionary
  703. enc_tensors = {}
  704. # first layer: input
  705. name = '%s_input' % prefix
  706. enc_tensors[name] = KL.Input(shape=input_shape + (nb_input_features,), name=name)
  707. last_tensor = enc_tensors[name]
  708. # down arm:
  709. # add nb_levels of conv + ReLu + conv + ReLu. Pool after each of first nb_levels - 1 layers
  710. for level in range(nb_levels):
  711. for conv in range(nb_conv_per_level):
  712. if conv_dropout > 0:
  713. name = '%s_dropout_%d_%d' % (prefix, level, conv)
  714. enc_tensors[name] = KL.Dropout(conv_dropout)(last_tensor)
  715. last_tensor = enc_tensors[name]
  716. name = '%s_conv_%d_%d' % (prefix, level, conv)
  717. nb_lvl_feats = np.round(nb_features*feat_mult**level).astype(int)
  718. enc_tensors[name] = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(last_tensor)
  719. last_tensor = enc_tensors[name]
  720. # max pool
  721. if use_strided_convolution_maxpool:
  722. name = '%s_strided_conv_%d' % (prefix, level)
  723. enc_tensors[name] = convL(nb_lvl_feats, pool_size, **conv_kwargs, name=name)(last_tensor)
  724. last_tensor = enc_tensors[name]
  725. else:
  726. name = '%s_maxpool_%d' % (prefix, level)
  727. enc_tensors[name] = maxpool(pool_size=pool_size, name=name, padding=padding)(last_tensor)
  728. last_tensor = enc_tensors[name]
  729. # dense layer
  730. if final_layer == 'dense-sigmoid':
  731. name = "%s_flatten" % prefix
  732. enc_tensors[name] = KL.Flatten(name=name)(last_tensor)
  733. last_tensor = enc_tensors[name]
  734. name = '%s_dense' % prefix
  735. enc_tensors[name] = KL.Dense(1, name=name, activation="sigmoid")(last_tensor)
  736. elif final_layer == 'dense-tanh':
  737. name = "%s_flatten" % prefix
  738. enc_tensors[name] = KL.Flatten(name=name)(last_tensor)
  739. last_tensor = enc_tensors[name]
  740. name = '%s_dense' % prefix
  741. enc_tensors[name] = KL.Dense(1, name=name)(last_tensor)
  742. last_tensor = enc_tensors[name]
  743. # Omittting BatchNorm for now, it seems to have a cpu vs gpu problem
  744. # https://github.com/tensorflow/tensorflow/pull/8906
  745. # https://github.com/fchollet/keras/issues/5802
  746. # name = '%s_%s_bn' % prefix
  747. # enc_tensors[name] = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
  748. # last_tensor = enc_tensors[name]
  749. name = '%s_%s_tanh' % prefix
  750. enc_tensors[name] = KL.Activation(activation="tanh", name=name)(last_tensor)
  751. elif final_layer == 'dense-softmax':
  752. name = "%s_flatten" % prefix
  753. enc_tensors[name] = KL.Flatten(name=name)(last_tensor)
  754. last_tensor = enc_tensors[name]
  755. name = '%s_dense' % prefix
  756. enc_tensors[name] = KL.Dense(nb_labels, name=name, activation="softmax")(last_tensor)
  757. # global max pooling layer
  758. elif final_layer == 'myglobalmaxpooling':
  759. name = '%s_batch_norm' % prefix
  760. enc_tensors[name] = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
  761. last_tensor = enc_tensors[name]
  762. name = '%s_global_max_pool' % prefix
  763. enc_tensors[name] = KL.Lambda(_global_max_nd, name=name)(last_tensor)
  764. last_tensor = enc_tensors[name]
  765. name = '%s_global_max_pool_reshape' % prefix
  766. enc_tensors[name] = KL.Reshape((1, 1), name=name)(last_tensor)
  767. last_tensor = enc_tensors[name]
  768. # cannot do activation in lambda layer. Could code inside, but will do extra lyaer
  769. name = '%s_global_max_pool_sigmoid' % prefix
  770. enc_tensors[name] = KL.Conv1D(1, 1, name=name, activation="sigmoid", use_bias=True)(last_tensor)
  771. elif final_layer == 'globalmaxpooling':
  772. name = '%s_conv_to_featmaps' % prefix
  773. enc_tensors[name] = KL.Conv3D(2, 1, name=name, activation="relu")(last_tensor)
  774. last_tensor = enc_tensors[name]
  775. name = '%s_global_max_pool' % prefix
  776. enc_tensors[name] = KL.GlobalMaxPooling3D(name=name)(last_tensor)
  777. last_tensor = enc_tensors[name]
  778. # cannot do activation in lambda layer. Could code inside, but will do extra lyaer
  779. name = '%s_global_max_pool_softmax' % prefix
  780. enc_tensors[name] = KL.Activation('softmax', name=name)(last_tensor)
  781. last_tensor = enc_tensors[name]
  782. # create the model
  783. model = Model(inputs=[enc_tensors['%s_input' % prefix]], outputs=[last_tensor], name=model_name)
  784. return model
  785. ###############################################################################
  786. # Helper function
  787. ###############################################################################
  788. def _global_max_nd(xtens):
  789. ytens = K.batch_flatten(xtens)
  790. return K.max(ytens, 1, keepdims=True)
  791. def _log_layer_wrap(reg=K.epsilon()):
  792. def _log_layer(tens):
  793. return K.log(tens + reg)
  794. return _log_layer
  795. # def _global_max_nd(x):
  796. # return K.exp(x)
  797. class _VAESample():
  798. def __init__(self): #, nb_z):
  799. # self.nb_z = nb_z
  800. pass
  801. def sample_z(self, args):
  802. mu, log_var = args
  803. # shape = (K.shape(mu)[0], self.nb_z)
  804. shape = K.shape(mu)
  805. eps = K.random_normal(shape=shape, mean=0., stddev=1.)
  806. return mu + K.exp(log_var / 2) * eps
  807. def _softmax(x, axis=-1, alpha=1):
  808. """
  809. building on keras implementation, allow alpha parameter
  810. Softmax activation function.
  811. # Arguments
  812. x : Tensor.
  813. axis: Integer, axis along which the softmax normalization is applied.
  814. alpha: a value to multiply all x
  815. # Returns
  816. Tensor, output of softmax transformation.
  817. # Raises
  818. ValueError: In case `dim(x) == 1`.
  819. """
  820. x = alpha * x
  821. ndim = K.ndim(x)
  822. if ndim == 2:
  823. return K.softmax(x)
  824. elif ndim > 2:
  825. e = K.exp(x - K.max(x, axis=axis, keepdims=True))
  826. s = K.sum(e, axis=axis, keepdims=True)
  827. return e / s
  828. else:
  829. raise ValueError('Cannot apply softmax to a tensor that is 1D')

models.py at commit ceab402, under MIT · at the source

Overview

Authors: Henrik Skibbe1,2, Michal Byra3, Akiya Watakabe4, Tetsuo Yamamori5, Marco Reisert6
  1. Department of Informatics,Faculty of Informatics, Matsuyama University, Matsuyama, Ehime Japan
  2. Brain Image Analysis Unit, RIKEN Center for Brain Science, Wako, Saitama Japan
  3. Institute of Fundamental Technological Research, Polish Academy of Sciences, Warsaw, Poland
  4. Laboratory for Molecular Mechanisms of Brain Development, RIKEN Center for Brain Science, Wako, Saitama Japan
  5. Department of Biological Functions and Regulation, CIEM (Central Institute of Experimental Medicine and Life Science), Kawasaki, Japan
  6. Department of Radiology, Medical Physics, and Department of Stereotactic and Functional Neurosurgery, Medical Center Freiburg, Faculty of Medicine, University of Freiburg, Freiburg, Germany
Journal: Scientific reports, volume 16, issue 1, article 14386
Dates: received 30 April 2025; accepted 16 March 2026; published online 21 March 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41598-026-44858-x · PMID 41865161 · PMCID PMC13144365 · OpenAlex W7139991225
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: structural MRI / diffusion (modality), human (organism)
Methods: Machine learning
Keywords: Engineering, Mathematics and computing
MeSH: Brain*, Image Processing, Computer-Assisted*, Imaging, Three-Dimensional*, Algorithms, Animals, Convolutional Neural Networks, Humans, Magnetic Resonance Imaging (* major topic)
Topic: Advanced Neural Network Applications (Computer Vision and Pattern Recognition, Computer Science), according to OpenAlex
Funding: Japan Agency for Medical Research and Development (AMED) (JP23wm0625001)
Citations: not cited yet (Europe PMC); 35 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

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

cwmok/LapIRN

License: MIT
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: c17fb7564ca752d37e97baf12cafff44e01da668, 20 July 2023
Languages: Python (11)
Size: 25 files, 11 scripts
Software Heritage: not archived
Found in: the text, “Reference approaches”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (11 files), PyTorch (11 files), NiBabel (2 files)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
13 files

MungoMeng/Registration-NICE-Net

License: MIT
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: ceab4022123a9e50524c2d72f184959807fd7039, 18 September 2023
Languages: Python (26)
Size: 29 files, 26 scripts
Software Heritage: not archived
Found in: the text, “Reference approaches”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (20 files), Keras (11 files), TensorFlow (8 files), Matplotlib (5 files), SciPy (5 files), NiBabel (3 files), NEURON (2 files), Pillow (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
28 files

MungoMeng/Registration-NICE-Trans

License: GPL-3.0
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: f213adeac0868ac5631e354ff0357e497b8ff78b, 15 March 2024
Languages: Python (5)
Size: 8 files, 5 scripts
Software Heritage: not archived
Found in: the text, “Reference approaches”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (5 files), PyTorch (4 files), SciPy (3 files), NiBabel (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
7 files

BrainImageAnalysis/INRsRegExp

License: MIT
State: the link answers, verified on 31 October 2026
Evidence: files inventoried
Commit: 74036e2b761b252375dfda40dca3fd6bfa19deaa, 5 December 2023
Languages: Python (5), Jupyter (2)
Size: 16 files, 7 scripts
Software Heritage: not archived
Found in: the text, “Reference approaches”
Holds: README, license file, 2 notebooks
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (6 files), PyTorch (6 files), Matplotlib (3 files), NiBabel (1 file), SciPy (1 file)
Availability: 2 checks, the latest on 31 October 2026: the link answers
  • 31 October 2026: the link answers
  • 27 September 2026: the link answers
9 files

BrainImageAnalysis/PatchMorph

License: Apache-2.0
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 562eeac00382cad3609ccf115223308ef7268819, 19 April 2024
Size: 1 file, 0 scripts
Software Heritage: not archived
Found in: the text, “Conclusion”
Holds: license file
Not found: README, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
1 file

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:

  • 5 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 49 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

Code and data availability statement

The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41598-026-44858-x.

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, issue, pages, dates, 5 authors, 2 keywords, 8 MeSH terms, 1 funder, 18 references.

Cite

This paper

Skibbe, H., Byra, M., Watakabe, A., Yamamori, T., & Reisert, M. (2026). Memory efficient training for 3D brain image registration networks using PatchMorph. Scientific reports, 16(1), 14386. https://doi.org/10.1038/s41598-026-44858-x

BibTeX

@article{skibbe2026memory,
author = {Skibbe, Henrik and Byra, Michal and Watakabe, Akiya and Yamamori, Tetsuo and Reisert, Marco},
title = {{Memory efficient training for 3D brain image registration networks using PatchMorph}},
journal = {Scientific reports},
year = {2026},
month = mar,
volume = {16},
number = {1},
pages = {14386},
publisher = {Nature Publishing Group},
issn = {2045-2322},
doi = {10.1038/s41598-026-44858-x},
url = {https://doi.org/10.1038/s41598-026-44858-x},
pmid = {41865161},
pmcid = {PMC13144365}
}

RIS

TY - JOUR
AU - Skibbe, Henrik
AU - Byra, Michal
AU - Watakabe, Akiya
AU - Yamamori, Tetsuo
AU - Reisert, Marco
TI - Memory efficient training for 3D brain image registration networks using PatchMorph
T2 - Scientific reports
J2 - Sci Rep
PY - 2026
DA - 2026/03/21
VL - 16
IS - 1
SP - 14386
SN - 2045-2322
PB - Nature Publishing Group
DO - 10.1038/s41598-026-44858-x
UR - https://doi.org/10.1038/s41598-026-44858-x
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41598-026-44858-x",
"type": "article-journal",
"title": "Memory efficient training for 3D brain image registration networks using PatchMorph",
"container-title": "Scientific reports",
"author": [
{
"family": "Skibbe",
"given": "Henrik"
},
{
"family": "Byra",
"given": "Michal"
},
{
"family": "Watakabe",
"given": "Akiya"
},
{
"family": "Yamamori",
"given": "Tetsuo"
},
{
"family": "Reisert",
"given": "Marco"
}
],
"container-title-short": "Sci Rep",
"volume": "16",
"issue": "1",
"page": "14386",
"DOI": "10.1038/s41598-026-44858-x",
"PMID": "41865161",
"PMCID": "PMC13144365",
"ISSN": "2045-2322",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41598-026-44858-x",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
21
]
]
}
}

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.1038/s41598-023-44517-5 [code]
Exploring the performance of implicit neural representations for brain image registration.
Journal: Scientific reports
In common: NiBabel, PyTorch, SciPy, 2 other tools, structural MRI / diffusion, 6 references
[2] doi:10.3390/jimaging12070276 [code]
Hyperelastic Regularization for Near-Diffeomorphic Transformer-Based Brain MRI Registration.
Journal: Journal of imaging
In common: Pillow, NiBabel, PyTorch, 3 other tools, structural MRI / diffusion, 4 references
[3] doi:10.1002/hbm.26165 [code]
Fast three-dimensional image generation for healthy brain aging using diffeomorphic registration.
Journal: Human brain mapping
In common: Keras, TensorFlow, NiBabel, 4 other tools, structural MRI / diffusion, 3 references
[4] doi:10.1016/j.neuroimage.2024.120981 [code]
RegBoost: Enhancing mouse brain image registration using geometric priors and Laplacian interpolation.
Journal: NeuroImage
In common: Pillow, NiBabel, PyTorch, 3 other tools, 5 references
[5] doi:10.1038/s41598-025-96234-w [code]
Decomposing the effect of normal aging and Alzheimer's disease in brain morphological changes via learned aging templates.
Journal: Scientific reports
In common: Keras, TensorFlow, Pillow, 5 other tools, structural MRI / diffusion, 2 references
[6] doi:10.1371/journal.pone.0282110 [code]
Learning deep abdominal CT registration through adaptive loss weighting and synthetic data generation.
Journal: PloS one
In common: Keras, TensorFlow, Pillow, 4 other tools, structural MRI / diffusion, 3 references
[7] doi:10.1038/s41592-025-02599-1 [code]
DeepPrep: an accelerated, scalable and robust pipeline for neuroimaging preprocessing empowered by deep learning.
Journal: Nature methods
In common: Keras, TensorFlow, Pillow, 5 other tools, structural MRI / diffusion, 2 references
[8] doi:10.1038/s41586-025-09708-2 [code]
A probabilistic histological atlas of the human brain for MRI segmentation.
Journal: Nature
In common: NEURON, Keras, TensorFlow, 6 other tools, structural MRI / diffusion
[9] doi:10.3389/fnsys.2026.1822122 [code]
Convergence-divergence circuits for multimodal integration of innate and learned opponent valences.
Journal: Frontiers in systems neuroscience
In common: NEURON, Keras, TensorFlow, 6 other tools
[10] doi:10.1016/j.isci.2024.111734 [code]
Enhancing unsupervised learning in medical image registration through scale-aware context aggregation.
Journal: iScience
In common: PyTorch, SciPy, Matplotlib, 1 other tool, 4 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.1038/s41598-026-44858-x.svg)](https://openscicode.org/badge/doi/10.1038/s41598-026-44858-x)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1038/s41598-026-44858-x.svg
   :target: https://openscicode.org/badge/doi/10.1038/s41598-026-44858-x
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1038/s41598-026-44858-x"><img src="https://openscicode.org/badge/doi/10.1038/s41598-026-44858-x.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.