public static <T extends RealType<T>> CompositeImage createOverlay(
      final T targetType,
      final ArrayList<ImagePlus> images,
      final ArrayList<InvertibleBoundable> models,
      final int dimensionality,
      final int timepoint,
      final InterpolatorFactory<FloatType> factory) {
    final int numImages = images.size();

    // the size of the new image
    final int[] size = new int[dimensionality];
    // the offset relative to the output image which starts with its local coordinates (0,0,0)
    final float[] offset = new float[dimensionality];

    // estimate the boundaries of the output image and the offset for fusion (negative coordinates
    // after transform have to be shifted to 0,0,0)
    estimateBounds(offset, size, images, models, dimensionality);

    // for output
    final ImageFactory<T> f = new ImageFactory<T>(targetType, new ImagePlusContainerFactory());
    // the composite
    final ImageStack stack = new ImageStack(size[0], size[1]);

    int numChannels = 0;

    // loop over all images
    for (int i = 0; i < images.size(); ++i) {
      final ImagePlus imp = images.get(i);

      // loop over all channels
      for (int c = 1; c <= imp.getNChannels(); ++c) {
        final Image<T> out = f.createImage(size);
        fuseChannel(
            out,
            ImageJFunctions.convertFloat(Hyperstack_rearranger.getImageChunk(imp, c, timepoint)),
            offset,
            models.get(i + (timepoint - 1) * numImages),
            factory);
        try {
          final ImagePlus outImp = ((ImagePlusContainer<?, ?>) out.getContainer()).getImagePlus();
          for (int z = 1; z <= out.getDimension(2); ++z)
            stack.addSlice(imp.getTitle(), outImp.getStack().getProcessor(z));
        } catch (ImgLibException e) {
          IJ.log("Output image has no ImageJ type: " + e);
        }

        // count all channels
        ++numChannels;
      }
    }

    // convertXYZCT ...
    ImagePlus result =
        new ImagePlus(
            "overlay " + images.get(0).getTitle() + " ... " + images.get(numImages - 1).getTitle(),
            stack);

    // numchannels, z-slices, timepoints (but right now the order is still XYZCT)
    if (dimensionality == 3) {
      result.setDimensions(size[2], numChannels, 1);
      result = OverlayFusion.switchZCinXYCZT(result);
    } else {
      result.setDimensions(numChannels, 1, 1);
    }

    return new CompositeImage(result, CompositeImage.COMPOSITE);
  }
  public static <T extends RealType<T>> ImagePlus createReRegisteredSeries(
      final T targetType,
      final ImagePlus imp,
      final ArrayList<InvertibleBoundable> models,
      final int dimensionality,
      final String directory) {
    final int numImages = imp.getNFrames();

    // the size of the new image
    final int[] size = new int[dimensionality];
    // the offset relative to the output image which starts with its local coordinates (0,0,0)
    final float[] offset = new float[dimensionality];

    final int[][] imgSizes = new int[numImages][dimensionality];

    for (int i = 0; i < numImages; ++i) {
      imgSizes[i][0] = imp.getWidth();
      imgSizes[i][1] = imp.getHeight();
      if (dimensionality == 3) imgSizes[i][2] = imp.getNSlices();
    }

    // estimate the boundaries of the output image and the offset for fusion (negative coordinates
    // after transform have to be shifted to 0,0,0)
    estimateBounds(offset, size, imgSizes, models, dimensionality);

    // use the same size as the first image, this is a little bit ad-hoc
    if (useSizeOfFirstImage) {
      for (int d = 0; d < dimensionality; ++d) {
        size[d] = imgSizes[0][d];
        offset[d] = 0;
      }
    }

    // for output
    final ImageFactory<T> f = new ImageFactory<T>(targetType, new ImagePlusContainerFactory());
    // the composite
    final ImageStack stack = new ImageStack(size[0], size[1]);

    for (int t = 1; t <= numImages; ++t) {
      for (int c = 1; c <= imp.getNChannels(); ++c) {
        final Image<T> out = f.createImage(size);
        if (useNearestNeighborInterpolation)
          fuseChannel(
              out,
              ImageJFunctions.convertFloat(Hyperstack_rearranger.getImageChunk(imp, c, t)),
              offset,
              models.get(t - 1),
              new NearestNeighborInterpolatorFactory<FloatType>(
                  new OutOfBoundsStrategyValueFactory<FloatType>()));
        else
          fuseChannel(
              out,
              ImageJFunctions.convertFloat(Hyperstack_rearranger.getImageChunk(imp, c, t)),
              offset,
              models.get(t - 1),
              new LinearInterpolatorFactory<FloatType>(
                  new OutOfBoundsStrategyValueFactory<FloatType>()));
        try {
          final ImagePlus outImp = ((ImagePlusContainer<?, ?>) out.getContainer()).getImagePlus();

          if (directory == null) {
            // fuse
            for (int z = 1; z <= out.getDimension(2); ++z)
              stack.addSlice(imp.getTitle(), outImp.getStack().getProcessor(z));
          } else {
            // write to disk
            for (int z = 1; z <= out.getDimension(2); ++z) {
              final ImagePlus tmp =
                  new ImagePlus(
                      "img_t"
                          + lz(t, numImages)
                          + "_z"
                          + lz(z, out.getDimension(2))
                          + "_c"
                          + lz(c, imp.getNChannels()),
                      outImp.getStack().getProcessor(z));
              final FileSaver fs = new FileSaver(tmp);
              fs.saveAsTiff(new File(directory, tmp.getTitle()).getAbsolutePath());
              tmp.close();
            }

            out.close();
            outImp.close();
          }
        } catch (ImgLibException e) {
          IJ.log("Output image has no ImageJ type: " + e);
        }
      }
    }

    if (directory != null) return null;

    // convertXYZCT ...
    ImagePlus result = new ImagePlus("registered " + imp.getTitle(), stack);

    // numchannels, z-slices, timepoints (but right now the order is still XYZCT)
    if (dimensionality == 3) {
      result.setDimensions(size[2], imp.getNChannels(), imp.getNFrames());
      result = OverlayFusion.switchZCinXYCZT(result);
      return new CompositeImage(result, CompositeImage.COMPOSITE);
    } else {
      // IJ.log( "ch: " + imp.getNChannels() );
      // IJ.log( "slices: " + imp.getNSlices() );
      // IJ.log( "frames: " + imp.getNFrames() );
      result.setDimensions(imp.getNChannels(), 1, imp.getNFrames());

      if (imp.getNChannels() > 1) return new CompositeImage(result, CompositeImage.COMPOSITE);
      else return result;
    }
  }