def assemble_non_overlapping_windows(x):
    """
    Assemble an image from non-overlapping windows. Essentially the inverse of `skimage.util.view_as_windows` where the tile shape
    and the step shape are the same.

    `X` should be an N-dimensional array, where N is divisible by 2. The first N/2 dimensions represent the tile co-ordinates within
    the grid of tiles and the second N/2 dimensions represent the pixel co-ordinates within the tiles

    :param X: an array, e.g. for 2D tiles `X` would be 4-dimensional of shape `(tile_y, tile_x, pixel_y, pixel_x)`
    :return: assembled image as an array, with half the number of dimensions as `X`
    """
    n = len(x.shape)
    if (n % 2) != 0:
        raise ValueError('x should have an even number of dimensions; it has {}'.format(n))

    grid_shape = x.shape[:n//2]
    tile_shape = x.shape[n//2:]

    # Re-order the dimensions that so that each tile co-ordinate is followed by each pixel co-ordinate
    # (tile_0, tile_1, ..., pixel_0, pixel_1, ...) -> (tile_0, pixel_0, tile_1, pixel_1, ...)
    dim_order = []
    for i in range(n//2):
        dim_order.append(i)
        dim_order.append(n//2 + i)
    x = x.transpose(dim_order)

    # Concatenate tile co-ordinate dimensions with pixel co-ordinate dimensions
    x = x.reshape(tuple([t * p for t, p in zip(grid_shape, tile_shape)]))

    return x