/**
   * TODO
   *
   * @param cumulativeMinCutoff
   * @param cumulativeMaxCutoff
   * @param state
   * @param setupAssignments
   */
  public static void initBrightness(
      final double cumulativeMinCutoff,
      final double cumulativeMaxCutoff,
      final ViewerState state,
      final SetupAssignments setupAssignments) {
    final Source<?> source = state.getSources().get(state.getCurrentSource()).getSpimSource();
    final int timepoint = state.getCurrentTimepoint();
    if (!source.isPresent(timepoint)) return;
    if (!UnsignedShortType.class.isInstance(source.getType())) return;
    @SuppressWarnings("unchecked")
    final RandomAccessibleInterval<UnsignedShortType> img =
        (RandomAccessibleInterval<UnsignedShortType>)
            source.getSource(timepoint, source.getNumMipmapLevels() - 1);
    final long z = (img.min(2) + img.max(2) + 1) / 2;

    final int numBins = 6535;
    final Histogram1d<UnsignedShortType> histogram =
        new Histogram1d<UnsignedShortType>(
            Views.iterable(Views.hyperSlice(img, 2, z)),
            new Real1dBinMapper<UnsignedShortType>(0, 65535, numBins, false));
    final DiscreteFrequencyDistribution dfd = histogram.dfd();
    final long[] bin = new long[] {0};
    double cumulative = 0;
    int i = 0;
    for (; i < numBins && cumulative < cumulativeMinCutoff; ++i) {
      bin[0] = i;
      cumulative += dfd.relativeFrequency(bin);
    }
    final int min = i * 65535 / numBins;
    for (; i < numBins && cumulative < cumulativeMaxCutoff; ++i) {
      bin[0] = i;
      cumulative += dfd.relativeFrequency(bin);
    }
    final int max = i * 65535 / numBins;
    final MinMaxGroup minmax = setupAssignments.getMinMaxGroups().get(0);
    minmax.getMinBoundedValue().setCurrentValue(min);
    minmax.getMaxBoundedValue().setCurrentValue(max);
  }
  /**
   * Get a "good" initial viewer transform. The viewer transform is chosen such that for the first
   * source,
   *
   * <ul>
   *   <li>the XY plane is aligned with the screen plane,
   *   <li>the <em>z = dim_z / 2</em> slice is shown,
   *   <li>centered and scaled such that the full <em>dim_x</em> by <em>dim_y</em> is visible.
   * </ul>
   *
   * @param viewerWidth width of the viewer display
   * @param viewerHeight height of the viewer display
   * @param state the {@link ViewerState} containing at least one source.
   * @return proposed initial viewer transform.
   */
  public static AffineTransform3D initTransform(
      final int viewerWidth,
      final int viewerHeight,
      final boolean zoomedIn,
      final ViewerState state) {
    final int cX = viewerWidth / 2;
    final int cY = viewerHeight / 2;

    final Source<?> source = state.getSources().get(state.getCurrentSource()).getSpimSource();
    final int timepoint = state.getCurrentTimepoint();
    if (!source.isPresent(timepoint)) return new AffineTransform3D();

    final AffineTransform3D sourceTransform = new AffineTransform3D();
    source.getSourceTransform(timepoint, 0, sourceTransform);

    final Interval sourceInterval = source.getSource(timepoint, 0);
    final double sX0 = sourceInterval.min(0);
    final double sX1 = sourceInterval.max(0);
    final double sY0 = sourceInterval.min(1);
    final double sY1 = sourceInterval.max(1);
    final double sZ0 = sourceInterval.min(2);
    final double sZ1 = sourceInterval.max(2);
    final double sX = (sX0 + sX1 + 1) / 2;
    final double sY = (sY0 + sY1 + 1) / 2;
    final double sZ = (sZ0 + sZ1 + 1) / 2;

    final double[][] m = new double[3][4];

    // rotation
    final double[] qSource = new double[4];
    final double[] qViewer = new double[4];
    Affine3DHelpers.extractApproximateRotationAffine(sourceTransform, qSource, 2);
    LinAlgHelpers.quaternionInvert(qSource, qViewer);
    LinAlgHelpers.quaternionToR(qViewer, m);

    // translation
    final double[] centerSource = new double[] {sX, sY, sZ};
    final double[] centerGlobal = new double[3];
    final double[] translation = new double[3];
    sourceTransform.apply(centerSource, centerGlobal);
    LinAlgHelpers.quaternionApply(qViewer, centerGlobal, translation);
    LinAlgHelpers.scale(translation, -1, translation);
    LinAlgHelpers.setCol(3, translation, m);

    final AffineTransform3D viewerTransform = new AffineTransform3D();
    viewerTransform.set(m);

    // scale
    final double[] pSource = new double[] {sX1 + 0.5, sY1 + 0.5, sZ};
    final double[] pGlobal = new double[3];
    final double[] pScreen = new double[3];
    sourceTransform.apply(pSource, pGlobal);
    viewerTransform.apply(pGlobal, pScreen);
    final double scaleX = cX / pScreen[0];
    final double scaleY = cY / pScreen[1];
    final double scale;
    if (zoomedIn) scale = Math.max(scaleX, scaleY);
    else scale = Math.min(scaleX, scaleY);
    viewerTransform.scale(scale);

    // window center offset
    viewerTransform.set(viewerTransform.get(0, 3) + cX, 0, 3);
    viewerTransform.set(viewerTransform.get(1, 3) + cY, 1, 3);
    return viewerTransform;
  }