//  [python]
def _update_scaling(x, y, sett, max_niter_gn=1, num_linesearch=4, verbose=0):
    """ Updates an even/odd slice scaling parameter using Gauss-Newton
        optimisation.

    Args:
        verbose (bool, optional): Verbose for testing, defaults to False.

    Returns:
        sll (torch.tensor): Log-likelihood.

    """
    # Update rigid parameters, for all input images
    sll = torch.tensor(0, device=sett.device, dtype=torch.float64)
    ll = torch.tensor(0, device=sett.device, dtype=torch.float64)
    for c in range(len(x)):  # Loop over channels
        for n_x in range(len(x[c])):  # Loop over repeats
            if x[c][n_x].ct:
                # Do not optimise scaling for CT data
                continue
            # Parameters
            dim_thick = x[c][n_x].po.dim_thick
            tau = x[c][n_x].tau
            scl = x[c][n_x].po.scl
            smo_ker = x[c][n_x].po.smo_ker
            dim_thick = x[c][n_x].po.dim_thick
            ratio = x[c][n_x].po.ratio
            dim = x[c][n_x].po.dim_yx
            mat_yx = x[c][n_x].po.mat_yx
            mat_y = x[c][n_x].po.mat_y
            rigid = _expm(x[c][n_x].rigid_q, sett.rigid_basis)
            mat = torch.linalg.solve(mat_y, rigid.mm(mat_yx))  # mat_y\rigid*mat_yx            
            # Observed data
            dat_x = x[c][n_x].dat
            msk = dat_x != 0
            # Get even/odd data
            xo = _even_odd(dat_x, 'odd', dim_thick)
            mo = _even_odd(msk, 'odd', dim_thick)
            xo = xo[mo]
            xe = _even_odd(dat_x, 'even', dim_thick)
            me = _even_odd(msk, 'even', dim_thick)
            xe = xe[me]
            # Get reconstruction (without scaling)
            grid = affine_grid(mat.type(torch.float32), dim, jitter=False)
            dat_y = grid_pull(y[c].dat[None, None, ...], grid[None, ...], bound=sett.bound,
                              interpolation=sett.interpolation,
                              extrapolate=False)
            dat_y = F.conv3d(dat_y, smo_ker, stride=ratio)[0, 0, ...]
            # Apply scaling
            dat_y = _apply_scaling(dat_y, scl, dim_thick)

            for n_gn in range(max_niter_gn):  # Loop over Gauss-Newton iterations
                # Log-likelihood
                ll = 0.5 * tau * torch.sum((dat_x[msk] - dat_y[msk]) ** 2, dtype=torch.float64)

                if verbose >= 2:  # Show images
                    show_slices(torch.stack((dat_x, dat_y, (dat_x - dat_y) ** 2), 3),
                                fig_num=666, colorbar=False, flip=False)

                # Get even/odd data
                yo = _even_odd(dat_y, 'odd', dim_thick)
                yo = yo[mo]
                ye = _even_odd(dat_y, 'even', dim_thick)
                ye = ye[me]

                # Gradient
                gr = tau * (torch.sum(ye * (xe - ye), dtype=torch.float64)
                            - torch.sum(yo * (xo - yo), dtype=torch.float64))

                # Hessian
                Hes = tau * (torch.sum(ye ** 2, dtype=torch.float64)
                             + torch.sum(yo ** 2, dtype=torch.float64))

                # Compute Gauss-Newton update step
                Update = gr / Hes

                # Do update..
                old_scl = scl.clone()
                old_ll = ll.clone()
                armijo = torch.tensor(1.0, device=sett.device, dtype=old_scl.dtype)
                if num_linesearch == 0:
                    # ..without a line-search
                    scl = old_scl - armijo * Update
                    if verbose >= 1:
                        print('c={}, n={}, gn={} | exp(s)={}'
                              .format(c, n_x, n_gn, round(scl.exp(), 5)))
                else:
                    # ..using a line-search
                    for n_ls in range(num_linesearch):
                        # Take step
                        scl = old_scl - armijo * Update
                        # Apply scaling
                        dat_y = _apply_scaling(dat_y, scl - old_scl, dim_thick)
                        # Compute matching term
                        ll = 0.5 * tau * torch.sum((dat_x[msk] - dat_y[msk]) ** 2, dtype=torch.float64)

                        if verbose >= 2:  # Show images
                            show_slices(torch.stack((dat_x, dat_y, (dat_x - dat_y) ** 2), 3),
                                        fig_num=666, colorbar=False, flip=False)

                        # Matching improved?
                        if ll < old_ll:
                            # Better fit!
                            if verbose >= 1:
                                print(
                                    'c={}, n={}, gn={}, ls={} | :) ll={:0.2f}, ll-oll={:0.2f} | exp(s)={} armijo={}'
                                    .format(c, n_x, n_gn, n_ls, ll, ll - old_ll, round(scl.exp(), 5),
                                            round(armijo, 4)))
                            break
                        else:
                            # Reset parameters
                            scl = old_scl
                            ll = old_ll
                            armijo *= 0.5
                            if verbose >= 1 and n_ls == num_linesearch - 1:
                                print(
                                    'c={}, n={}, gn={}, ls={} | :( ll={:0.2f}, ll-oll={:0.2f} | exp(s)={} armijo={}'
                                    .format(c, n_x, n_gn, n_ls, ll, ll - old_ll, round(old_scl.exp(), 5),
                                            round(armijo, 4)))
            # Update scaling in projection operator
            x[c][n_x].po.scl = scl
            # Accumulate neg log-lik
            sll += ll

    return x, sll