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);
  }
  // @Override
  public void run(String arg0) {
    // get list of image stacks
    final int[] idList = WindowManager.getIDList();

    if (idList == null || idList.length < 2) {
      IJ.error("You need at least two open images.");
      return;
    }

    final String[] imgList = new String[idList.length];
    for (int i = 0; i < idList.length; ++i)
      imgList[i] = WindowManager.getImage(idList[i]).getTitle();

    if (defaultImg1 >= imgList.length || defaultImg2 >= imgList.length) {
      defaultImg1 = 0;
      defaultImg2 = 1;
    }

    /** The first dialog for choosing the images */
    final GenericDialog gd = new GenericDialog("Descriptor based registration");

    gd.addChoice("First_image (to register)", imgList, imgList[defaultImg1]);
    gd.addChoice("Second_image (reference)", imgList, imgList[defaultImg2]);

    if (lastModel1 != null) gd.addCheckbox("Reapply last model", defaultReApply);

    gd.addMessage(
        "Warning: if images are of RGB or 8-bit color they will be converted to hyperstacks.");
    gd.addMessage(
        "Please note that the SPIM Registration is based on a publication.\n"
            + "If you use it successfully for your research please be so kind to cite our work:\n"
            + "Preibisch et al., Nature Methods (2010), 7(6):418-419\n");

    MultiLineLabel text = (MultiLineLabel) gd.getMessage();
    Bead_Registration.addHyperLinkListener(text, paperURL);

    gd.showDialog();

    if (gd.wasCanceled()) return;

    ImagePlus imp1 = WindowManager.getImage(idList[defaultImg1 = gd.getNextChoiceIndex()]);
    ImagePlus imp2 = WindowManager.getImage(idList[defaultImg2 = gd.getNextChoiceIndex()]);
    boolean reApply = false;

    if (lastModel1 != null) {
      reApply = gd.getNextBoolean();
      defaultReApply = reApply;
    }

    // if one of the images is rgb or 8-bit color convert them to hyperstack
    imp1 = Hyperstack_rearranger.convertToHyperStack(imp1);
    imp2 = Hyperstack_rearranger.convertToHyperStack(imp2);

    // test if the images are compatible
    String error = testRegistrationCompatibility(imp1, imp2);

    if (error != null) {
      IJ.log(error);
      return;
    }

    // get the parameters
    final int dimensionality;

    if (imp1.getNSlices() > 1) dimensionality = 3;
    else dimensionality = 2;

    if (imp1.getNFrames() > 1 || imp2.getNFrames() > 2)
      IJ.log(
          "WARNING: Images have more than one timepoint, same model as the first timepoint will be applied to all timepoints.");

    if (imp1.getNFrames() != imp2.getNFrames())
      IJ.log(
          "CRITICAL: Images do not have the same amount of timepoints. Will use the number of timepoints of the smaller series.");

    // reapply?
    if (reApply) {
      if (dimensionality < lastDimensionality) {
        IJ.log(
            "Cannot reapply, cannot apply a "
                + lastModel1.getClass().getSimpleName()
                + " to "
                + dimensionality
                + " data.");
        defaultReApply = false;
        return;
      } else if (dimensionality > lastDimensionality) {
        IJ.log(
            "WARNING: applying a "
                + lastModel1.getClass().getSimpleName()
                + " to "
                + dimensionality
                + " data.");
      }

      // just fuse
      final DescriptorParameters params = new DescriptorParameters();
      params.dimensionality = dimensionality;
      params.reApply = true;
      params.fuse = 0;
      params.setPointsRois = false;
      Matching.descriptorBasedRegistration(imp1, imp2, params);
      return;
    }

    // open a second dialog and query the other parameters
    final DescriptorParameters params = getParameters(imp1, imp2, dimensionality);

    if (params == null) return;

    // compute the actual matching
    Matching.descriptorBasedRegistration(imp1, imp2, params);
  }
  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;
    }
  }