Memory efficient training for 3D brain image registration networks using PatchMorph.
The 2 matches
- [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] § 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
- """
- tensorflow/keras utilities for the neuron project
- If you use this code, please cite
- Dalca AV, Guttag J, Sabuncu MR
- Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation,
- CVPR 2018
- Contact: adalca [at] csail [dot] mit [dot] edu
- License: GPLv3
- """
- import sys
- from . import layers
- # third party
- import numpy as np
- import tensorflow as tf
- import keras
- import keras.layers as KL
- from keras.models import Model
- import keras.backend as K
- from keras.constraints import maxnorm
- def dilation_net(nb_features,
- input_shape, # input layer shape, vector of size ndims + 1(nb_channels)
- nb_levels,
- conv_size,
- nb_labels,
- name='dilation_net',
- prefix=None,
- feat_mult=1,
- pool_size=2,
- use_logp=True,
- padding='same',
- dilation_rate_mult=1,
- activation='elu',
- use_residuals=False,
- final_pred_activation='softmax',
- nb_conv_per_level=1,
- add_prior_layer=False,
- add_prior_layer_reg=0,
- layer_nb_feats=None,
- batch_norm=None):
- return unet(nb_features,
- input_shape, # input layer shape, vector of size ndims + 1(nb_channels)
- nb_levels,
- conv_size,
- nb_labels,
- name='unet',
- prefix=None,
- feat_mult=1,
- pool_size=2,
- use_logp=True,
- padding='same',
- activation='elu',
- use_residuals=False,
- dilation_rate_mult=dilation_rate_mult,
- final_pred_activation='softmax',
- nb_conv_per_level=1,
- add_prior_layer=False,
- add_prior_layer_reg=0,
- layer_nb_feats=None,
- batch_norm=None)
- def unet(nb_features,
- input_shape, # input layer shape, vector of size ndims + 1(nb_channels)
- nb_levels,
- conv_size,
- nb_labels,
- name='unet',
- prefix=None,
- feat_mult=1,
- pool_size=2,
- use_logp=True,
- padding='same',
- dilation_rate_mult=1,
- activation='elu',
- use_residuals=False,
- final_pred_activation='softmax',
- nb_conv_per_level=1,
- add_prior_layer=False,
- add_prior_layer_reg=0,
- layer_nb_feats=None,
- conv_dropout=0,
- batch_norm=None):
- """
- unet-style model with an overdose of parametrization
- for U-net like architecture, we need to use Deconvolution3D.
- However, this is not yet available (maybe soon, it's on a dev branch in github I believe)
- Until then, we'll upsample and convolve.
- TODO: Need to check that UpSampling3D actually does NN-upsampling!
- """
- # naming
- model_name = name
- if prefix is None:
- prefix = model_name
- # volume size data
- ndims = len(input_shape) - 1
- if isinstance(pool_size, int):
- pool_size = (pool_size,) * ndims
- # get encoding model
- enc_model = conv_enc(nb_features,
- input_shape,
- nb_levels,
- conv_size,
- name=model_name,
- prefix=prefix,
- feat_mult=feat_mult,
- pool_size=pool_size,
- padding=padding,
- dilation_rate_mult=dilation_rate_mult,
- activation=activation,
- use_residuals=use_residuals,
- nb_conv_per_level=nb_conv_per_level,
- layer_nb_feats=layer_nb_feats,
- conv_dropout=conv_dropout,
- batch_norm=batch_norm)
- # get decoder
- # use_skip_connections=1 makes it a u-net
- lnf = layer_nb_feats[(nb_levels * nb_conv_per_level):] if layer_nb_feats is not None else None
- dec_model = conv_dec(nb_features,
- None,
- nb_levels,
- conv_size,
- nb_labels,
- name=model_name,
- prefix=prefix,
- feat_mult=feat_mult,
- pool_size=pool_size,
- use_skip_connections=1,
- padding=padding,
- dilation_rate_mult=dilation_rate_mult,
- activation=activation,
- use_residuals=use_residuals,
- final_pred_activation='linear' if add_prior_layer else final_pred_activation,
- nb_conv_per_level=nb_conv_per_level,
- batch_norm=batch_norm,
- layer_nb_feats=lnf,
- conv_dropout=conv_dropout,
- input_model=enc_model)
- final_model = dec_model
- if add_prior_layer:
- final_model = add_prior(dec_model,
- [*input_shape[:-1], nb_labels],
- name=model_name + '_prior',
- use_logp=use_logp,
- final_pred_activation=final_pred_activation,
- add_prior_layer_reg=add_prior_layer_reg)
- return final_model
- def ae(nb_features,
- input_shape,
- nb_levels,
- conv_size,
- nb_labels,
- enc_size,
- name='ae',
- prefix=None,
- feat_mult=1,
- pool_size=2,
- padding='same',
- activation='elu',
- use_residuals=False,
- nb_conv_per_level=1,
- batch_norm=None,
- enc_batch_norm=None,
- ae_type='conv', # 'dense', or 'conv'
- enc_lambda_layers=None,
- add_prior_layer=False,
- add_prior_layer_reg=0,
- use_logp=True,
- conv_dropout=0,
- include_mu_shift_layer=False,
- single_model=False, # whether to return a single model, or a tuple of models that can be stacked.
- final_pred_activation='softmax',
- do_vae=False):
- """
- Convolutional Auto-Encoder.
- Optionally Variational.
- Optionally Dense middle layer
- "Mostly" in that the inner encoding can be (optionally) constructed via dense features.
- Parameters:
- do_vae (bool): whether to do a variational auto-encoder or not.
- enc_lambda_layers functions to try:
- K.softsign
- a = 1
- longtanh = lambda x: K.tanh(x) * K.log(2 + a * abs(x))
- """
- # naming
- model_name = name
- # volume size data
- ndims = len(input_shape) - 1
- if isinstance(pool_size, int):
- pool_size = (pool_size,) * ndims
- # get encoding model
- enc_model = conv_enc(nb_features,
- input_shape,
- nb_levels,
- conv_size,
- name=model_name,
- feat_mult=feat_mult,
- pool_size=pool_size,
- padding=padding,
- activation=activation,
- use_residuals=use_residuals,
- nb_conv_per_level=nb_conv_per_level,
- conv_dropout=conv_dropout,
- batch_norm=batch_norm)
- # middle AE structure
- if single_model:
- in_input_shape = None
- in_model = enc_model
- else:
- in_input_shape = enc_model.output.shape.as_list()[1:]
- in_model = None
- mid_ae_model = single_ae(enc_size,
- in_input_shape,
- conv_size=conv_size,
- name=model_name,
- ae_type=ae_type,
- input_model=in_model,
- batch_norm=enc_batch_norm,
- enc_lambda_layers=enc_lambda_layers,
- include_mu_shift_layer=include_mu_shift_layer,
- do_vae=do_vae)
- # decoder
- if single_model:
- in_input_shape = None
- in_model = mid_ae_model
- else:
- in_input_shape = mid_ae_model.output.shape.as_list()[1:]
- in_model = None
- dec_model = conv_dec(nb_features,
- in_input_shape,
- nb_levels,
- conv_size,
- nb_labels,
- name=model_name,
- feat_mult=feat_mult,
- pool_size=pool_size,
- use_skip_connections=False,
- padding=padding,
- activation=activation,
- use_residuals=use_residuals,
- final_pred_activation='linear',
- nb_conv_per_level=nb_conv_per_level,
- batch_norm=batch_norm,
- conv_dropout=conv_dropout,
- input_model=in_model)
- if add_prior_layer:
- dec_model = add_prior(dec_model,
- [*input_shape[:-1],nb_labels],
- name=model_name,
- prefix=model_name + '_prior',
- use_logp=use_logp,
- final_pred_activation=final_pred_activation,
- add_prior_layer_reg=add_prior_layer_reg)
- if single_model:
- return dec_model
- else:
- return (dec_model, mid_ae_model, enc_model)
- def conv_enc(nb_features,
- input_shape,
- nb_levels,
- conv_size,
- name=None,
- prefix=None,
- feat_mult=1,
- pool_size=2,
- dilation_rate_mult=1,
- padding='same',
- activation='elu',
- layer_nb_feats=None,
- use_residuals=False,
- nb_conv_per_level=2,
- conv_dropout=0,
- batch_norm=None):
- """
- Fully Convolutional Encoder
- """
- # naming
- model_name = name
- if prefix is None:
- prefix = model_name
- # volume size data
- ndims = len(input_shape) - 1
- input_shape = tuple(input_shape)
- if isinstance(pool_size, int):
- pool_size = (pool_size,) * ndims
- # prepare layers
- convL = getattr(KL, 'Conv%dD' % ndims)
- conv_kwargs = {'padding': padding, 'activation': activation}
- maxpool = getattr(KL, 'MaxPooling%dD' % ndims)
- # first layer: input
- name = '%s_input' % prefix
- last_tensor = KL.Input(shape=input_shape, name=name)
- input_tensor = last_tensor
- # down arm:
- # add nb_levels of conv + ReLu + conv + ReLu. Pool after each of first nb_levels - 1 layers
- lfidx = 0
- for level in range(nb_levels):
- lvl_first_tensor = last_tensor
- nb_lvl_feats = np.round(nb_features*feat_mult**level).astype(int)
- conv_kwargs['dilation_rate'] = dilation_rate_mult**level
- for conv in range(nb_conv_per_level):
- if layer_nb_feats is not None:
- nb_lvl_feats = layer_nb_feats[lfidx]
- lfidx += 1
- name = '%s_conv_downarm_%d_%d' % (prefix, level, conv)
- if conv < (nb_conv_per_level-1) or (not use_residuals):
- last_tensor = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(last_tensor)
- else: # no activation
- last_tensor = convL(nb_lvl_feats, conv_size, padding=padding, name=name)(last_tensor)
- if conv_dropout > 0:
- # conv dropout along feature space only
- name = '%s_dropout_downarm_%d_%d' % (prefix, level, conv)
- noise_shape = [None, *[1]*ndims, nb_lvl_feats]
- last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor)
- if use_residuals:
- convarm_layer = last_tensor
- # the "add" layer is the original input
- # However, it may not have the right number of features to be added
- nb_feats_in = lvl_first_tensor.get_shape()[-1]
- nb_feats_out = convarm_layer.get_shape()[-1]
- add_layer = lvl_first_tensor
- if nb_feats_in > 1 and nb_feats_out > 1 and (nb_feats_in != nb_feats_out):
- name = '%s_expand_down_merge_%d' % (prefix, level)
- last_tensor = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(lvl_first_tensor)
- add_layer = last_tensor
- if conv_dropout > 0:
- name = '%s_dropout_down_merge_%d_%d' % (prefix, level, conv)
- noise_shape = [None, *[1]*ndims, nb_lvl_feats]
- last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor)
- name = '%s_res_down_merge_%d' % (prefix, level)
- last_tensor = KL.add([add_layer, convarm_layer], name=name)
- name = '%s_res_down_merge_act_%d' % (prefix, level)
- last_tensor = KL.Activation(activation, name=name)(last_tensor)
- if batch_norm is not None:
- name = '%s_bn_down_%d' % (prefix, level)
- last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
- # max pool if we're not at the last level
- if level < (nb_levels - 1):
- name = '%s_maxpool_%d' % (prefix, level)
- last_tensor = maxpool(pool_size=pool_size, name=name, padding=padding)(last_tensor)
- # create the model and return
- model = Model(inputs=input_tensor, outputs=[last_tensor], name=model_name)
- return model
- def conv_dec(nb_features,
- input_shape,
- nb_levels,
- conv_size,
- nb_labels,
- name=None,
- prefix=None,
- feat_mult=1,
- pool_size=2,
- use_skip_connections=False,
- padding='same',
- dilation_rate_mult=1,
- activation='elu',
- use_residuals=False,
- final_pred_activation='softmax',
- nb_conv_per_level=2,
- layer_nb_feats=None,
- batch_norm=None,
- conv_dropout=0,
- input_model=None):
- """
- Fully Convolutional Decoder
- Parameters:
- ...
- use_skip_connections (bool): if true, turns an Enc-Dec to a U-Net.
- If true, input_tensor and tensors are required.
- It assumes a particular naming of layers. conv_enc...
- """
- # naming
- model_name = name
- if prefix is None:
- prefix = model_name
- # if using skip connections, make sure need to use them.
- if use_skip_connections:
- assert input_model is not None, "is using skip connections, tensors dictionary is required"
- # first layer: input
- input_name = '%s_input' % prefix
- if input_model is None:
- input_tensor = KL.Input(shape=input_shape, name=input_name)
- last_tensor = input_tensor
- else:
- input_tensor = input_model.input
- last_tensor = input_model.output
- input_shape = last_tensor.shape.as_list()[1:]
- # vol size info
- ndims = len(input_shape) - 1
- input_shape = tuple(input_shape)
- if isinstance(pool_size, int):
- pool_size = (pool_size,) * ndims
- # prepare layers
- convL = getattr(KL, 'Conv%dD' % ndims)
- conv_kwargs = {'padding': padding, 'activation': activation}
- upsample = getattr(KL, 'UpSampling%dD' % ndims)
- # up arm:
- # nb_levels - 1 layers of Deconvolution3D
- # (approx via up + conv + ReLu) + merge + conv + ReLu + conv + ReLu
- lfidx = 0
- for level in range(nb_levels - 1):
- nb_lvl_feats = np.round(nb_features*feat_mult**(nb_levels-2-level)).astype(int)
- conv_kwargs['dilation_rate'] = dilation_rate_mult**(nb_levels-2-level)
- # upsample matching the max pooling layers size
- name = '%s_up_%d' % (prefix, nb_levels + level)
- last_tensor = upsample(size=pool_size, name=name)(last_tensor)
- up_tensor = last_tensor
- # merge layers combining previous layer
- # TODO: add Cropping3D or Cropping2D if 'valid' padding
- if use_skip_connections:
- conv_name = '%s_conv_downarm_%d_%d' % (prefix, nb_levels - 2 - level, nb_conv_per_level - 1)
- cat_tensor = input_model.get_layer(conv_name).output
- name = '%s_merge_%d' % (prefix, nb_levels + level)
- last_tensor = KL.concatenate([cat_tensor, last_tensor], axis=ndims+1, name=name)
- # convolution layers
- for conv in range(nb_conv_per_level):
- if layer_nb_feats is not None:
- nb_lvl_feats = layer_nb_feats[lfidx]
- lfidx += 1
- name = '%s_conv_uparm_%d_%d' % (prefix, nb_levels + level, conv)
- if conv < (nb_conv_per_level-1) or (not use_residuals):
- last_tensor = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(last_tensor)
- else:
- last_tensor = convL(nb_lvl_feats, conv_size, padding=padding, name=name)(last_tensor)
- if conv_dropout > 0:
- name = '%s_dropout_uparm_%d_%d' % (prefix, level, conv)
- noise_shape = [None, *[1]*ndims, nb_lvl_feats]
- last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor)
- # residual block
- if use_residuals:
- # the "add" layer is the original input
- # However, it may not have the right number of features to be added
- add_layer = up_tensor
- nb_feats_in = add_layer.get_shape()[-1]
- nb_feats_out = last_tensor.get_shape()[-1]
- if nb_feats_in > 1 and nb_feats_out > 1 and (nb_feats_in != nb_feats_out):
- name = '%s_expand_up_merge_%d' % (prefix, level)
- add_layer = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(add_layer)
- if conv_dropout > 0:
- name = '%s_dropout_up_merge_%d_%d' % (prefix, level, conv)
- noise_shape = [None, *[1]*ndims, nb_lvl_feats]
- last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor)
- name = '%s_res_up_merge_%d' % (prefix, level)
- last_tensor = KL.add([last_tensor, add_layer], name=name)
- name = '%s_res_up_merge_act_%d' % (prefix, level)
- last_tensor = KL.Activation(activation, name=name)(last_tensor)
- if batch_norm is not None:
- name = '%s_bn_up_%d' % (prefix, level)
- last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
- # Compute likelyhood prediction (no activation yet)
- name = '%s_likelihood' % prefix
- last_tensor = convL(nb_labels, 1, activation=None, name=name)(last_tensor)
- like_tensor = last_tensor
- # output prediction layer
- # we use a softmax to compute P(L_x|I) where x is each location
- if final_pred_activation == 'softmax':
- print("using final_pred_activation %s for %s" % (final_pred_activation, model_name))
- name = '%s_prediction' % prefix
- softmax_lambda_fcn = lambda x: keras.activations.softmax(x, axis=ndims + 1)
- pred_tensor = KL.Lambda(softmax_lambda_fcn, name=name)(last_tensor)
- # otherwise create a layer that does nothing.
- else:
- name = '%s_prediction' % prefix
- pred_tensor = KL.Activation('linear', name=name)(like_tensor)
- # create the model and retun
- model = Model(inputs=input_tensor, outputs=pred_tensor, name=model_name)
- return model
- def add_prior(input_model,
- prior_shape,
- name='prior_model',
- prefix=None,
- use_logp=True,
- final_pred_activation='softmax',
- add_prior_layer_reg=0):
- """
- Append post-prior layer to a given model
- """
- # naming
- model_name = name
- if prefix is None:
- prefix = model_name
- # prior input layer
- prior_input_name = '%s-input' % prefix
- prior_tensor = KL.Input(shape=prior_shape, name=prior_input_name)
- prior_tensor_input = prior_tensor
- like_tensor = input_model.output
- # operation varies depending on whether we log() prior or not.
- if use_logp:
- # name = '%s-log' % prefix
- # prior_tensor = KL.Lambda(_log_layer_wrap(add_prior_layer_reg), name=name)(prior_tensor)
- print("Breaking change: use_logp option now requires log input!", file=sys.stderr)
- merge_op = KL.add
- else:
- # using sigmoid to get the likelihood values between 0 and 1
- # note: they won't add up to 1.
- name = '%s_likelihood_sigmoid' % prefix
- like_tensor = KL.Activation('sigmoid', name=name)(like_tensor)
- merge_op = KL.multiply
- # merge the likelihood and prior layers into posterior layer
- name = '%s_posterior' % prefix
- post_tensor = merge_op([prior_tensor, like_tensor], name=name)
- # output prediction layer
- # we use a softmax to compute P(L_x|I) where x is each location
- pred_name = '%s_prediction' % prefix
- if final_pred_activation == 'softmax':
- assert use_logp, 'cannot do softmax when adding prior via P()'
- print("using final_pred_activation %s for %s" % (final_pred_activation, model_name))
- softmax_lambda_fcn = lambda x: keras.activations.softmax(x, axis=-1)
- pred_tensor = KL.Lambda(softmax_lambda_fcn, name=pred_name)(post_tensor)
- else:
- pred_tensor = KL.Activation('linear', name=pred_name)(post_tensor)
- # create the model
- model_inputs = [*input_model.inputs, prior_tensor_input]
- model = Model(inputs=model_inputs, outputs=[pred_tensor], name=model_name)
- # compile
- return model
- def single_ae(enc_size,
- input_shape,
- name='single_ae',
- prefix=None,
- ae_type='dense', # 'dense', or 'conv'
- conv_size=None,
- input_model=None,
- enc_lambda_layers=None,
- batch_norm=True,
- padding='same',
- activation=None,
- include_mu_shift_layer=False,
- do_vae=False):
- """single-layer Autoencoder (i.e. input - encoding - output"""
- # naming
- model_name = name
- if prefix is None:
- prefix = model_name
- if enc_lambda_layers is None:
- enc_lambda_layers = []
- # prepare input
- input_name = '%s_input' % prefix
- if input_model is None:
- assert input_shape is not None, 'input_shape of input_model is necessary'
- input_tensor = KL.Input(shape=input_shape, name=input_name)
- last_tensor = input_tensor
- else:
- input_tensor = input_model.input
- last_tensor = input_model.output
- input_shape = last_tensor.shape.as_list()[1:]
- input_nb_feats = last_tensor.shape.as_list()[-1]
- # prepare conv type based on input
- if ae_type == 'conv':
- ndims = len(input_shape) - 1
- convL = getattr(KL, 'Conv%dD' % ndims)
- assert conv_size is not None, 'with conv ae, need conv_size'
- conv_kwargs = {'padding': padding, 'activation': activation}
- # if want to go through a dense layer in the middle of the U, need to:
- # - flatten last layer if not flat
- # - do dense encoding and decoding
- # - unflatten (rehsape spatially) at end
- if ae_type == 'dense' and len(input_shape) > 1:
- name = '%s_ae_%s_down_flat' % (prefix, ae_type)
- last_tensor = KL.Flatten(name=name)(last_tensor)
- # recall this layer
- pre_enc_layer = last_tensor
- # encoding layer
- if ae_type == 'dense':
- assert len(enc_size) == 1, "enc_size should be of length 1 for dense layer"
- enc_size_str = ''.join(['%d_' % d for d in enc_size])[:-1]
- name = '%s_ae_mu_enc_dense_%s' % (prefix, enc_size_str)
- last_tensor = KL.Dense(enc_size[0], name=name)(pre_enc_layer)
- else: # convolution
- # convolve then resize. enc_size should be [nb_dim1, nb_dim2, ..., nb_feats]
- assert len(enc_size) == len(input_shape), \
- "encoding size does not match input shape %d %d" % (len(enc_size), len(input_shape))
- if list(enc_size)[:-1] != list(input_shape)[:-1] and \
- all([f is not None for f in input_shape[:-1]]) and \
- all([f is not None for f in enc_size[:-1]]):
- assert len(enc_size) - 1 == 2, "Sorry, I have not yet implemented non-2D resizing -- need to check out interpn!"
- name = '%s_ae_mu_enc_conv' % (prefix)
- last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
- name = '%s_ae_mu_enc' % (prefix)
- resize_fn = lambda x: tf.image.resize_bilinear(x, enc_size[:-1])
- last_tensor = KL.Lambda(resize_fn, name=name)(last_tensor)
- elif enc_size[-1] is None: # convolutional, but won't tell us bottleneck
- name = '%s_ae_mu_enc' % (prefix)
- last_tensor = KL.Lambda(lambda x: x, name=name)(pre_enc_layer)
- else:
- name = '%s_ae_mu_enc' % (prefix)
- last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
- if include_mu_shift_layer:
- # shift
- name = '%s_ae_mu_shift' % (prefix)
- last_tensor = layers.LocalBias(name=name)(last_tensor)
- # encoding clean-up layers
- for layer_fcn in enc_lambda_layers:
- lambda_name = layer_fcn.__name__
- name = '%s_ae_mu_%s' % (prefix, lambda_name)
- last_tensor = KL.Lambda(layer_fcn, name=name)(last_tensor)
- if batch_norm is not None:
- name = '%s_ae_mu_bn' % (prefix)
- last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
- # have a simple layer that does nothing to have a clear name before sampling
- name = '%s_ae_mu' % (prefix)
- last_tensor = KL.Lambda(lambda x: x, name=name)(last_tensor)
- # if doing variational AE, will need the sigma layer as well.
- if do_vae:
- mu_tensor = last_tensor
- # encoding layer
- if ae_type == 'dense':
- name = '%s_ae_sigma_enc_dense_%s' % (prefix, enc_size_str)
- last_tensor = KL.Dense(enc_size[0], name=name)(pre_enc_layer)
- else:
- if list(enc_size)[:-1] != list(input_shape)[:-1] and \
- all([f is not None for f in input_shape[:-1]]) and \
- all([f is not None for f in enc_size[:-1]]):
- assert len(enc_size) - 1 == 2, "Sorry, I have not yet implemented non-2D resizing..."
- name = '%s_ae_sigma_enc_conv' % (prefix)
- last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
- name = '%s_ae_sigma_enc' % (prefix)
- resize_fn = lambda x: tf.image.resize_bilinear(x, enc_size[:-1])
- last_tensor = KL.Lambda(resize_fn, name=name)(last_tensor)
- elif enc_size[-1] is None: # convolutional, but won't tell us bottleneck
- name = '%s_ae_sigma_enc' % (prefix)
- last_tensor = convL(pre_enc_layer.shape.as_list()[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
- # cannot use lambda, then mu and sigma will be same layer.
- # last_tensor = KL.Lambda(lambda x: x, name=name)(pre_enc_layer)
- else:
- name = '%s_ae_sigma_enc' % (prefix)
- last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer)
- # encoding clean-up layers
- for layer_fcn in enc_lambda_layers:
- lambda_name = layer_fcn.__name__
- name = '%s_ae_sigma_%s' % (prefix, lambda_name)
- last_tensor = KL.Lambda(layer_fcn, name=name)(last_tensor)
- if batch_norm is not None:
- name = '%s_ae_sigma_bn' % (prefix)
- last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
- # have a simple layer that does nothing to have a clear name before sampling
- name = '%s_ae_sigma' % (prefix)
- last_tensor = KL.Lambda(lambda x: x, name=name)(last_tensor)
- logvar_tensor = last_tensor
- # VAE sampling
- sampler = _VAESample().sample_z
- name = '%s_ae_sample' % (prefix)
- last_tensor = KL.Lambda(sampler, name=name)([mu_tensor, logvar_tensor])
- if include_mu_shift_layer:
- # shift
- name = '%s_ae_sample_shift' % (prefix)
- last_tensor = layers.LocalBias(name=name)(last_tensor)
- # decoding layer
- if ae_type == 'dense':
- name = '%s_ae_%s_dec_flat_%s' % (prefix, ae_type, enc_size_str)
- last_tensor = KL.Dense(np.prod(input_shape), name=name)(last_tensor)
- # unflatten if dense method
- if len(input_shape) > 1:
- name = '%s_ae_%s_dec' % (prefix, ae_type)
- last_tensor = KL.Reshape(input_shape, name=name)(last_tensor)
- else:
- if list(enc_size)[:-1] != list(input_shape)[:-1] and \
- all([f is not None for f in input_shape[:-1]]) and \
- all([f is not None for f in enc_size[:-1]]):
- name = '%s_ae_mu_dec' % (prefix)
- resize_fn = lambda x: tf.image.resize_bilinear(x, input_shape[:-1])
- last_tensor = KL.Lambda(resize_fn, name=name)(last_tensor)
- name = '%s_ae_%s_dec' % (prefix, ae_type)
- last_tensor = convL(input_nb_feats, conv_size, name=name, **conv_kwargs)(last_tensor)
- if batch_norm is not None:
- name = '%s_bn_ae_%s_dec' % (prefix, ae_type)
- last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
- # create the model and retun
- model = Model(inputs=input_tensor, outputs=[last_tensor], name=model_name)
- return model
- def design_dnn(nb_features, input_shape, nb_levels, conv_size, nb_labels,
- feat_mult=1,
- pool_size=2,
- padding='same',
- activation='elu',
- final_layer='dense-sigmoid',
- conv_dropout=0,
- conv_maxnorm=0,
- nb_input_features=1,
- batch_norm=False,
- name=None,
- prefix=None,
- use_strided_convolution_maxpool=True,
- nb_conv_per_level=2):
- """
- "deep" cnn with dense or global max pooling layer @ end...
- Could use sequential...
- """
- model_name = name
- if model_name is None:
- model_name = 'model_1'
- if prefix is None:
- prefix = model_name
- ndims = len(input_shape)
- input_shape = tuple(input_shape)
- convL = getattr(KL, 'Conv%dD' % ndims)
- maxpool = KL.MaxPooling3D if len(input_shape) == 3 else KL.MaxPooling2D
- if isinstance(pool_size, int):
- pool_size = (pool_size,) * ndims
- # kwargs for the convolution layer
- conv_kwargs = {'padding': padding, 'activation': activation}
- if conv_maxnorm > 0:
- conv_kwargs['kernel_constraint'] = maxnorm(conv_maxnorm)
- # initialize a dictionary
- enc_tensors = {}
- # first layer: input
- name = '%s_input' % prefix
- enc_tensors[name] = KL.Input(shape=input_shape + (nb_input_features,), name=name)
- last_tensor = enc_tensors[name]
- # down arm:
- # add nb_levels of conv + ReLu + conv + ReLu. Pool after each of first nb_levels - 1 layers
- for level in range(nb_levels):
- for conv in range(nb_conv_per_level):
- if conv_dropout > 0:
- name = '%s_dropout_%d_%d' % (prefix, level, conv)
- enc_tensors[name] = KL.Dropout(conv_dropout)(last_tensor)
- last_tensor = enc_tensors[name]
- name = '%s_conv_%d_%d' % (prefix, level, conv)
- nb_lvl_feats = np.round(nb_features*feat_mult**level).astype(int)
- enc_tensors[name] = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- # max pool
- if use_strided_convolution_maxpool:
- name = '%s_strided_conv_%d' % (prefix, level)
- enc_tensors[name] = convL(nb_lvl_feats, pool_size, **conv_kwargs, name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- else:
- name = '%s_maxpool_%d' % (prefix, level)
- enc_tensors[name] = maxpool(pool_size=pool_size, name=name, padding=padding)(last_tensor)
- last_tensor = enc_tensors[name]
- # dense layer
- if final_layer == 'dense-sigmoid':
- name = "%s_flatten" % prefix
- enc_tensors[name] = KL.Flatten(name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- name = '%s_dense' % prefix
- enc_tensors[name] = KL.Dense(1, name=name, activation="sigmoid")(last_tensor)
- elif final_layer == 'dense-tanh':
- name = "%s_flatten" % prefix
- enc_tensors[name] = KL.Flatten(name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- name = '%s_dense' % prefix
- enc_tensors[name] = KL.Dense(1, name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- # Omittting BatchNorm for now, it seems to have a cpu vs gpu problem
- # https://github.com/tensorflow/tensorflow/pull/8906
- # https://github.com/fchollet/keras/issues/5802
- # name = '%s_%s_bn' % prefix
- # enc_tensors[name] = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
- # last_tensor = enc_tensors[name]
- name = '%s_%s_tanh' % prefix
- enc_tensors[name] = KL.Activation(activation="tanh", name=name)(last_tensor)
- elif final_layer == 'dense-softmax':
- name = "%s_flatten" % prefix
- enc_tensors[name] = KL.Flatten(name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- name = '%s_dense' % prefix
- enc_tensors[name] = KL.Dense(nb_labels, name=name, activation="softmax")(last_tensor)
- # global max pooling layer
- elif final_layer == 'myglobalmaxpooling':
- name = '%s_batch_norm' % prefix
- enc_tensors[name] = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- name = '%s_global_max_pool' % prefix
- enc_tensors[name] = KL.Lambda(_global_max_nd, name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- name = '%s_global_max_pool_reshape' % prefix
- enc_tensors[name] = KL.Reshape((1, 1), name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- # cannot do activation in lambda layer. Could code inside, but will do extra lyaer
- name = '%s_global_max_pool_sigmoid' % prefix
- enc_tensors[name] = KL.Conv1D(1, 1, name=name, activation="sigmoid", use_bias=True)(last_tensor)
- elif final_layer == 'globalmaxpooling':
- name = '%s_conv_to_featmaps' % prefix
- enc_tensors[name] = KL.Conv3D(2, 1, name=name, activation="relu")(last_tensor)
- last_tensor = enc_tensors[name]
- name = '%s_global_max_pool' % prefix
- enc_tensors[name] = KL.GlobalMaxPooling3D(name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- # cannot do activation in lambda layer. Could code inside, but will do extra lyaer
- name = '%s_global_max_pool_softmax' % prefix
- enc_tensors[name] = KL.Activation('softmax', name=name)(last_tensor)
- last_tensor = enc_tensors[name]
- # create the model
- model = Model(inputs=[enc_tensors['%s_input' % prefix]], outputs=[last_tensor], name=model_name)
- return model
- ###############################################################################
- # Helper function
- ###############################################################################
- def _global_max_nd(xtens):
- ytens = K.batch_flatten(xtens)
- return K.max(ytens, 1, keepdims=True)
- def _log_layer_wrap(reg=K.epsilon()):
- def _log_layer(tens):
- return K.log(tens + reg)
- return _log_layer
- # def _global_max_nd(x):
- # return K.exp(x)
- class _VAESample():
- def __init__(self): #, nb_z):
- # self.nb_z = nb_z
- pass
- def sample_z(self, args):
- mu, log_var = args
- # shape = (K.shape(mu)[0], self.nb_z)
- shape = K.shape(mu)
- eps = K.random_normal(shape=shape, mean=0., stddev=1.)
- return mu + K.exp(log_var / 2) * eps
- def _softmax(x, axis=-1, alpha=1):
- """
- building on keras implementation, allow alpha parameter
- Softmax activation function.
- # Arguments
- x : Tensor.
- axis: Integer, axis along which the softmax normalization is applied.
- alpha: a value to multiply all x
- # Returns
- Tensor, output of softmax transformation.
- # Raises
- ValueError: In case `dim(x) == 1`.
- """
- x = alpha * x
- ndim = K.ndim(x)
- if ndim == 2:
- return K.softmax(x)
- elif ndim > 2:
- e = K.exp(x - K.max(x, axis=axis, keepdims=True))
- s = K.sum(e, axis=axis, keepdims=True)
- return e / s
- else:
- raise ValueError('Cannot apply softmax to a tensor that is 1D')
models.py at commit ceab402, under MIT · at the source
Overview
- Department of Informatics,Faculty of Informatics, Matsuyama University, Matsuyama, Ehime Japan
- Brain Image Analysis Unit, RIKEN Center for Brain Science, Wako, Saitama Japan
- Institute of Fundamental Technological Research, Polish Academy of Sciences, Warsaw, Poland
- Laboratory for Molecular Mechanisms of Brain Development, RIKEN Center for Brain Science, Wako, Saitama Japan
- Department of Biological Functions and Regulation, CIEM (Central Institute of Experimental Medicine and Life Science), Kawasaki, Japan
- Department of Radiology, Medical Physics, and Department of Stereotactic and Functional Neurosurgery, Medical Center Freiburg, Faculty of Medicine, University of Freiburg, Freiburg, Germany
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
c17fb7564ca752d37e97baf12cafff44e01da668, 20 July 2023Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
13 files
- Code/
Functions.py , Python, 213 lines - Code/
Test_LapIRN_diff.py , Python, 92 lines - Code/
Test_LapIRN_diff_resize. , Python, 100 linespy - Code/
Test_LapIRN_disp.py , Python, 85 lines - Code/
Test_LapIRN_disp_resize_ , Python, 110 linesunorm_beta.py - Code/
Train_LapIRN_diff.py , Python, 391 lines - Code/
Train_LapIRN_diff_resize , Python, 471 lines.py - Code/
Train_LapIRN_disp.py , Python, 391 lines - Code/
Train_LapIRN_disp_resize , Python, 470 lines_unorm_beta.py - Code/
count_parameters.py , Python, 53 lines - Code/
miccai2020_model_stage.p , Python, 1,280 linesy - LICENSE.md, License, 22 lines
- README.md, Text, 56 lines
MungoMeng/Registration-NICE-Net
ceab4022123a9e50524c2d72f184959807fd7039, 18 September 2023Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
28 files
- NICE-Net/
datagenerators.py , Python, 113 lines - NICE-Net/
ext/ , Python, 1 linemedipy/ __init__.py - NICE-Net/
ext/ , Python, 52 linesmedipy/ metrics.py - NICE-Net/
ext/ , Python, 10 linesneuron/ __init__.py - NICE-Net/
ext/ , Python, 626 linesneuron/ callbacks.py - NICE-Net/
ext/ , Python, 420 linesneuron/ dataproc.py - NICE-Net/
ext/ , Python, 1,415 linesneuron/ generators.py - NICE-Net/
ext/ , Python, 19 linesneuron/ inits.py - NICE-Net/
ext/ , Python, 853 linesneuron/ layers.py - NICE-Net/
ext/ , Python, 435 linesneuron/ metrics.py - NICE-Net/
ext/ , Python, 1,009 lines, 1 matchneuron/ models.py - NICE-Net/
ext/ , Python, 254 linesneuron/ plot.py - NICE-Net/
ext/ , Python, 1,287 linesneuron/ utils.py - NICE-Net/
ext/ , Python, 2 linespynd/ __init__.py - NICE-Net/
ext/ , Python, 11 linespynd/ imutils.py - NICE-Net/
ext/ , Python, 436 linespynd/ ndutils.py - NICE-Net/
ext/ , Python, 122 linespynd/ segutils.py - NICE-Net/
ext/ , Python, 4 linespytoolslib/ __init__.py - NICE-Net/
ext/ , Python, 211 linespytoolslib/ iniparse.py - NICE-Net/
ext/ , Python, 451 linespytoolslib/ patchlib.py - NICE-Net/
ext/ , Python, 63 linespytoolslib/ plotting.py - NICE-Net/
ext/ , Python, 31 linespytoolslib/ timer.py - NICE-Net/
losses.py , Python, 117 lines - NICE-Net/
networks.py , Python, 493 lines - NICE-Net/
test.py , Python, 131 lines - NICE-Net/
train.py , Python, 136 lines - LICENSE, License, 21 lines
- README.md, Text, 11 lines
MungoMeng/Registration-NICE-Trans
f213adeac0868ac5631e354ff0357e497b8ff78b, 15 March 2024Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
7 files
- NICE-Trans/
datagenerators.py , Python, 91 lines - NICE-Trans/
losses.py , Python, 118 lines - NICE-Trans/
networks.py , Python, 710 lines - NICE-Trans/
test.py , Python, 156 lines - NICE-Trans/
train.py , Python, 229 lines - LICENSE, License, 674 lines
- README.md, Text, 10 lines
BrainImageAnalysis/INRsRegExp
74036e2b761b252375dfda40dca3fd6bfa19deaa, 5 December 2023Availability: 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
- example_custom.ipynb, Jupyter, 240 lines
- example_functions.ipynb, Jupyter, 96 lines
- libs/
__init__.py , Python, 1 line - libs/
codes.py , Python, 1,693 lines, 1 match - libs/
data.py , Python, 325 lines - libs/
losses.py , Python, 190 lines - libs/
networks.py , Python, 1,578 lines - LICENSE, License, 21 lines
- README.md, Text, 31 lines
BrainImageAnalysis/PatchMorph
562eeac00382cad3609ccf115223308ef7268819, 19 April 2024Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
1 file
- LICENSE, License, 201 lines
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
- zenodo:19041876, at Zenodo; found in “Data availability”
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:
- it points to a dataset: Zenodo 19041876
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://
BibTeX
@article{skibbe2026memor
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/
url = {https://
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/
VL - 16
IS - 1
SP - 14386
SN - 2045-2322
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"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":
"volume": "16",
"issue": "1",
"page": "14386",
"DOI": "10.1038/
"PMID": "41865161",
"PMCID": "PMC13144365",
"ISSN": "2045-2322",
"publisher": "Nature Publishing Group",
"URL": "https://
"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 reportsIn 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 imagingIn 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 mappingIn 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: NeuroImageIn 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 reportsIn 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 oneIn 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 methodsIn 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: NatureIn 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 neuroscienceIn 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: iScienceIn 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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 5 repositories of the authors' code, each at its verified commit and with its license, 49 scripts, and 2 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:e249507def2d5a84…
Add the badge to the code's README
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 -->
[](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:endHTML
<!-- 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".
- cwmok/LapIRN: README.md on GitHub
- MungoMeng/Registration-NICE-Net: README.md on GitHub
- MungoMeng/Registration-NICE-Trans: README.md on GitHub
- BrainImageAnalysis/INRsRegExp: README.md on GitHub
- BrainImageAnalysis/PatchMorph on GitHub (add a README there)
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.
