#! /bin/bash

# imfuse - Combines focus stackshot images to one overall sharp image.
Version="0.9.14-beta"

### Information

usage() {
  echo "imfuse: Combines focus stackshot images to one overall sharp image.

Usage:
  imfuse [OPTIONS] -- IMAGES...

imfuse assumes that the alphanumerical order of the input images goes
from front to back; for the other way around use option --revert.

imfuse results suffer a lot from JPG compression. If you have JPG images
as a source only, first convert them to TIF or PNG before doing anything else.
Tool stackprepare can do that.

Dependencies:
  ImageMagick 7       Mandatory (command 'magick').
  exiftool            Optional, needed to set and transfer image metadata.
  enfuse              Optional, needed for options --enfuse and --bg=enfuse.
  geeqie              Optional, needed for option -V to show progress..
  feh                 Optional, needed for option -W to show result image.
  focus-stack         Optional, needed for option --align.
                        https://github.com/PetteriAimonen/focus-stack

General options:
     --align              Align images. Rather use tool stackalign, because 
                          this option is not well integrated in cache reusage.
 -B, --basename [=NAME]   Base name [+path] for output image. 
                          If empty, the name of current directory is used.
 -C, --cache [=DIR]       Store generated masks and images in directory DIR.
                          They can be re-used in later runs of imfuse. See also
                          option --rmcache. Default DIR: ~/.cache/imfuse
                          For best performance the cache should be on an SSD.
     --cacheformat=FORMAT  Store cache files in format FORMAT. Default: $(maskarg_defaultvalue cacheformat word1)
                          Should be a lossless format like tif or png.
                          mpc is optimized for fast access by ImageMagick,
                          but needs lots of disk space. In case you run out
                          of disk space, use 'tif'.
     --exif [=IMAGE]      Transfer exif meta data from IMAGE or first image.
 -f, --force              Force imfuse to run even if output image exists.
     --format=FORMAT      Store result in image format FORMAT. Default: $(maskarg_defaultvalue format word1)
 -h, --help               Show this help and exit.
     --layered            Store backgrounds and substacks in layered TIF.
     --license            Show license (GPLv2) and exit.
     --limit-memory=ARG   Limit amount of used memory. Default: 80%
                          ARG can be a % value or an absolute value for MB.
 -L, --longname           Create long filename containing all options.
 -o, --output=FILE        Result image file name. See also --basename.
     --revert             Revert order of source images.
     --rmcache            Clean cache and exit.
     --version            Show imfuse version and exit.
     --video              Generate a video of shown intermediate images.
 -V                       Show intermediate images with image viewer geeqie.
 -W                       Show result image with image viewer feh.
                          For keyboard shortcuts see 'man feh'. Examples:
                            arrow left/right: switch images
                            arrow up/down:    zoom in/out
                            /:                zoom to window size
                            *:                zoom 100%
                            i:                show/hide imfuse options
                            d:                show/hide file name
 -X                       Store [and show] mask and depth map, too.

The options below can take additionally arguments [=ARG].
Multiple arguments are comma-separated. Example: --morphology=r3,blur

Arguments taken by all mask generating options: (Mostly you only need w).
  w         Weight of mask. Percent value from 0 to 100.
            Of interest if specifying more than one mask generation method.
            Example: --morphology --darkness=w15
            This will generate two masks. The saturation mask will only have
            noteable effect where the morphology mask strength is below 25%.
  mask [=yes|no] Apply --mask* options. Default is yes except for channel and
            image comparision options.
  C=        Colorspace to use instead of default $(maskarg_defaultvalue colorspace colorspace). Compare --colorspace.
  c         Colorspace channel to use. Counting up from 0 for first channel.
            Example: C=HSL,c2 will use the saturation channel from HSL.
  I=        Image creation and comparision. For possible args see --background.
            Example: --statistic=E=max will generate a mask from an evaluated
            max image that was compared with the source image.
            Compare step 2b: Mask generation - image comparision masks.
  diff [=yes|no] Use difference of command result to source image.
            Normally not neded to be set manually.
  neg       Negate / invert the mask. Most of interest for color channels.
  t         Threshold (removal) of low contrast areas.
  T         Threshold (removal) of high contrast areas.
  level [=yes|no] Equalize histogram distribution to get compareable masks.
            Default is yes except for image comparision options
            and for some colorspace channel options.
Arguments additionally taken by some options:
  r         Radius. Must be an integer value.
  R         Second radius.
  s         Sigma. Takes also non-integer values like 0.75
  S         Second Sigma.
  p         A percent value.
  P         Second percent value
  n         An integer value.
  N         Second integer value.
  (word)    Some options take one or two word arguments.

Generally spoken, increased radius or sigma enhances contrast, reduces noise,
looses details and increases undesired seams. There's always a tradeoff.
The default values are set rather low and mostly profit from increasing.
As a metapher, think of radius and sigma as of brush sizes.

Step 1: Source image adjustment
These option do not modify the original files, but only adjust them in RAM.
In general you can skip this step.

 --colorspace [=ARG]  Colorspace in which to generate the masks.
                      Normally you don't need to adjust this option.
                      ARG takes arguments:
                        c        channel
                        (word)   colorspace        Default: $(maskarg_defaultvalue colorspace colorspace)
                      colorspace can be one of 'magick -list colorspace'.
                      Some of interest: sRGB, CMYK,  RGB
                      https://legacy.imagemagick.org/Usage/color_basics
 --prepresize [=ARG]  Resize images before mask generation. The result will
                      have the original size nonetheless.
                      ARG takes arguments:
                        p        percent           Default: $(maskarg_defaultvalue prepresize percent1)
                        (word)   interpolation     Default: $(maskarg_defaultvalue prepresize word1)
                      percent values >100% can make sense to get more detail.
                        Values <100% can speed up processing of large images
                        at the cost of precision.
                      word must be one of 'magick -list interpolate'
                        and adjusts resize interpolation.
                      Note that you would need to adjust radius and sigma in
                      mask options proportinally for similar results.
                      Percent values like in --fft are less affected.

Step 2: Mask generation
  Options can be specified multiple times. The order does not matter.
  The generated masks will be composed into one mask containing them all.
  Most recommended: 
    --blur, --fft, --wavelet and --diffstat for fine detail.
    --statistic and --morphology for strong edges.

  Contrast masks:
    The contrast mask options detect edges that indicate focused areas.

 --blur [=ARG]        Difference of blur.
                      ARG additionally takes arguments:
                        r        radius            Default: $(maskarg_defaultvalue cutblur radius1)
                        s        sigma1            Default: $(maskarg_defaultvalue cutblur sigma1)
                        S        sigma2            Default: 1.6 * sigma1
                        (word)   gaussian
                      Adjust only sigma, radius 0 is adjusted automatically.
                      If sigma2 is set to 0, --blur will compare the source
                        with a blurred version of itself directly.
                      gaussian enables slower but more accurate gaussian blur.
                      Compare quite similar but slower option --dog.
 --cmd [=ARG]         Custom ImageMagick option to create a mask.
                      Compare --cmddiff.
                      ARG additionally takes argument:
                        (word)   string
                      string can be e.g. '-edge 2'. Avoid comma , in string.
                      There's also a use case without a command at all
                      but setting arguments C=,c only.
                      https://imagemagick.org/script/command-line-options.php
 --cmddiff [=ARG]     Custom ImageMagick option to create a mask.
                      The resulting mask will be a '-compose Difference' 
                      comparision from command result with source image.
                      There's also a use case without a command at all
                      but setting arguments C=,c or I= only.
                      ARG additionally takes argument:
                        (word)   string
                      string can be e.g. '-blur 0x5'. Avoid comma , in string.
                      https://imagemagick.org/script/command-line-options.php
 --comet [=ARG]       Comet edge detection. Compares rotated mean areas.
                      ARG additionally takes arguments: 
                        r        radius            Default: $(maskarg_defaultvalue comet radius1)
                        s        sigma             Default: $(maskarg_defaultvalue comet sigma1)
                        (word)   mode              Default: $(maskarg_defaultvalue comet word1)
                      mode can be one of 'magick -list compose'
                      Recommended: Lighten, Darken.
                      Adjust only sigma, radius 0 is adjusted automatically.
 --compass [=ARG]     Compass edge detection.
                      ARG additionally takes argument:
                        (word)   mode              Default: $(maskarg_defaultvalue compass word1)
                      mode can be one of 'magick -list compose'
 --diffstat [=ARG]    Compares two local statistics of source images. 
                      Uses ImageMagick option -statistic. Compare --statistic.
                      ARG additionally takes arguments: 
                        r        radius            Default: $(maskarg_defaultvalue diffstat radius1)
                        (word)   mode1             Default: $(maskarg_defaultvalue diffstat word1)
                        (word)   mode2             Default: $(maskarg_defaultvalue diffstat word2)
                      mode1 and mode2 can be two of 'magick -list evaluate'.
 --dog [=ARG]         Difference of Gaussian.
                      ARG additionally takes arguments:
                        r        radius            Default: $(maskarg_defaultvalue cutblur radius1)
                        s        sigma1            Default: $(maskarg_defaultvalue cutblur sigma1)
                        S        sigma2            Default: 1.6 * sigma1
                      Adjust only sigma, radius 0 is adjusted automatically.
                      Compare https://www.imagemagick.org/Usage/convolve/#dog
                      Quite similar but faster is option --blur.
 --fft [=ARG]         Discrete fourier transformation.
                      This option is in development and might change in future.
                      The arguments configure a mask that is multiplied with
                      the fourier magnitude part.
                      ARG additionally takes arguments:
                        r        radius1           Default: $(maskarg_defaultvalue fft radius1)
                        R        radius2
                        s        sigma1
                        p        percent           Default: $(maskarg_defaultvalue fft percent1)
                        N        number2           Default: $(maskarg_defaultvalue fft number2)
                        (word1)  shape conversion  Default: $(maskarg_defaultvalue fft word1)
                        (word2)  shape form        Default: $(maskarg_defaultvalue fft word2)
                      radius1 sets the size of the mask shape. Low values
                        increase contrast, high values increase detail.
                        The radius is a percent value relative to image size.
                      radius2 creates a second result compared with the first.
                      word1 defines the shape conversion. Possible:
                        gradient gaussian logVALUE powVALUE solid
                        For VALUE replace VALUE with a value like
                          2 10 100 1000, e.g. log2, log100, log1000, pow2, pow0.5
                      word2 is the mask shape. Possible word2:
                          circle square rhombus cross crossx
                      sigma applies -sigmoidal-contrast to the shape.
                        Negative values apply a +sigmoidal-contrast.
                        For 'solid' shape it defines a blur sigma instead.
                      percent1 sets the mid-point for -sigmoidal-contrast.
                        No effect for 'solid' shapes.
                      number2 gives the choice between low and high pass filter:
                        0  low pass filter
                        1  high pass filter
 --freichen [=ARG]    Frei-Chen edge detection.
                      ARG additionally takes arguments: 
                        n        mode number       Default: $(maskarg_defaultvalue freichen number1)
                        (word1)  compose mode      Default: $(maskarg_defaultvalue freichen word1)
                      Possible mode numbers at:
                      http://www.imagemagick.org/Usage/convolve/#freichen
                      Composen mode can be one of 'magick -list compose'.
                      No useful settings known other than default.
 --kirsch [=ARG]      Kirsch edge detection. 
 --laplacian [=ARG]   Laplacian edge detection.
                      ARG additionally takes argument: 
                        n        mode number       Default: $(maskarg_defaultvalue laplacian number1)
                      Possible mode numbers at:
                      http://www.imagemagick.org/Usage/convolve/#laplacian
 --log [=ARG]         Laplacian of Gaussian. Needs rework, not useful yet.
                      ARG additionally takes arguments: 
                        r        radius            Default: $(maskarg_defaultvalue log radius1)
                        s        sigma1            Default: $(maskarg_defaultvalue log sigma1)
                        S        sigma2
                      Adjust only sigma, radius 0 is adjusted automatically.
                      If sigma2 is set, -log gives the difference of
                      two log results with sigma1 and sigma2.
 --morphology [=ARG]  Morphology edge detection.
                      ARG additionally takes arguments:
                        r        radius            Default: $(maskarg_defaultvalue morphology radius1)
                        R        kernel iteration  Default: $(maskarg_defaultvalue morphology radius2)
                        (word1)  mode              Default: $(maskarg_defaultvalue morphology word1)
                        (word2)  kernel            Default: $(maskarg_defaultvalue morphology word2)
                      mode can be one of 'magick -list morphology'.
                        try: edge, edgein, edgeout, dilate, smooth.
                      kernel can be one of 'magick -list kernel'.
                      Compare: https://imagemagick.org/Usage/morphology
 --prewitt [=ARG]     Prewitt edge detection.
 --resize [=ARG]      Resize image to percent and back to normal size.
                        p        percent           Default: $(maskarg_defaultvalue resize percent1)%
                        (word)   interpolation     Default: $(maskarg_defaultvalue resize word1)
                      interpolation can be one of 'magick -list interpolate'.
 --roberts [=ARG]     Roberts edge detection.
 --sobel [=ARG]       Sobel edge detection.
 --statistic [=ARG]   ImageMagick option -statistic.
                      ARG additionally takes arguments: 
                        r        radius1           Default: $(maskarg_defaultvalue statistic radius1)
                        R        radius2
                        (word)   mode              Default: $(maskarg_defaultvalue statistic word1)
                      mode can be one of 'magick -list statistic'.
                      If radius2 is set, --statistic gives the difference of
                      two statistic results with radius1 and radius2.
 --wavelet [=ARG]     Based on ImageMagick option -wavelet-denoise.
                      ARG additionally takes arguments:
                        p        percent1          Default: $(maskarg_defaultvalue wavelet percent1)%
                        P        percent2
                      For percent value compare option -wavelet-denoise.
                      If percent2 is set, --wavelet gives the difference of
                      two wavelet results with percent1 and percent2.

  Colorspace channel masks:
    All channel options are variations of --channel.
    Adding some of them with a rather low weight between w5..w25
    can help to close gaps where edges to detect focus are rare.
    Most useful are --darkness and --saturation.
    Changing argument level is worth a try.

 --channel [=ARG]     Use a colorspace channel as mask.
                      ARG takes the general mask arguments:
                        w        weight
                        C=       colorspace
                        c        colorspace channel
                        neg      negate
                        level    leveling          Default: $(maskarg_defaultvalue channel level)
                        mask     --mask*           Default: $(maskarg_defaultvalue channel mask)
                      Colorspace can be one of 'magick -list colorspace'
                      The color channel is a number from 0..31.
                      Compare 'magick -list colorspace'.
 --chroma [=ARG]      Chroma, color strength. Same as --channel=C=HCL,c1
                      ARG takes the general mask arguments:
                        w        weight
                        level    leveling          Default: $(maskarg_defaultvalue chroma level)
                        mask     --mask*           Default: $(maskarg_defaultvalue channel mask)
 --darkness [=ARG]    Darkness. Same as --channel=C=HSL,c2,neg
                      ARG takes the general mask arguments:
                        w        weight
                        level    leveling          Default: $(maskarg_defaultvalue darkness level)
                        mask     --mask*           Default: $(maskarg_defaultvalue channel mask)
 --lightness [=ARG]   Lightness. Same as --channel=C=HSL,c2
                      ARG takes the general mask arguments:
                        w        weight
                        level    leveling          Default: $(maskarg_defaultvalue lightness level)
                        mask     --mask*           Default: $(maskarg_defaultvalue channel mask)
 --saturation [=ARG]  Color saturation. Same as --channel=C=HSL,c1
                      ARG takes the general mask arguments:
                        w        weight
                        level    leveling          Default: $(maskarg_defaultvalue saturation level)
                        mask     --mask*           Default: $(maskarg_defaultvalue channel mask)

  Image comparision masks:
    Adding --compose with e.g. =Overlay with a rather low weight
    between w5..w25 can help to close gaps, especially in large dark areas.

 --compose [=ARG]     Composes evaluated min,max of source images with MODE.
                      ARG takes the general mask arguments:
                        level    leveling          Default: $(maskarg_defaultvalue compose level)
                        mask     --mask*           Default: $(maskarg_defaultvalue compose mask)
                      ARG additionally takes argument:
                        (word)   mode              Default: $(maskarg_defaultvalue compose word1)
                      Adding a '2', e.g. '--compose=overlay2' swaps min,max
                      to max,min and can give a different result.
                      mode can be one of 'magick -list compose'
                      Some modes of interest:
                        overlay interpolate colordodge hardlight reflect 
                        softburn softlight linearlight pegtoplight
 --depthmap [=ARG]    Use a previously created depth map to get focus areas.
                      ARG takes the general mask arguments:
                        level    leveling          Default: $(maskarg_defaultvalue depthmap level)
                        mask     --mask*           Default: $(maskarg_defaultvalue depthmap mask)
                      ARG additionally takes argument:
                        (word)   filename
                      Yet works well only if no --cut* options except
                      --cutless has been used for the depth map.
                      Sample use: imfuse creates a depth map with -X, you can
                      change it in an image editor, and use this option to
                      create the focus image based on the adjusted depth map.
 --enfuse [=ARG]      Image comparision with a basic enfuse result.
                      ARG takes the general mask arguments:
                        level    leveling          Default: $(maskarg_defaultvalue enfuse level)
                        mask     --mask*           Default: $(maskarg_defaultvalue enfuse mask)
 --evaluate [=ARG]    Evaluates from all source images with mode MODE and
                      compares the result with each source image.
                      ARG takes the general mask arguments:
                        level    leveling          Default: $(maskarg_defaultvalue evaluate level)
                        mask     --mask*           Default: $(maskarg_defaultvalue evaluate mask)
                      ARG additionally takes arguments:
                        (word)   mode              Default: max
                      mode can be one of 'magick -list evaluate'
                      Compare --max, --min, --mean, --median.
 --max [=ARG]         Same as --evaluate=max. Brightest pixels of stack.
                      Result is similar to --lightness.
                      ARG takes the general mask arguments:
                        level    leveling          Default: $(maskarg_defaultvalue max level)
                        mask     --mask*           Default: $(maskarg_defaultvalue max mask)
 --mean [=ARG]        Same as --evaluate=mean. Average of pixels in stack.
                        level    leveling          Default: $(maskarg_defaultvalue mean level)
                        mask     --mask*           Default: $(maskarg_defaultvalue mean mask)
 --median [=ARG]      Same as --evaluate=median. Median pixel of stack.
                        level    leveling          Default: $(maskarg_defaultvalue median level)
                        mask     --mask*           Default: $(maskarg_defaultvalue median mask)
 --min [=ARG]         Same as --evaluate=min. Darkest pixels of stack.
                      Result is similar to --darkness.
                        level    leveling          Default: $(maskarg_defaultvalue min level)
                        mask     --mask*           Default: $(maskarg_defaultvalue min mask)

Step 3: Mask merging
  The contrast masks generated in step 2 are merged into one per image.
  The merged result can be adjusted with --mask* options.
  Most recommended: --maskblur and --maskwave.

  These options can be specified multiple times, the order matters:

 --maskblur [=ARG]    Blur masks to enhance contrast and close minor gaps.
                      ARG takes argument:
                        r        radius            Default: $(maskarg_defaultvalue cutblur radius1)
                        s        sigma             Default: $(maskarg_defaultvalue cutblur sigma1)
                        p        percent1          Default: 100%
                        P        percent2          Default: 100-percent1%
                        (word)   gaussian
                      Adjust only sigma, radius 0 is adjusted automatically.
                      percent1 defines how strong the blur will be applied.
                      percent2 defines how strong the original we remain.
                      gaussian enables slower but more accurate gaussian blur.
                      Compare ImageMagick option -blur.
 --maskcmd [=ARG]     Custom ImageMagick option to apply on merged masks.
                      ARG takes argument:
                        (word)   string
                      string can be e.g. '-blur 0x5'. Avoid comma , in string.
                      https://imagemagick.org/script/command-line-options.php
 --maskenhance [=ARG] Remove noise from mask.
                      ARG takes argument:
                        n        iterations        Default: $(maskarg_defaultvalue maskenhance number1)
                      Compare ImageMagick option -enhance.
 --maskdespeckle [=ARG] Remove noise from mask.
                      ARG takes argument:
                        n        iterations        Default: $(maskarg_defaultvalue maskdespeckle number1)
                      Compare ImageMagick option -despeckle.
 --maskfft [=ARG]     Discrete fourier transformation.
                      This option is in development and might change in future.
                      ARG additionally takes arguments:
                        r        radius1           Default: $(maskarg_defaultvalue fft radius1)
                        s        sigma1
                        p        percent           Default: $(maskarg_defaultvalue fft percent1)
                        N        number2           Default: $(maskarg_defaultvalue fft number2)
                        (word1)  shape conversion  Default: $(maskarg_defaultvalue fft word1)
                        (word2)  shape form        Default: $(maskarg_defaultvalue fft word2)
                      See option --fft for arguments.
 --maskkuwahara [=ARG]  Noise removal with kuwahara method.
                      ARG takes argument:
                        r        radius            Default: $(maskarg_defaultvalue maskkuwahara radius1)
                      Compare ImageMagick option -kuwahara.
 --maskmorph [=ARG]   Change shape of mask. 
                      ARG takes arguments:
                        r        radius            Default: $(maskarg_defaultvalue maskmorph radius1)
                        R        kernel iteration  Default: $(maskarg_defaultvalue maskmorph radius2)
                        (word1)  mode              Default: $(maskarg_defaultvalue maskmorph word1)
                        (word2)  kernel            Default: $(maskarg_defaultvalue maskmorph word2)
                      radius is the kernel radius.
                      kernel iteration multiplies the kernel radius.
                      mode can be one of 'magick -list morphology'.
                        try: open, close, erode, dilate, smooth.
                      kernel can be one of 'magick -list kernel'.
                      CPU expensive option. Iterating the kernel is cheaper
                      than using a greater radius.
                      Compare: https://imagemagick.org/Usage/morphology
 --maskstat [=ARG]    Apply a statistic method for each mask pixel
                      to adjust it according to its neighborhood.
                      Option is applied to contrast masks only.
                      ARG takes arguments:
                        r        radius            Default: $(maskarg_defaultvalue maskstat radius1)
                        (word)   statistic mode    Default: $(maskarg_defaultvalue maskstat word1)
                      word1 can be one of 'magick -list statistic'.
                        Modes of interest: mean, median, gradient.
                          Mode mean softens the result and closes gaps.
                          Mode median sharpens the result and opens gaps.
                          Mode gradient sharpens the result, closes gaps
                            and adds noise in weak areas.
                      Compare ImageMagick option -statistic.
 --maskthreshold [=ARG]  Remove low (or high) contrast area.
                      ARG takes arguments:
                        t        threshold1        Default: $(maskarg_defaultvalue maskthreshold threshold1)%
                        T        threshold2        Default: $(maskarg_defaultvalue maskthreshold threshold2)%
                      threshold1 removes low contrast areas below a percent.
                      threshold2 removes high contrast areas above a percent.
 --maskwave [=ARG]    Remove noise in masks. Can close gaps, but can also
                      create artefacts in very low contrast areas.
                      ARG takes arguments:
                        p        percent           Default: $(maskarg_defaultvalue maskwave percent1)%
                      Compare ImageMagick option -wavelet-denoise.

  These options can be specified only once, order does not matter:

 --maskmerge [=ARG]   Compose mode to merge multiple masks.
                      ARG takes arguments:
                        (word)   mode              Default: $(maskarg_defaultvalue maskmerge word1)
                      mode can be one of 'magick -list compose'.
                      Of interest: Plus Interpolate Multiply Exclusion Blend
 --masklevel [=ARG]   Levels merged masks into visible spectrum from 0%..100%
                      to provide the following focus and postprocessing option
                      arguments a full range from 0%..100%. Enabled by default.
                      ARG takes argument:
                        t        round threshold   Default: $(maskarg_defaultvalue masklevel threshold1)%
                        p        min
                        P        max
                        (word)   mode              Default: $(maskarg_defaultvalue masklevel word1)
                      mode can be one of:
                        all:      Level all masks.
                        substack: Level only merged masks in current substack.
                        off:      Do not level masks.
                      The round threshold rounds up or down to the next matching
                       rounded percent value. This helps to get same results
                       for cropped image parts as well as for whole images.
                      mode 'substack' should only be used to speed up test runs
                       with different mask merging options within one single
                       substack. Only mode 'all' reliably provides valid values.
                      percent values min and max allow to set absolute values.
                      This option is always executed as the last one of --mask*.

Step 4: Stack fusion
  The source images are processed with the merged masks,
  the overall sharp image will be generated.
  For each source image a cut mask is generated based on the merged masks
  and the following --cut* options.

  The order of --cut* options matters, use --cutless always first.
  All --cut* options except --cutless can be specified multiple times.

  Recommended combination: --cutwave --cutbg --cutalpha
  To strengthen foreground objects use --cutless.

 --cutalpha [=ARG]    Make cut semitransparent according to mask.
                      Nice result if used after --cutwave. Use --cutbg before.
                      ARG takes arguments:
                        p        percent1          Default: $(maskarg_defaultvalue cutalpha percent1)%
                        P        percent2          Default: $(maskarg_defaultvalue cutalpha percent2)%
                      For percent arguments compare --finalalpha.
                      If the result is too soft, try 'p-25,P150'.
 --cutbg [=ARG]       Create a background from current cuts.
                      To be used before --cutthreshold or --cutalpha.
                      ARG takes argument:
                        (word)   mode              Default: $(maskarg_defaultvalue cutbg word1)%
                      Possible modes:
                        background   Use as background, applied after --final*.
                        compose      Add to result before --final* options.
                        off          Store background, but do not show it.
 --cutblur [=ARG]     Blur cut masks in final focus montage.
                      ARG takes argument:
                        r        radius            Default: $(maskarg_defaultvalue cutblur radius1)
                        s        sigma             Default: $(maskarg_defaultvalue cutblur sigma1)
                        p        percent1          Default: 100%
                        P        percent2          Default: 100-percent1%
                        (word)   gaussian
                      Adjust only sigma, radius 0 is adjusted automatically.
                      percent1 defines how strong the blur will be applied.
                      percent2 defines how strong the original we remain.
                      gaussian enables slower but more accurate gaussian blur.
 --cutcmd [=ARG]      Custom ImageMagick option to apply on cut mask.
                      ARG takes argument:
                        (word)   string
                      string can be e.g. '-blur 0x5'. Avoid comma , in string.
                      https://imagemagick.org/script/command-line-options.php
 --cutless [=ARG]     Strenghtens less contrasted objects in front of strong
                      contrasted background objects.
                      ARG takes argument:
                        p        percent           Default: $(maskarg_defaultvalue cutless percent1)%
                      Percent is the minimal intermediate contrast
                      difference between background and foreground object.
                      If unsharp areas appear, use a greater percent value.
                      If the foreground object still has holes, use a lower 
                      percent value.
                      Useful percent values heavily depend on previous options.
                      Can be specified only once, the order matters.
 --cutmax [=ARG]      Remove parts of current cut mask that are weaker than
                      current max in result mask.
                      ARG takes argument:
                        p        percent           Default: 0
                      The percent value preserves some weak cut mask parts.
 --cutmorph [=ARG]    Change shape of cut mask. Default mode 'erode' shrinks
                      the cut mask to eliminate undesired fringes.
                      ARG takes arguments:
                        r        radius            Default: $(maskarg_defaultvalue cutmorph radius1)
                        R        kernel iteration  Default: $(maskarg_defaultvalue cutmorph radius2)
                        (word1)  mode              Default: $(maskarg_defaultvalue cutmorph word1)
                        (word2)  kernel            Default: $(maskarg_defaultvalue cutmorph word2)
                      radius is the kernel radius.
                      kernel iteration multiplies the kernel radius.
                      mode can be one of 'magick -list morphology'.
                        try: open, close, erode, dilate, smooth.
                      kernel can be one of 'magick -list kernel'.
                      CPU expensive option. Iterating the kernel is cheaper
                      than using a greater radius.
                      Compare: https://imagemagick.org/Usage/morphology
 --cutsoft [=ARG]     Similar to --cutblur, but paint default sharp cut mask
                      over blurred cut mask.
                      ARG takes argument:
                        s        sigma             Default: $(maskarg_defaultvalue cutsoft sigma1)
 --cutthreshold [=ARG]  Cut of parts of cut mask where current source mask is
                      weaker than threshold. Removes undesired seams, but also
                      the background. Preserve background with --cutbg before.
                      ARG takes arguments:
                        t        threshold         Default: $(maskarg_defaultvalue cutthreshold threshold1)%
 --cutwave [=ARG]     Soften the cut mask. Result looses a bit of sharpness,
                      but looks more friendly overall, and covers small issues.
                      ARG takes arguments:
                        p        percent           Default: $(maskarg_defaultvalue cutwave percent1)%
                      For percent value compare option -wavelet-denoise.

 --substacks [=ARG]   Split stack into different stacks (called substacks)
                      and fuse them separately. Can be specified multiple times.
                      ARG takes arguments:
                        r        radius            Default: 5%
                        p        percent1          Default: 14%
                        P        percent2
                        n        number1
                        N        number2
                        (word)   kurt
                      You can specify single substacks or generate a set of 
                      substacks. Please specify either percents or numbers.
                      - Given only one of percent or number, imfuse will
                        generate a set of substacks accordingly.
                         - word 'kurt' starts all substacks with image 1.
                      - If you specify two of percents or numbers, one single
                        substack within the given range of images is generated.
                      - radius specifies how many images neigboured substacks
                        should share. A percent value for radius is allowed.
                      Use --threshold or --alpha for transparent substacks.
                      The substacks will be composed over each other onto
                      a background (to specify with --background).
                      See also --layered to store the substacks as layers.
                      That allows manual rework of the results in gimp.

Step 5: Post processing
  The order of the options on cli matters. Noteably --alpha with percent values
  different from =0%,100% affects the following percent arguments.nd to have
  a good start for --finalalpha.
  All options can be specified multiple times.

 --finalalpha [=ARG]  Generate transparent image using the contrast mask as 
                      alpha channel (=transparency channel).
                      The semitransparent result is composed over --background.
                      Compare similar but often better option --cutalpa.
                      ARG takes arguments:
                        p        percent1          Default: $(maskarg_defaultvalue finalalpha percent1)%
                        P        percent2          Default: $(maskarg_defaultvalue finalalpha percent2)%
                        (word)   off
                      percent1 affects the low contrast areas with
                       a useful range of -100%..+50%.
                      percent2 affects the high contrast areas with
                       a useful range of +100%..+200%.
                      Higher values make less transparent.
                       The percent values are applied as '+level p%,P%'
                       on the transparency alpha channel.
                      If word 'off' is set, the alpha channel is disabled
                       without further action.
 --finalblur [=ARG]   Soft blur of bokeh in low contrast areas.
                      ARG takes arguments:
                        t        threshold         Default: $(maskarg_defaultvalue finalblur threshold1)%
                        s        sigma1            Default: $(maskarg_defaultvalue finalblur sigma1)
                        S        sigma2            Default: $(maskarg_defaultvalue finalblur sigma2)
                      Blurs low contrast areas below threshold with sigma1.
                      If sigma2 is given, the cut border between bokeh and 
                      foreground is blurred for a soft transition.
 --finalblur2 [=ARG]  Soft blur of bokeh in low contrast areas.
                      Other than --finalblur it blurs according to the contrast
                      mask strengh. Low contrast is blurred more than high.
                      ARG takes arguments:
                        r        radius1           Default: $(maskarg_defaultvalue finalblur2 radius1)
                        t        threshold         Default: $(maskarg_defaultvalue finalblur2 threshold1)%
                      A greater radius1 blures more.
                      threshold restricts blur to contrast mask strength.
 --finalcmd [=ARG]    Custom ImageMagick option to apply on result image.
                      ARG takes argument:
                        (word)   string            Default: $(maskarg_defaultvalue finalcmd word1)
                      string can be e.g. '-auto-level'. Avoid comma , in string.
                      https://imagemagick.org/script/command-line-options.php
 --finalgamma         Set gamma of image. Can improve contrast.
                        (word)   value/auto        Default: $(maskarg_defaultvalue finalgamma word1)
                      Gamma less than 1.0 darkens the image,
                      and gamma greater than 1.0 lightens the image.
                      'auto' sets gamma automatically.
                      Compare ImageMagick options -gamma and -auto-gamma.
 --finalsharpen [=ARG] Sharpen result image.
                      ARG takes arguments:
                        r        radius            Default: $(maskarg_defaultvalue finalsharpen radius1)
                        s        sigma             Default: $(maskarg_defaultvalue finalsharpen sigma1)
                      Adjust only sigma, radius 0 is adjusted automatically.
                      Compare ImageMagick option -adaptive-sharpen.
 --finalthreshold [=ARG]  Make low (or high) contrast area transparent.
                      ARG takes arguments:
                        t        threshold1        Default: $(maskarg_defaultvalue finalthreshold threshold1)%
                        T        threshold2        Default: $(maskarg_defaultvalue finalthreshold threshold2)%
                        s        sigma             Default: $(maskarg_defaultvalue finalthreshold sigma1)
                      threshold1 removes low contrast areas below a percent.
                      threshold2 removes high contrast areas above a percent.
                      sigma blurs the cut border.

Step 6: Background

 --bg, --background [=ARG]  Specify a background to paint on.
                      Useful for (semi-)transparent results or as a result
                      on its own.
                      ARG takes argument:
                        (word)   background        Default: $(maskarg_defaultvalue bg word1)
                      background can be one out of:
                        wave wavedark enfuse
                        transparent
                        magick -list color
                        magick -list evaluate (compare --evaluate)
                        magick -list compose  (compare --compose)
                      Try:  enfuse mean max min
                            colorize colorize2 pinlight pinlight2
                            interpolate overlay pegtoplight reflect
                      Can be specified multiple times for option --layered.

Much thanks to the developers and supporters of ImageMagick!
Without them this project would not have been possible.
A first start to get an idea of the stackshot image fusing workflow was
inspired by Alan Gibson's focStack.bat at https://im.snibgo.com/focstack.htm .
imfuse also contains code from Fred Weinhaus with his friendly permisson.

Version: imfuse v$Version
Author:  Martin Viereck, Germany
License: GPLv2 https://www.gnu.org/licenses/old-licenses/gpl-2.0.html#SEC1
Website: https://github.com/mviereck/microscopy-tools
"
}
license() {
  echo "
imfuse - Combines focus stackshot images to one overall sharp image.
Copyright (C) 2022  Martin Viereck

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software Foundation,
    Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

imfuse website and contact: https://github.com/mviereck/microscopy-tools
"
}

### Messages

error() {
  echo "
imfuse$Subprocess ERROR: $*
" >&2
  kill -s SIGINT "$Imfusepid"
}
note() {
  echo "imfuse$Subprocess: $*" >&2
  return 0
}
verbose() {
  [ "$Verbose" = "yes" ] && echo "imfuse$Subprocess: $*" >&2
  return 0
}
showimage() {
  local Frame
  [ "$Showimageprocessing" = "yes" ] && geeqie -t -r --File:"${1:-}" 2>/dev/null &
  disown $!
  [ "$Video" ] && {
#    Videoframecount="$((Videoframecount+1))"
    Videoframecount="$(ls "${Cachedir}"/videoframe????.* | sort -V | tail -n1)"
    Videoframecount="$(basename "$Videoframecount")"
    Videoframecount="${Videoframecount//[^0-9]/}"
    Videoframecount="$(sed "s/^0*//" <<< "$Videoframecount")"
    Videoframecount="$(calc "$Videoframecount+1")"
    Frame="${Cachedir}/videoframe$(printnum "$Videoframecount").tif"
    #ln -s "${1:-}" "$Frame"
    cp "${1:-}" "$Frame" ### FIXME ln -s where possible
  }
  return 0
}
showviewnior() {
  local W H
  [ "$Viewnior" ] && {
    #viewnior "$@" >/dev/null 2>&1 &
    W=900
    H=900
    compare "$Imagewidth"  -lt "$W" && W="$Imagewidth" 
    compare "$Imageheight" -lt "$H" && H="$Imageheight" 
    nohup feh --draw-tinted --zoom="max" --keep-zoom-vp \
              --geometry="${W}x${H}+37+0" \
              --title="$Sourcemd5:  $Parsedoptions" \
              --info="exiftool -imagedescription %F | cut -d: -f2- | fold -s -w $((W/7))" \
              "$@" </dev/null >/dev/null 2>&1 &
    disown $!
  }
  return 0
}
showresult() {
  showimage "$Resultimage"
  #command -v xclip >/dev/null && echo -n "$Resultimage" | xclip -i -selection clipboard
  notify-send "imfuse is ready" 2>/dev/null ||:
  case "$Extendedsave" in
    yes)
      showviewnior "$Resultimage" "$Resultmask" "$Resultdepthmap"
    ;;
    no)
      showviewnior "$Resultimage"
    ;;
  esac
  echo "$Resultimage"
}
traperror() {
  error "traperror($Subprocess): Command at Line ${2:-} returned with error code ${1:-}:
  ${4:-}
  ${3:-} - ${5:-}"
}

### Misc

calc() {
  # float calculation of $* with awk
  # first awk calculates, second awk removes trailing zeros.
  LC_ALL=C awk "BEGIN { OFMT=\"%f\"; print $**1 ; }" | awk '{ if ($0 ~ /\./){ sub("0*$","",$0); sub ("\\.$","",$0);} print }'
}
checkmagicklist() {
  local Check
  [ "${3:-}" = "print" ] && Check="" || Check="-q"
  $Magickbin -list "${1:-}" | grep -w -i "^${2:-XXX}" | head -n1 | awk '{print $1}' | grep $Check -w -i "^${2:-XXX}"
}
compare() {
  # compare floating number with < > =
  local Arg1 Arg2 Operator
  Arg1="${1:-}"
  Operator="${2}"
  Arg2="${3:-}"
  case "$Operator" in
    "<"|"lt"|"-lt")      Operator="<" ;;
    ">"|"gt"|"-gt")      Operator=">" ;;
    "="|"eq"|"-eq"|"==") Operator="==" ;;
  esac
  [ -n "$(LC_ALL=C awk "BEGIN{if ($Arg1 $Operator $Arg2) print \"yes\"}")" ]
}
digitonly() {
  #sed "s/[^0-9.]//g" <<< "${1:-}"
  echo "${1//[^0-9.]/}"
}
digitrm() {
  #sed "s/[0-9.]//g" <<< "${1:-}"
  echo "${1//[0-9.-]/}"
}
generate_key() {
  # generate a unique key value from current time and nanoseconds
  date +%s.%N
}
lowercase() {
    # Usage: lowercase "string"
    printf '%s\n' "${1,,}"
}
md5cut() {
  # print last 6 digits
  grep -v "#" <<< "$*" | tr "\n" " " | md5sum | cut -c27-32
}
numberofpercent() {
  Number="${1:-}"
  Number="${Number//%}"
  Number="$((Number*Sourceimagenumber/100))"
  [ "$Number" -lt "1" ]            && Number=1
  [ "$Number" -gt "$Sourceimagenumber" ] && Number="$Sourceimagenumber"
  echo "$Number"
}
percentrm() {
  # remove % from string
  #sed s/%//g <<< "${1:-}"
  echo "${1//%/}"
}
printnum(){
  # print number $1 with leading zeroes.
  # $1 number
  # $2 digits. Default: 4
  [ "${1:-}" = "NUMBER" ] && echo "NUMBER" && return 0 
  printf "%0${2:-4}d" "${1:-0}"
}
printsameline() {
  # print $1 without newline at begin of current line
  echo -ne "${1:-}\033[0K\r" >&2
}
printtotalmemory() {
  # print total memory including zram
  local Memory Line Zram
  Memory="$(LC_ALL=C free | grep "Mem:" | LC_ALL=C awk '{print $2}')"
  while read Line; do
    Zram="$(LC_ALL=C awk 'BEGIN {OFMT = "%.0f"} {print $3}' <<< "$Line")"
    Zram="$(LC_ALL=C awk 'BEGIN {OFMT = "%.0f"} {print $1 / 1000}' <<< "$Zram")"
    Memory="$((Memory + Zram))"
  done < <(/sbin/swapon --bytes | grep zram ||:)
  echo $Memory
}
unspecialstring() {
  # Replace all characters except those described in tr string with a '-'.
  printf %s "${1:-}" | LC_ALL=C tr -c "a-zA-Z0-9.,=-_" "-"
}

### Debugging helpers

forcemask() {
  note "Forcing mask generation"
  rm -f "${Cachedir}"/${Sourcemd5}.mask.*
}
forcelevel() {
  note "Forcing leveling"
  rm -f "${Cachedir}"/level*
}
forcemerge() {
  note "Forcing merge"
  rm -f "${Cachedir}"/${Sourcemd5}.mask.merge*
}
forcefocus() {
  note "Forcing focus"
  rm -f "${Cachedir}"/${Sourcemd5}.focus*
  rm -f "${Cachedir}"/${Sourcemd5}.substack*
}
forcepost() {
  note "Forcing focus postprocessing"
  rm -f "${Cachedir}"/${Sourcemd5}.substack*.final.*
}

#### run commands on all CPUs

multicore() {
  # Run multiple processes in parallel, but not more than $Multicore_maxprocesses
  # $1 Command
  # $2 Image to show if $1 is finished
  # $3 Memory needed by the process
  # Run multicore_wait afterwards to wait for the last processes to finish.

  local Process Command
  local Mem_needed Zram

  [ "${1:-}" = "-t1" ] && {
    shift
    [ "$Multicore_processcount" -gt 0 ] && {
      multicore_wait || return 1
    }
  }

  [ "$Multicore_processcount" = "$Multicore_maxprocesses" ] && {
    multicore_wait || return 1
  }
  [ "$Multicore_processcount" = "0" ] && {
    Multicore_memorymax="$(printfreememory)"
    Multicore_memorymax="$((Multicore_memorymax*8/10))"
  }
  Mem_needed=0
  for Process in $(seq $Multicore_maxprocesses); do
    Mem_needed="$(awk "BEGIN {print $Mem_needed + ${Multicore_memory[$Process]:-0} }" )"
  done
  Mem_needed="$((Mem_needed+${3:-0}))"
  [ "$Mem_needed" -gt "$Multicore_memorymax" ] && {
    note "multicore: Low memory. Waiting for $Multicore_processcount running processes to finish. Need: $(( ${3:-0}/1000 )) (overall $((Mem_needed/1000))) MB, Available: $((Multicore_memorymax/1000)) MB"
    [ "$Mem_needed" -gt "$Multicore_memorymax" ] && [ "$Multicore_processcount" = "0" ] && note "multicore: Likely hard disk cache will be used and slow down the calculation."
    multicore_wait || return 1
  }

  ifcmdbreak && return 1

  Multicore_processcount=$((Multicore_processcount +1))

  Command="$(cut -d ' ' -f1 <<< "${1:-}")"
  case $(type -t "$Command") in
    file) Command="nice ${1:-}" ;;
    *)    Command="${1:-}" ;;
  esac

  #verbose "multicore: ${1:-}"
  eval "$Command &"

  Multicore_process[Multicore_processcount]=$!
  Multicore_image[Multicore_processcount]="${2:-}"
  Multicore_memory[Multicore_processcount]="${3:-0}"

  return 0
}
multicore_wait() {
  local Process Error=
  for Process in $(seq ${Multicore_maxprocesses:-0}); do
    [ "${Multicore_process[$Process]}" ] && {
      multicore_waitprocess "${Multicore_process[$Process]}" || {
        multicore_break
        Error=1
      }
      [ "$Error" ] && break
      [ "${Multicore_image[$Process]}" ] && showimage "${Multicore_image[$Process]}"
    }
    Multicore_process[$Process]=""
    Multicore_image[$Process]=""
    Multicore_memory[$Process]="0"
  done
  [ "$Error" ] && return 1
  Multicore_processcount=0
  return 0
}
multicore_waitprocess() {
  local Error=
  while sleep 0.2 ; do
    ps -p "${1:-}" >/dev/null || break
    ifcmdbreak && Error=1
    [ "$Error" ] && break
  done
  [ "$Error" ] && return 1
  wait "${1:-}"
  return $?
}
multicore_break() {
  local Process
  for Process in $(seq ${Multicore_maxprocesses:-0}); do
    [ "${Multicore_process[$Process]}" ] && {
      verbose "multicore_break: Sending SIGINT to $(ps -p ${Multicore_process[$Process]})"
      kill "${Multicore_process[$Process]}"
      wait "${Multicore_process[$Process]}"
      Multicore_process[$Process]=""
      Multicore_image[$Process]=""
      Multicore_memory[$Process]="0"
    }
  done
}
multicore_init() {
  # declare global variables
  local Process
  Multicore_maxprocesses="$(nproc)"
  Multicore_maxprocesses="${Multicore_maxprocesses:-1}"
  Multicore_maxprocesses="$((Multicore_maxprocesses * 2))"
  for Process in $(seq $Multicore_maxprocesses); do
    Multicore_process[$Process]=""
    Multicore_image[$Process]=""
    Multicore_memory[$Process]="0"
  done
  Multicore_processcount=0
  Multicore_minram=250000
  Multicore_maxprocesses=$Multicore_maxprocesses
}
ifcmdbreak() {
  [ -e "${Cachedir}/exit" ]
  #return 1
}
printfreememory() {
  # print current free memory including zram
  local Memory Line Zram
  Memory="$(LC_ALL=C free | grep "Mem:" | LC_ALL=C awk '{print $7}')"
  while read Line; do
    Zram="$(LC_ALL=C awk 'BEGIN {OFMT = "%.0f"} {print ($3 - $4)}' <<< "$Line")"
    Zram="$(LC_ALL=C awk 'BEGIN {OFMT = "%.0f"} {print $1 / 1000}' <<< "$Zram")"
    #Memory="$((Memory + Zram))"
  done < <(/sbin/swapon --bytes | grep zram ||:)
  echo "$Memory"
}

### Files

check_exifstring() {
  # Generate parsed option string for EXIF meta data
  local Count

  Parsedoptions=""
  [ "$Align" = "yes" ] && Parsedoptions="$Parsedoptions --align"

  # step source image preparation
  maskarg_single "colorspace" && Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"
  maskarg_single "prepresize" && Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"

  # step mask generation
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      mask)
        Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"
     ;;
    esac
  done

  # step merge
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      merge)
        Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"
      ;;
    esac
  done
  maskarg_single "maskmerge" && Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"
  maskarg_single "masklevel" && Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"

  # step focus
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      focus)
        Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"
      ;;
    esac
  done

  # step substacks
  [ "$Substackautoall" = "no" ] && for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      substack)
        Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"
      ;;
    esac
  done

  # step postprocessing
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      postfocus)
        Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"
      ;;
    esac
  done

  # step background
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      background)
        Parsedoptions="$Parsedoptions $(maskarg_short "$Argcount")"
      ;;
    esac
  done

  # misc
  [ -n "$Revertimagelist" ]   && Parsedoptions="$Parsedoptions --revert"
  [ "$Storelayered" = "yes" ] && Parsedoptions="$Parsedoptions --layered"
  [ -n "$Testsetup" ]         && Parsedoptions="$Parsedoptions --test=$Testarg"

  Parsedoptions="${Parsedoptions#" "}"
}
check_outputname() {
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      background|postfocus) ;;
      *) Substackmd5="$Substackmd5 ${Imoptions[$Count]}=${Imarguments[$Count]}" ;;
    esac
    case "$Argtype" in
      background) ;;
      *) Finalmd5="$Finalmd5 ${Imoptions[$Count]}=${Imarguments[$Count]}" ;;
    esac
  done
  Substackmd5="$(md5cut "$Sourcemd5 $Substackmd5")"
  Finalmd5="$(md5cut    "$Sourcemd5 $Finalmd5")"
  Optionmd5="$(md5cut "${Imoptions[*]} ${Imarguments[*]} $Testsetup$Testarg")"

  Resultbasename="${Outputbasename}${Sourcemd5}_${Optionmd5}"
  Resultsearchmask="*${Sourcemd5}_${Optionmd5}*"
  [ "$Storelayered" = "yes" ] && Resultbasename="${Resultbasename}_layered"

  [ -n "$Resultimage" ]   && Resultbasename="$(basename "$Resultimage")"
  [ -z "${Outputdir:-}" ] && Outputdir="."

  [ "$Longname" = "yes" ] && Longname="${Parsedoptions// /}" || Longname=""
  case "$Extendedsave" in
    yes)
      Resultdepthmap="${Outputdir}/${Resultbasename}${Longname}.depthmap.${Imageformat}"
      Resultmask="${Outputdir}/${Resultbasename}${Longname}.mask.${Imageformat}"
    ;;
    no) ### FIXME
      Resultdepthmap="${Cachedir}/${Resultbasename}${Longname}.depthmap.tif"
      Resultmask="${Cachedir}/${Resultbasename}${Longname}.mask.tif"
    ;;
  esac
  [ "$Video" ] && Video="${Outputdir}/${Resultbasename}${Longname}.video.webm"
  [ -z "$Resultimage" ] && Resultimage="${Outputdir}/${Resultbasename}${Longname}.result.${Imageformat}"

  Sourceimagepath="$(realpath "${Sourceimagelist[1]}")"
  Sourceimagepath="$(dirname "$Sourceimagepath")"
  Sourceimagepath="${Sourceimagepath/"$HOME"/"~"}"

  return 0
}
load_sourceimages() {
  # Load source images into imagemagick registry
  # (Can also happen in align())
  local Sourceimage Count Number Command
  local Firstimage Lastimage

  Firstimage="${1:-1}"
  Lastimage="${2:-"$Sourceimagenumber"}"

  sendmagickmessage "STOPWATCH"
  for Count in $(seq "$Lastimage" -1 "$Firstimage"); do
    Number="$(printnum "$Count")"

    case "$Loadsourceimages" in
      yes)
        [ -z "${Imsourceimagelist[$Count]:-}" ] && {
          Imsourceimagelist[$Count]="mpr:sourceimage.$(printnum "$Count")"

          sendmagickmessage "PROGRESS:Loading source image: ${Sourceimagelist[$Count]} ETA:$Count"
          Command="
  '${Sourceimagelist[$Count]}'
    -alpha off
    -depth 16
    -write '$(sourceimagename "$Count")'
    -delete 0"
          cmd "$Command"
        }
      ;;
      no)
        Imsourceimagelist[$Count]="${Sourceimagelist[$Count]}"
      ;;
    esac
  done
  [ "$Loadsourceimages" = "yes" ] && sendmagickmessage "/PROGRESS"
  cmd_waitforready

  return 0
}
maskarray() {
  local Basename
  local -n List="${4:-}"
  local Firstimage Lastimage
  Firstimage="${2:-1}"
  Lastimage="${3:-$Sourceimagenumber}"
  Basename="${1:-}"
  for Count in $(seq "$Firstimage" "$Lastimage"); do
    List[$Count]="$(maskname "$Basename" "$Count")"
  done
}
masklist() {
  local Basename List=
  local Firstimage Lastimage
  Basename="${1:-}"
  Firstimage="${2:-1}"
  Lastimage="${3:-$Sourceimagenumber}"
  for Count in $(seq "$Firstimage" "$Lastimage"); do
    List="$List '$(maskname "$Basename" "$Count")'"
  done
  echo "$List"
}
sourceimagename() {
  # print source image name number $1.
  # might be an mpr: or a file depending on $Loadsourceimages.
  echo "${Imsourceimagelist[${1:-}]}"
}
maskexist() {
  local Count
  local Firstimage Lastimage=

  Firstimage="${2:-1}"
  [ -n "${2:-}" ] && [ -z "${3:-}" ] && Lastimage="$Firstimage"
  [ -z "$Lastimage" ]                && Lastimage="${3:-$Sourceimagenumber}"

  case "$Masktocache" in
    yes)
      for Count in $(seq "$Firstimage" "$Lastimage"); do
        [ -e "$(maskname "${1:-}" "$Count")" ] || return 1
      done
      return 0
    ;;
    no)
      return 1
    ;;
  esac
}
maskname() {
  [ -z "${1:-}" ] && error "maskname(): maskbasename is empty: ${1:-} ${2:-}"
  [ -z "${2:-}" ] && error "maskname(): number not given: ${1:-} ${2:-}"
  case "$Masktocache" in
    yes)
      echo "${Cachedir}/${Sourcemd5}.mask.${1:-}.$(printnum "${2:-}").${Cacheformat}"
    ;;
    no)
      echo "mpr:${1:-}.$(printnum "${2:-}")"
    ;;
  esac
}

### image processing helpers

align() {
  # align with focus-stack
  # https://github.com/PetteriAimonen/focus-stack

  local Log Line Image Count Command
  local X Y W H Lmax=0 Tmax=0 Rmin=10000000 Bmin=10000000
  local Aligndir

  Log="${Cachedir}/align.log"
  Aligndir="${Cachedir}/aligned.$Sourcemd5"
  mkdir -p "$Aligndir"

  note "Aligning source images with external tool focus-stack."
  focus-stack --verbose --output="$Aligndir/" --align-only --no-contrast --no-whitebalance "${Sourceimagelist[@]}" >"$Log" || return 1

  # calculate smallest valid area common to all aligned images
  while read Line; do
    Line="$(cut -d' ' -f4- <<< "$Line")"
    X="$(digitonly "$(cut -d, -f1 <<< "$Line")" )"
    Y="$(digitonly "$(cut -d, -f2 <<< "$Line")" )"
    W="$(digitonly "$(cut -d, -f3 <<< "$Line")" )"
    H="$(digitonly "$(cut -d, -f4 <<< "$Line")" )"
    [ "$X" -gt "$Lmax" ] && Lmax="$X"
    [ "$Y" -gt "$Tmax" ] && Tmax="$Y"
    [ "$((X+W))" -lt "$Rmin" ] && Rmin="$((X+W))"
    [ "$((Y+H))" -lt "$Bmin" ] && Bmin="$((X+W))"
  done < <(grep "valid area" "$Log" ||:)
  X="$Lmax"
  Y="$Tmax"
  W="$((Rmin-Lmax-1))"
  H="$((Bmin-Tmax-1))"

  # crop aligned images to valid area, store as mpr source images
  sendmagickmessage "STOPWATCH"
  for Count in $(seq "$Sourceimagenumber" -1 1); do
    Image="${Sourceimagelist[$Count]}"
    Number="$(printnum "$Count")"
    Image="$Aligndir/$(basename "$Image")"
    Sourceimagelist[$Count]="$Image"
    case "$Loadsourceimages" in
      yes)
        Imsourceimagelist[$Count]="mpr:sourceimage.$(printnum "$Count")"
      ;;
      no)
        Imsourceimagelist[$Count]="$Image"
      ;;
    esac

    sendmagickmessage "PROGRESS:Cropping aligned images ${W}x${H}+${X}+${Y}: $Image ETA:$Count"

    Command="
  '$Image'
    -crop ${W}x${H}+${X}+${Y}
    $Tifstorealpha
    -write '$(sourceimagename "$Count")'
    $(showimagecode "$(sourceimagename "$Count")")
    -delete 0"
    cmd "$Command"
  done
  sendmagickmessage "/PROGRESS"
  cmd_waitforready

  Sourcemd5="$(md5cut "$(ls -f -l --full-time "${Sourceimagelist[@]}")" )"

  return 0
}
alphalevel() {
  local Command Image Mask Percent1 Percent2
  local Longoptions Parsedoptions

  Longoptions="image:,mask:,percent1:,percent2:"
  Parsedoptions="$(getopt --options="" --longoptions "$Longoptions" -- "$@")"
  eval set -- "$Parsedoptions"

  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --image)        Image="${2:-}"    ; shift ;;
      --mask)         Mask="${2:-}"     ; shift ;;
      --percent1)     Percent1="${2:-}" ; shift ;;
      --percent2)     Percent2="${2:-}" ; shift ;;
    esac
    shift
  done

  [ -z "$Image" ] && return 1
  Percent1="${Percent1:-0}"
  Percent2="${Percent2:-100}"

  Command="
  ## alphalevel()
  '$Image'
    -write mpr:image
    -delete 0"
  case "$Mask" in
    "")
      Command="$Command
  mpr:image
    -channel alpha
    -separate
    -write mpr:mask
    -delete 0"
    ;;
    *)
      Command="$Command
  '$Mask'
    -write mpr:mask
    -delete 0"
    ;;
  esac

  Command="$Command
  mpr:mask
    +level ${Percent1}%x${Percent2}%
    -write mpr:mask
    -delete 0"

  Command="$Command
  mpr:image
  mpr:mask
    -alpha off
    -compose CopyOpacity -composite
    $Tifstorealpha
    -write '$Image'
    -delete 0"

  Command="$Command
  +set registry:image
  +set registry:mask"

  Command="$Command
  ## /alphalevel()"

  cmd "$Command"
  cmd_waitforready
  return 0
}
clut_gaussian() {
  # create gaussian clut image $1 with sigma $2
  local Command Clutimage Sigma
  Clutimage="${1:-}"
  Sigma="${2:-1}"
  Command="
  -size 1x1
  xc:white
    -bordercolor Black
    -border 2x0
    -filter gaussian
    -define filter:sigma=$Sigma
    -resize 512x256!
    -crop 50%x100%+0+0
    -auto-level
    -write '$Clutimage'
    -delete -1"
  cmd "$Command"
  cmd_waitforready
}
enfuse_split() {
  local Resultimage Enfuseoptions
  local Command Count
  local Firstimage Lastimage
  local Mem_free Mem_needed
  local Splitimagelist= Splitimagename Splitresult= Splitcache Split Splits Splitheight
  local Startzeit

  Resultimage="${1:-}"
  Firstimage="${2:-1}"
  Lastimage="${3:-$Sourceimagenumber}"

  Mem_free="$(printfreememory)"
  Mem_needed="$(( (Lastimage-Firstimage+1) * Imagewidth*Imageheight * 2 * 32 / (8*1024)))"
  Enfuseoptions="--contrast-weight=1 --saturation-weight=0 --exposure-weight=0 --hard-mask"

  sendmagickmessage "NOTE:Generating image with external tool enfuse"
  Startzeit="$(date +%s)"
  Splits="$((Mem_needed / Mem_free +1))"

  case "$Splits" in
    1)
      ### Single run attempt
      #nice enfuse $Enfuseoptions -o "$Resultimage" "${Sourceimagelist[@]:$Firstimage:$Lastimage}" 2>&1 | grep -v -E "loading next image|assuming all pixels should contribute|does not have an alpha channel|TIFFDecoder" || error "Failed to generate enfuse image"
      nice enfuse $Enfuseoptions -o "$Resultimage" "${Sourceimagelist[@]:$Firstimage:$Lastimage}" || error "Failed to generate enfuse image"
    ;;
    *)
      ### Splitting attempt
      note "enfuse: Splitting source images due to low memory."
      load_sourceimages "$Firstimage" "$Lastimage"

      Splitheight=$((Imageheight/Splits))
      Splitcache="${Cachedir}/enfuse.split"
      mkdir -p "$Splitcache"

      for Split in $(seq $Splits); do
        Splitgeometry[$Split]="${Imagewidth}x$((Splitheight + $([ "$Split" -lt "$Splits" ] && echo 20 || echo 0) ))+0+$(( (Split-1) * Splitheight ))"
      done

      sendmagickmessage "STOPWATCH"
      for Count in $(seq "$Firstimage" "$Lastimage"); do

        sendmagickmessage "PROGRESS:enfuse: Splitting source images into $Splits parts: ${Sourceimagelist[$Count]} ETA:$Count"

        Command="
  '${Sourceimagelist[$Count]}'
    -write mpr:sourceimage
    -delete 0"
        for Split in $(seq "$Splits"); do
          Splitimagename="$Splitcache/split.$(printnum "$Count").${Split}.png"
          Command="$Command
  mpr:sourceimage
    -crop ${Splitgeometry[$Split]}
    +repage
    $Tifstore
    -write '${Splitimagename}'
    -delete 0"
          Splitimagelist[$Split]="${Splitimagelist[$Split]:-} $Splitimagename"
        done
        #cmd "$Command"
        multicore "$Magickbin $(tr -d "\n" <<< "$Command") -exit"
      done
      multicore_wait
      sendmagickmessage "/PROGRESS"
      #cmd_waitforready

      for Split in $(seq $Splits); do
        note "Running enfuse step $Split / $Splits"
        Splitresult[$Split]="$Splitcache/splitresult${Split}.png"
        #nice enfuse $Enfuseoptions -o "${Splitresult[$Split]}" "${Splitimagelist[$Split]}" 2>&1 | grep -v -E "loading next image|assuming all pixels should contribute|does not have an alpha channel|TIFFDecoder" || error "Failed to generate enfuse image"
        nice enfuse $Enfuseoptions -o "${Splitresult[$Split]}" "${Splitimagelist[$Split]}" || error "Failed to generate enfuse image"
        showimage "${Splitresult[$Split]}"
      done

      sendmagickmessage "NOTE:enfuse: Appending splits to result"
      Command="
  mpr:black
    -write mpr:append
    -delete 0"
      for Split in $(seq $Splits); do
        Command="$Command
  mpr:append
    +repage
    -gravity NorthWest
  '${Splitresult[$Split]}'
    +repage
    -gravity NorthWest
    -geometry +0+$(( (Split -1) * Splitheight ))
    -compose Over -composite
    -write mpr:append
    -delete 0"
      done
      Command="$Command
  mpr:append
    $Tifstore
    -write '$Resultimage'
    $(showimagecode "$Resultimage")
    -delete 0
  +set registry:append"
      cmd "$Command"
      cmd_waitforready

      rm -r "$Splitcache"
    ;;
  esac

  note "enfuse_split()[$Splits] ready after: $(date -u -d @$(($(date +%s)-Startzeit)) +"%T")"
  return 0
}
evaluate() {
  local Imagelist Imagenumber=0 Resultimage Mode
  local Command

  Mode="${1:-}" ; shift
  Resultimage="${1:-}" ; shift
  while [ $# -gt 0 ]; do
    Imagenumber="$((Imagenumber+1))"
    Imagelist[$Imagenumber]="${1:-}"
    shift
  done

  sendmagickmessage "NOTE:Evaluating $Mode: $Resultimage"

  [ "$Loadsourceimages" = "no" ] && {
    case "${Mode,,}" in
      max|min|mean)
        #evaluate_splitlist "$Mode" "$Resultimage" "${Imagelist[@]}"
        evaluate_iterative "$Mode" "$Resultimage" "${Imagelist[@]}"
        return 0
      ;;
    esac
  }

  Command="
  # evaluate() $Mode: $Resultimage
  $(printf "'%s' " "${Imagelist[@]}")
    -alpha off
    -evaluate-sequence $Mode
    $Tifstore
    -write '$Resultimage'
    $(showimagecode "$Resultimage")
    -delete 0"
  cmd "$Command"
  cmd_waitforready

  return 0
}
evaluate_iterative() {
  # supports min, max, mean
  # loads images one by one instead all of them

  local Imagelist Imagenumber=0 Resultimage Mode
  local Command

  Mode="${1:-}" ; shift
  Resultimage="${1:-}" ; shift
  while [ $# -gt 0 ]; do
    Imagenumber="$((Imagenumber+1))"
    Imagelist[$Imagenumber]="${1:-}"
    shift
  done
  case "${Mode,,}" in
    max) Mode="Lighten" ;;
    min) Mode="Darken" ;;
  esac

  sendmagickmessage "STOPWATCH"
  Command="
  '${Imagelist[$Imagenumber]}'"
  case "${Mode,,}" in
    darken|lighten)
      for Count in $(seq $((Imagenumber-1)) -1 1); do
        Command="$Command
  '${Imagelist[$Count]}'
    -format 'PROGRESS:Compose $Mode ETA:$Count/$Imagenumber\n'
    -write info:
    -compose $Mode -composite
    $(showimagecode)"
      done
    ;;
    mean)
      for Count in $(seq $Imagenumber -1 1); do
        Command="$Command
  '${Imagelist[$Count]}'
    -alpha off
    -format 'PROGRESS:Compose Blend ETA:$Count/$Imagenumber\n'
    -write info:
    -compose blend
    -set option:compose:args $(calc 100/$((Imagenumber-Count+1)) )
    -composite
    $(showimagecode)"
      done
    ;;
    *)
      error "evaluate_iterative(): Unsupported mode: $Mode"
    ;;
  esac
  Command="$Command
    $Tifstorealpha
    -write '$Resultimage'
    -delete 0"
  cmd "$Command"
  cmd_waitforready
  sendmagickmessage "/PROGRESS"

  return 0
}
evaluate_splitlist() {

  local Imagelist Imagenumber=0 Resultimage Mode
  local Command
  local Split Splits Splitresult Splitfirstimage Splitlastimage
  local Mem_free Mem_needed

  Mode="${1:-}" ; shift
  Resultimage="${1:-}" ; shift
  while [ $# -gt 0 ]; do
    Imagenumber="$((Imagenumber+1))"
    Imagelist[$Imagenumber]="${1:-}"
    shift
  done

  Splits="$(nproc)"
  Mem_free="$(printfreememory)"
  Mem_free="$((Mem_free * 8/10))"
  Mem_needed="$((Imagenumber * Imagememsize))"

  while [ "$((Mem_needed / Splits))" -gt "$Mem_free" ]; do
    Splits="$((Splits +1))"
  done
  [ "$Splits" -lt "1" ] && Splits=1
  Splits="$((Splits+1))"

  for Split in $(seq $Splits); do
    Splitfirstimage[$Split]="$(( (Split-1)*Imagenumber/Splits +1))"
    Splitlastimage[$Split]="$(( (Split)*Imagenumber/Splits +1))"
    [ "${Splitlastimage[$Split]}" -gt "$Imagenumber" ] && Splitlastimage[$Split]="$Imagenumber"
    Splitresult[$Split]="${Cachedir}/evalsplit_$Mode.$Split.${Cacheformat}"
  done

  sendmagickmessage "NOTE:evaluating in $Splits tasks outside of script"

  for Split in $(seq $Splits); do
#  -limit memory ${Mem_free}KB
#  -define registry:temporary-path='${Cachedir}'
    Command="$Magickbin
  $(printf "'%s' " "${Imagelist[@]:${Splitfirstimage[$Split]}:${Splitlastimage[$Split]}}")
    -evaluate-sequence $Mode
    $Tifstore
    -write '${Splitresult[$Split]}'
    -delete 0
    -exit"
    multicore "$(tr -d "\n" <<< "$Command")" "${Splitresult[$Split]}" "$(( Imagememsize * (${Splitlastimage[$Split]}-${Splitfirstimage[$Split]}+1) ))"
  done
  multicore_wait

  Command="
  ${Splitresult[*]}
    -evaluate-sequence $Mode
    $Tifstorealpha
    -write '$Resultimage'
    -delete 0"
  cmd "$Command"
  cmd_waitforready
}
exiftransfer() {
  # Transfer exif data from image $1 to image $2
  # Does not transfer image size information.
  # Sets orientation tag to horizontal / no rotation.
  # Several warnings are supressed.
  local Sourceimage Destinationimage Exifargs
  
  Sourceimage="${1:-}"
  Destinationimage="${2:-}"
  Exifargs="$(exiftool -a -u -g1 -args "$Sourceimage")"
  Exifargs="$(LC_ALL=C grep -v -E -- \
    '-ExifTool|-System:|-File:|ImageWidth|ImageHeight|ImageSize|Compression|Orientation|Resolution' <<< "$Exifargs" \
    | sed "s/'/'\"'\"'/g ; s/=/='/ ; s/\$/'/" )"
  eval exiftool -ignoreMinorErrors -overwrite_original_in_place $Exifargs -Orientation=Horizontal "$Destinationimage" 2>&1 | grep -v -E 'Warning|files updated'
}
finalblur() {
  local Image Mask Threshold Sigma1 Sigma2 Command
  local Longoptions Parsedoptions

  Longoptions="image:,mask:,threshold:,sigma1:,sigma2:"
  Parsedoptions="$(getopt --options="" --longoptions "$Longoptions" -- "$@")"
  eval set -- "$Parsedoptions"

  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --image)     Image="${2:-}"   ; shift ;;
      --mask)      Mask="${2:-}"    ; shift ;;
      --threshold) Threshold="${2:-}" ; shift ;;
      --sigma1)    Sigma1="${2:-}"  ; shift;;
      --sigma2)    Sigma2="${2:-}"  ; shift ;;
      --) ;;
    esac
    shift
  done
  Sigma1="${Sigma1:-1}"
  Sigma2="${Sigma2:-0}"
  Threshold="${Threshold:-50}"

  Command="
  # --finalblur
  '$Image'
    -write mpr:image
    -delete 0
  '$Mask'
    -write mpr:maxmask
    -delete 0
  mpr:maxmask
    -threshold ${Threshold}%
    -negate
    -write mpr:cutmask
    -delete 0"
    Command="$Command
  mpr:image
    -alpha off
    -blur 0x$Sigma1
    -write mpr:bokeh
    -delete 0
  mpr:bokeh
  ( mpr:cutmask -blur 0x$Sigma2 )
    -alpha off
    -compose CopyOpacity -composite
    -write mpr:partsource_blurred
    -delete 0
  mpr:image
  mpr:partsource_blurred
    -compose Over -composite
    $Tifstorealpha
    -write '$Image'
    $(showimagecode)
    -delete 0
  +set registry:bokeh
  +set registry:cutmask
  +set registry:image
  +set registry:maxmask
  +set registry:partsource_blurred"

  cmd "$Command"
  cmd_waitforready
  return 0
}
finalblur2() {
  # --finalblur2

  local Longoptions Parsedoptions
  local Image Threshold Blur Mask
  local Command

  Longoptions="image:,mask:,threshold:,radius1:,radius2:"
  Parsedoptions="$(getopt --options="" --longoptions "$Longoptions" -- "$@")"
  eval set -- "$Parsedoptions"
  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --image)     Image="${2:-}"     ; shift ;;
      --mask)      Mask="${2:-}"      ; shift ;;
      --radius1)   Radius1="${2:-}"   ; shift ;;
      --radius2)   Radius2="${2:-}"   ; shift ;;
      --threshold) Threshold="${2:-}" ; shift ;;
      --) ;;
    esac
    shift
  done

  Command="
  # --finalblur2
  '$Mask'
    -white-threshold ${Threshold}%
    -negate"
  Command="$Command
    -write mpr:mask
    -delete 0
  '$Image'
  mpr:mask
    -set option:compose:args ${Radius1}x${Radius1}
    -compose Blur
    -composite
    $Tifstorealpha
    -write '$Image'
    $(showimagecode "$Image")
    -delete 0
"
  cmd "$Command"
  cmd_waitforready
}
generate_fftshape() {
  local Shape Virtualsize Virtualborder Radius Negate Sigmoidalcontrast Sigmoidalmidpoint
  local Fftcenter Shapemodearg Outputname Command=
  local Longoptions Parsedoptions

  Longoptions="shape:,size:,conversion:,radius:,negate:,sigmoidal:,sigmoidalmidpoint:"
  Parsedoptions="$(getopt --options="" --longoptions "$Longoptions" -- "$@")"
  eval set -- "$Parsedoptions"
  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --shape)             Shape="${2:-}"              ; shift ;;
      #--virtualborder)     Virtualborder="${2:-}"      ; shift ;;
      --conversion)        Shapemode="${2:-}"          ; shift ;;
      --radius)            Radius="${2:-}"             ; shift ;;
      --negate)            Negate="${2:-}"             ; shift ;;
      --sigmoidal)         Sigmoidalcontrast="${2:-}"  ; shift ;;
      --sigmoidalmidpoint) Sigmoidalmidpoint="${2:-}"  ; shift ;;
      --) ;;
    esac
    shift
  done

  Shape="${Shape:-circle}"
  Virtualsize="${Virtualsize:-$Fftsize}"
  Radius="${Radius:-100}"
  Shapemode="${Shapemode:-gradient}"
  Negate="${Negate:-0}"
  Sigmoidalcontrast="${Sigmoidalcontrast:-}"
  Sigmoidalmidpoint="${Sigmoidalmidpoint:-50}"

  Fftcenter="$((Virtualsize/2))"
  Radius="$((Virtualsize*Radius/200))"
  Shapemodearg="${Shapemode//[a-zA-Z]/}"


  case "$Shape" in
    circle)
      case "$Shapemode" in
        solid)
          Command="
  -size ${Virtualsize}x${Virtualsize}
  xc:black
    -fill white
    -draw 'circle ${Fftcenter},${Fftcenter},${Fftcenter},$((Fftcenter+Radius))'"
        ;;
        *)
          Command="
  -size $((Radius*2))x$((Radius*2))
  radial-gradient:"
        ;;
      esac
    ;;
    cross|xcross)
      case "$Shapemode" in
        solid)
          Command="
  -size $(calc "$Virtualsize*1.5")x$(calc "$Virtualsize*1.5")
  xc:black
    -background white
    -gravity center
    -splice $((Radius*2))x$((Radius1*2))"
        ;;
        *)
          Command="
  -size $(calc "$Virtualsize*2")x$Radius
  gradient:
    -rotate 180 
  (
  -clone 0
    -flip
  )
    -append
    -write mpr:grad1
    -rotate 90
    -write mpr:grad2
    -delete 0
  -size $(calc "$Virtualsize*1.5")x$(calc "$Virtualsize*1.5")
  xc:black
  mpr:grad1
    -gravity center
    -compose lighten -composite
  mpr:grad2
    -compose lighten -composite"
        ;;
      esac
    ;;
    square|rhombus)
      case "$Shapemode" in
        solid)
          Command="
  -size $(calc "$Virtualsize*1.5")x$(calc "$Virtualsize*1.5")
  xc:black
    -fill white
    -draw 'rectangle $((Fftcenter-Radius)),$((Fftcenter-Radius)) \
                     $((Fftcenter+Radius)),$((Fftcenter+Radius))'"
        ;;
        *)
          Command="
  -size ${Argradius1}x${Argradius1}
  gradient:
    -rotate 180
  (
  gradient:
    -rotate 90
  )
    -compose Darken -composite
  ( -clone 0 -flip )
    -append
  ( -clone 0 -flop )
    +append"
        ;;
      esac
    ;;
  esac
  case "$Shape" in
    rhombus|xcross)
      Command="$Command
    -background black
    -rotate 45
    +repage"
    ;;
  esac
  Command="$Command
  -size ${Virtualsize}x${Virtualsize}
  xc:black
    -swap 0,1
    -gravity Center
    -compose Blend -composite
    +gravity
    +repage"

  case "$Shapemode" in
    cos*)
      Command="$Command
    -evaluate cos ${Shapemodearg:-0.5}
    -negate"
    ;;
    gauss|gaussian)
      Command="$Command
    -negate
    -fx 'exp(-(u^2)/(sqrt(2*pi)))'
    -auto-level"
    ;;
    gradient) ;;
    inverselog*)
      Command="$Command
    -negate
    -evaluate inverselog ${Shapemodearg:-10}
    -negate
    -auto-level" 
    ;;
    log*)
      Command="$Command
    -negate
    -evaluate log ${Shapemodearg:-10}
    -negate
    -auto-level" 
    ;;
    pow*)
      Command="$Command
    -evaluate pow ${Shapemodearg:-2}
    -auto-level" 
    ;;
    solid) ;;
  esac

  [ -n "$Sigmoidalcontrast" ] && case "$Shapemode" in
    solid)
      Command="$Command
    -blur 0x$Sigmoidalcontrast"
    ;;
    *)
      compare "$Sigmoidalcontrast" -gt "0" && Command="$Command
    -sigmoidal-contrast ${Sigmoidalcontrast}x${Sigmoidalmidpoint}%" || Command="$Command
    +sigmoidal-contrast $(calc "-1*$Sigmoidalcontrast")x${Sigmoidalmidpoint}%"
    ;;
  esac
  [ "$Negate" = "1" ] && Command="$Command
    -negate"

  Outputname="mpr:fftshape$(md5cut "$Command")-$Shape-r$Radius"
  Command="$Command
    -alpha off
    -write '$Outputname'
    $(showimagecode "$Outputname")
    -delete 0"

  cmd "$Command"
  echo "$Outputname"
}
generate_image() {
  local Mode Imagename= Swap= Imagetype= Showname= Showmode= Command Size 
  local Generalimage= Skip= Subimfuse
  local Longoptions Parsedoptions
  local Firstimage Lastimage

  Longoptions="first:,last:,mode:,name:,showname,showmode"
  Parsedoptions="$(getopt --options="" --longoptions "$Longoptions" -- "$@")"
  eval set -- "$Parsedoptions"
  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --first)    Firstimage="${2:-}" ; shift ;;
      --last)     Lastimage="${2:-}"  ; shift ;;
      --mode)     Mode="${2:-}"       ; shift ;;
      --name)     Imagename="${2:-}"  ; shift ;;
      --showname) Showname="yes"      ;;
      --showmode) Showmode="yes"      ;;
    esac
    shift
  done

  Firstimage="${Firstimage:-1}"
  Lastimage="${Lastimage:-$Sourceimagenumber}"
  Size="${Imagewidth}x${Imageheight}"

  grep -q "2" <<< "$Mode" && Swap="-swap 0,1" || Swap=""
  Mode="$(tr -d "2" <<< "$Mode")"

  checkmagicklist color                     "$Mode"   && Imagetype="color"    && Mode="$(checkmagicklist "$Imagetype" "$Mode" print)"
  checkmagicklist evaluate                  "$Mode"   && Imagetype="evaluate" && Mode="$(checkmagicklist "$Imagetype" "$Mode" print)"
  checkmagicklist compose  "$(tr -d "2" <<< "$Mode")" && Imagetype="compose"  && Mode="$(checkmagicklist "$Imagetype" "$Mode" print)"
  case "$Mode" in
    enfuse)                                              Imagetype="enfuse" ;;
    wave)                                                Imagetype="wave" ;;
    wavedark)                                            Imagetype="wavedark" ;;
  esac

  [ -z "$Imagename" ] && {
    Generalimage="yes"
    case "$Imagetype" in
      enfuse)     Imagename="${Cachedir}/${Sourcemd5}.enfuse.${Firstimage}..${Lastimage}.tif" ;;
      wave)       Imagename="${Cachedir}/${Sourcemd5}.wave.${Firstimage}..${Lastimage}.${Cacheformat}" ;;
      wavedark)   Imagename="${Cachedir}/${Sourcemd5}.wavedark.${Firstimage}..${Lastimage}.${Cacheformat}" ;;
      evaluate)   Imagename="${Cachedir}/${Sourcemd5}.evaluate.${Firstimage}..${Lastimage}.${Mode}.${Cacheformat}" ;;
      compose)    Imagename="${Cachedir}/${Sourcemd5}.compose.${Firstimage}..${Lastimage}.${Mode}$([ "$Swap" ] && echo 2 ||:).${Cacheformat}" ;;
      color)      Imagename="mpr:${Mode}" ;;
    esac
  }
  Subimfuse="imfuse --cache=${Cachedir} --output=$Imagename --sub=bg-wave"
  [ "$Showimageprocessing" = "yes" ] && Subimfuse="$Subimfuse -V"

  [ -z "$Imagename" ] && {
    note "generate_image() ERROR: No image name given and none created
  based on possibly unknown image mode: '$Mode'"
    return 1
  }

  [ "$Showmode" ] && {
    echo "$Mode$([ "$Swap" ] && echo 2 ||:)"
    return 0
  }
  [ "$Showname" ] && {
    echo "$Imagename"
    return 0
  }

  [ "$Generalimage" = "yes" ] && case $Imagetype in
    color) ;;
    *) [ -e "$Imagename" ] && Skip="yes" ;;
  esac

  [ "$Skip" = "yes" ] && {
    sendmagickmessage "NOTE:Skipping image generation, already exists: $Imagename"
    return 0
  }
  sendmagickmessage "NOTE:Generating image $Imagename"

  case "$Imagetype" in
    color)
      Command="
  -size $Size
  canvas:$Mode
    $Tifstorealpha
    -write '$Imagename'
    -delete 0"
      cmd "$Command"
      cmd_waitforready
    ;;
    compose)
      generate_image --mode min --first "$Firstimage" --last "$Lastimage"
      generate_image --mode max --first "$Firstimage" --last "$Lastimage"

      Command="
  '$(generate_image --showname --mode min --first "$Firstimage" --last "$Lastimage")'
  '$(generate_image --showname --mode max --first "$Firstimage" --last "$Lastimage")'
    -alpha off
    $Swap
    -compose $Mode -composite
    $Tifstorealpha
    -write '$Imagename'
    $(showimagecode "$Imagename")
    -delete 0"

      cmd "$Command"
      cmd_waitforready
    ;;
    enfuse)
      enfuse_split "$Imagename" "${Firstimage}" "${Lastimage}"
    ;;
    evaluate)
      load_sourceimages
      evaluate "$Mode" "$Imagename" "${Imsourceimagelist[@]:$Firstimage:$Lastimage}"
    ;;
    wave)
      $Subimfuse --format="tif" --wavelet=p100 --cutwave=p100 --finalalpha=off "${Sourceimagelist[@]:$Firstimage:$Lastimage}"
    ;;
    wavedark)
      $Subimfuse --format="tif" --wavelet=p100 --cutwave=p100 --darkness=w25 --finalalpha=off "${Sourceimagelist[@]:$Firstimage:$Lastimage}"
    ;;
  esac

  return 0
}
generate_video() {
  note "Generating video $Video"
  Videoframerate=5
  #nice ffmpeg -y -hide_banner -nostdin  -r $Videoframerate -f image2 -start_number 1 -i $Framedir/frame%04d.$Imageformat $Destinationfile || note "ERROR in video generation"
  [ -f "$Video" ] && rm "$Video"
  nice ffmpeg -y -hide_banner -nostdin  -r $Videoframerate -f image2 -start_number 1 -i "${Cachedir}/videoframe%04d.$Imageformat" "$Video" || note "ERROR in video generation"
  rm "${Cachedir}"/videoframe*
  ffplay "$Video"
}
level_masks() {
  local Maskbasename= Maskmax Maskmin Maskmd5 Clutimage Clut= Round=
  local Count Mask Levelmin= Levelmax= Checkmin= Checkmax=
  local Longoptions Parsedoptions
  local Firstimage Lastimage Masknumber=0 Sourcemask Destmask Outputbasename
  local Masklist

  Longoptions="first:,last:,max::,min::,name:,output:,clut::,round::"
  Parsedoptions="$(getopt --options="" --longoptions "$Longoptions" -- "$@")"
  eval set -- "$Parsedoptions"
  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --first)    Firstimage="${2:-}"     ; shift ;;
      --last)     Lastimage="${2:-}"      ; shift ;;
      --name)     Maskbasename="${2:-}"   ; shift ;;
      --output)   Outputbasename="${2:-}" ; shift ;;
      --clut)     Clut="${2:-yes}"        ; shift ;;
      --round)    Round="${2:-5}"         ; shift ;;
      --min)      Levelmin="${2:-}"       ; shift ;;
      --max)      Levelmax="${2:-}"       ; shift ;;
      --) ;;
      *)
        Masknumber="$((Masknumber+1))"
        Masklist[$Masknumber]="${1:-}"
    esac
    shift
  done

  Firstimage="${Firstimage:-1}"
  Lastimage="${Lastimage:-$Sourceimagenumber}"
  Outputbasename="${Outputbasename:-"$Maskbasename"}"
  Clut="${Clut:-no}"
  [ "$Round" = "0" ] && Round=""
  case "$Masknumber" in
    "0")
      maskarray "$Maskbasename" "$Firstimage" "$Lastimage" Masklist
    ;;
    *)
      Firstimage="1"
      Lastimage="$Masknumber"
    ;;
  esac
  Maskmax="mpr:maskmax"
  Maskmin="mpr:maskmin"
  Maskmaxmin="mpr:maskmaxmin"
  Clutimage="mpr:clut"
  #Clutimage=clut.tif

  [ -z "$Levelmax" ] && Checkmax="yes"
  [ -z "$Levelmin" ] && Checkmin="yes"

  sendmagickmessage "NOTE:Leveling $Maskbasename"

  case "0" in
    0)
      [ "$Checkmax" ] && {
        evaluate max "$Maskmax" "${Masklist[@]}"
        Levelmax="$(getmagickinfo "$Maskmax" "%[fx:maxima*100]")"
      }
      [ "$Checkmin" ] && {
        evaluate min "$Maskmin" "${Masklist[@]}"
        Levelmin="$(getmagickinfo "$Maskmin" "%[fx:minima*100]")"
      }
    ;;
    1)
  sendmagickmessage "STOPWATCH"
  [ -z "$Levelmin$Levelmax" ] && {
    [ "$Checkmin" ] && Levelmin=100
    [ "$Checkmax" ] && Levelmax=0
    for Count in $(seq $Lastimage -1 $Firstimage); do
      eval "$(getmagickinfo "${Masklist[$Count]}" "
Imageminlevel=%[fx:minima*100]
Imagemaxlevel=%[fx:maxima*100]
")"
      Imageminlevel="$(calc "$Imageminlevel")"
      Imagemaxlevel="$(calc "$Imagemaxlevel")"
      [ "$Checkmin" ] && compare "$Imageminlevel" -lt "$Levelmin" && Levelmin="$Imageminlevel"
      [ "$Checkmax" ] && compare "$Imagemaxlevel" -gt "$Levelmax" && Levelmax="$Imagemaxlevel"
      sendmagickmessage "PROGRESS:Calculating min:$Levelmin%, max:$Levelmax%, ETA:$Count"
      [ -n "$Round" ] && compare "$Levelmin" -lt "$Round" && compare "$Levelmax" -gt "$((100-Round))" && {
        sendmagickmessage "PROGRESS:Stopped calculating min/max because min<$Round%, max>$((100-Round))%"
        break
      }
    done
    sendmagickmessage "/PROGRESS"
  }
    ;;
  esac

  [ -n "$Round" ] && {
    Count="100"
    while compare "$Count" -gt "0" ; do
      Count="$((Count-Round))"
      compare "$Count" -lt "$Levelmax" && Levelmax="$((Count+Round))" && break
    done
  }
  [ -n "$Round" ] && {
    Count="0"
    while compare "$Count" -lt "100" ; do
      Count="$((Count+Round))"
      compare "$Count" -gt "$Levelmin" && Levelmin="$((Count-Round))" && break
    done
  }

  [ "$Clut" = "no" ] && compare "$Levelmin" lt "1" && compare "$Levelmax" gt "99" && [ "$Maskbasename" = "$Outputbasename" ] && {
    sendmagickmessage "NOTE:Skipping level, range is greater than 1%..99%"
    return 0
  }

  [ "$Clut" = "yes" ] && {
    evaluate max "$Maskmax" "${Masklist[@]}"
    Command="
  '$Maskmax'
    -separate
    -append
    -level ${Levelmin}%,${Levelmax}%
    -clamp
    $Tifstore
    -write '$Maskmax'
    -delete 0"
    cmd "$Command"
    cmd_waitforready
    mkclut_uniform "$Maskmax" "$Clutimage"
  }

  sendmagickmessage "STOPWATCH"
  for Count in $(seq $Lastimage -1 $Firstimage); do
    sendmagickmessage "PROGRESS:Mask leveling ${Levelmin}%,${Levelmax}%: $Maskbasename ETA:$Count"

    Sourcemask="${Masklist[$Count]}"
    Destmask="${Masklist[$Count]}"
    [ -n "$Outputbasename" ] && Destmask="$(maskname "$Outputbasename" "$Count")"

    Command="
  '$Sourcemask'
    -level ${Levelmin}%,${Levelmax}%"
    [ "$Clut" = "yes" ] && Command="$Command
  '$Clutimage'
    -clut"
    Command="$Command
    $Tifstore
    -write '$Destmask'
    $(showimagecode "$Destmask")
    -delete 0"

    cmd "$Command"
  done
  sendmagickmessage "/PROGRESS"
  cmd_waitforready

  return 0
}
mkclut_uniform() {
  # Generate a clut image to uniform distribution in level_masks()
  # Contains code from http://www.fmwconcepts.com/imagemagick/redist with friendly permission from Fred Weinhaus.

  local Referenceimage Clutimage Netpbm Netpbmdata
  local Histogramarray Colorcountarray Totalpixel Lutlist Quantumrange Factor
  local Command Key1 Key2 Count

  Referenceimage="${1:-}"
  Clutimage="${2:-}"

  # get cumulative histogram
  Key1="$(generate_key)"
  Key2="$(generate_key)"
  Command="
  '$Referenceimage'
    -colorspace gray
    -depth 8
    -format '\n$Key1\n%c$Key2\n'
    -write histogram:info:-
    -delete 0"
  cmd "$Command"
  cmd_waitforready
  Histogramarray="$(sed -n "/$Key1/,/$Key2/p" "$Magickfifolog" | sed '1d ; $d')"
  Histogramarray=($(echo "$Histogramarray" | sed -n  's/[ ]*\([0-9]*\).*gray[(]\([0-9]*\).*$/\1 \2/p' \
                                           | LC_ALL=C awk     '# AWK to generate a zero filled histogram
                                                               { bin[int($2)] += $1; } 
                                                               END { for (i=0;i<256;i++) {hist = bin[i]+0; print hist; } } ' ) )
  Colorcountarray=($(echo ${Histogramarray[*]} | LC_ALL=C awk '# AWK to generate a cumulative histogram
                                                               { split($0,count," ") }
                                                               END { for (i=0;i<256;i++) { cum += count[i]; print cum } }' ))
  Totalpixel="${Colorcountarray[255]}"

  # generate lut for Uniform Distribution which is just the cumulative histogram normalized to max value
  # the uniform distribution has an integral which is f(x)=x
  # this means that the cumulative distribution of the image is its own lut and only needs to be scaled
  # get raw cumulative histogram
  Quantumrange="$(getmagickinfo "$Referenceimage" "%[fx:quantumrange]")"
  Factor="$(getmagickinfo rose: "%[fx:$Quantumrange/$Totalpixel]")"
  Lutlist="$(for ((Count=0; Count<256; Count++)); do
    echo "${Colorcountarray[$Count]}"
  done | LC_ALL=C awk -v Factor="$Factor" '{ print int(Factor*$1); }')"

  # convert lutArr into lut image
  Netpbm="P2 256 1 $Quantumrange $Lutlist"
  Netpbmdata="$(base64 <<< "$Netpbm")"
  sendmagickmessage "Content of encoded NetPBM clut image: $(echo "$Netpbm" | tr "\n" " ")"
  Command="
  'inline:data:image/netpbm;base64,
$Netpbmdata
'
    -scale 256x1\!
    -write '$Clutimage'
    -delete 0"
  cmd "$Command"
  cmd_waitforready
  return 0
}
threshold() {
  local Mask Image= Blackthreshold Whitethreshold Thresholdblur Cutmask= Cutimage=
  local Command
  local Longoptions Parsedoptions

  Longoptions="image:,mask:,threshold1:,threshold2:,sigma1:,cutmask::,cutimage::"
  Parsedoptions="$(getopt --options="" --longoptions "$Longoptions" -- "$@")"
  eval set -- "$Parsedoptions"
  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --mask)       Mask="${2:-}"           ; shift ;;
      --image)      Image="${2:-}"          ; shift ;;
      --sigma1)     Thresholdblur="${2:-}"  ; shift ;;
      --threshold1) Blackthreshold="${2:-}" ; shift ;;
      --threshold2) Whitethreshold="${2:-}" ; shift ;;
      --cutmask)    Cutmask="${2:-"soft"}"  ; shift ;;
      --cutimage)   Cutimage="${2:-"soft"}" ; shift ;;
    esac
    shift
  done

  Blackthreshold="${Blackthreshold:-0}"
  Whitethreshold="${Whitethreshold:-100}"
  Thresholdblur="${Thresholdblur:-0}"

  Command="
  '$Mask'
    -alpha off
    -write mpr:mask
    -delete 0
  mpr:mask
    -threshold ${Blackthreshold}%
    -write mpr:blackthreshold
    -delete 0
  mpr:mask
    -threshold $((Whitethreshold))%
    -negate
    -write mpr:whitethreshold
    -delete 0
  mpr:blackthreshold
  mpr:whitethreshold
    -compose Darken -composite
    -write mpr:threshold
    -delete 0"
  [ "$Thresholdblur" -gt "0" ] && Command="$Command
  mpr:threshold
    -blur 0x$Thresholdblur
    -write mpr:threshold
    -delete 0"
  case "$Cutmask" in
    soft)
      Command="$Command
  mpr:mask
  mpr:threshold
    -compose Darken -composite
    -write mpr:mask
    -delete 0
  mpr:mask
    $Tifstore
    -write '$Mask'
    -delete 0"
    ;;
    hard)
      Command="$Command
  mpr:threshold
    $Tifstore
    -write '$Mask'
    -delete 0"
    ;;
  esac
  case "$Cutimage" in
    soft)
      Command="$Command
  mpr:mask
  mpr:threshold
    -compose Lighten -composite
    -write mpr:mask
    -delete 0
  '$Image'
    -alpha extract
  mpr:mask
    -compose Darken -composite
    -write mpr:mask
    -delete 0
  '$Image'
  mpr:mask
    -alpha off
    -compose CopyOpacity -composite
    $Tifstorealpha
    -write '$Image'
    -delete 0"
    ;;
    hard)
      Command="$Command
  '$Image'
  mpr:threshold
    -alpha off
    -compose CopyOpacity -composite
    $Tifstorealpha
    -write '$Image'
    -delete 0"
    ;;
  esac
  Command="$Command
  +set registry:mask
  +set registry:threshold
  +set registry:whitethreshold
  +set registry:blackthreshold"

  cmd "$Command"
  cmd_waitforready
  return 0
}

### core focus stack routines

focus_maskmethod() {
  # Mask generating ImageMagick options
  # Most of them are based on edge detection: http://www.imagemagick.org/Usage/convolve/#edgedet
  local Sourceimage
  local Maskgenerator=
  local Firstimage Lastimage
  local Fftmaskname1 Fftmaskname2

  Sourceimage="${1:-1}"
  Firstimage="${2:-1}"
  Lastimage="${3:-$Sourceimagenumber}"

  Maskgenerator="
  # --$Argmethod=${Argoptions%,TYPE*}"
  case $Argmethod in
    blur)
      case "$Argsigma2" in
        "")
          Maskgenerator="$Maskgenerator
  $Sourceimage"
          case "$Argword1" in
            ""|"blur") 
              Maskgenerator="$Maskgenerator
    -blur ${Argradius1}x${Argsigma1}"
            ;;
            "gaussian")
              Maskgenerator="$Maskgenerator
    -gaussian-blur ${Argradius1}x${Argsigma1}"
            ;;
          esac
        ;;
        *)
          case "$Argword1" in
            ""|"blur") 
              Maskgenerator="$Maskgenerator
  (
  $Sourceimage
    -blur ${Argradius1},${Argsigma1}
  )
  (
  $Sourceimage
    -blur ${Argradius1},${Argsigma2}
  )
    -compose Difference -composite"
            ;;
            "gaussian") 
              Maskgenerator="$Maskgenerator
  (
  $Sourceimage
    -gaussian-blur ${Argradius1},${Argsigma1}
  )
  (
  $Sourceimage
    -gaussian-blur ${Argradius1},${Argsigma2}
  )
    -compose Difference -composite"
            ;;
          esac
        ;;
      esac
    ;;

    cmd|channel|chroma|darkness|lightness|saturation|enfuse|compose|evaluate|max|min|mean|median)
      Maskgenerator="$Maskgenerator
  $Sourceimage 
    $Argword1"
    ;;

    cmddiff)
      Maskgenerator="$Maskgenerator
  $Sourceimage 
    $Argword1"
    ;;

    comet)
      Maskgenerator="$Maskgenerator
  $Sourceimage 
    -define morphology:compose=${Argword1}
    -morphology Convolve Comet:${Argradius1}x${Argsigma1}:>"
    ;;

    compass)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -define convolve:scale=50%!
    -define morphology:compose=${Argword1}
    -define convolve:bias=50%
    -morphology Convolve Compass:>"
    ;;

    depthmap)
      Md5="md$(md5cut $(ls -f -l --full-time "$Argword1")).tif"
      cmd "
  # --depthmap='$Argword1'
  $Argword1
    -write mpr:$Md5
    -alpha extract
    -write mpr:${Md5}mask
    -delete 0
  mpr:$Md5
    -alpha off
  mpr:${Md5}mask
    -compose Darken -composite
    -write mpr:$Md5
    -delete 0"

      Maskgenerator="$Maskgenerator
  mpr:black
    +level CLUTLEVEL%
  mpr:rainbow
    -clut
  mpr:$Md5
    -compose Difference -composite
    -negate
  mpr:${Md5}mask
    -compose Darken -composite"
    ;;

    diffstat)
        Maskgenerator="$Maskgenerator
  (
  $Sourceimage
    -statistic ${Argword1} ${Argradius1}
  )
  (
  $Sourceimage
    -statistic ${Argword2} ${Argradius2}
  )
    -compose Difference -composite"
    ;;

    dog)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -define convolve:bias=50%
    -define convolve:scale=100%
    -morphology Convolve DoG:${Argradius1},${Argsigma1},${Argsigma2}"
    ;;

    experimental)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -grayscale RMS
  ( $Sourceimage )
    -compose Difference -composite
  "
    ;;
    experimentalX)
      Maskgenerator="$Maskgenerator
  $Sourceimage
  ( -clone 0 -colorspace HSL -channel 1 -separate +channel )
  ( -clone 0 -colorspace HSB -channel 1 -separate +channel )
    -delete 0
    -compose Difference -composite
  "
    ;;

    fft)
      Fftmaskname1="$(generate_fftshape --shape "$Argword2" --conversion "${Argword1}" --radius "$Argradius1" \
                                        --sigmoidal "$Argsigma1" --sigmoidalmidpoint "$Argpercent1" \
                                        --negate "$Argnumber2" )"
      [ "${Argradius2:-0}" -gt "0" ] && {
        Fftmaskname2="$(generate_fftshape --shape "$Argword2" --conversion "${Argword1}" --radius "$Argradius2" \
                                        --sigmoidal "$Argsigma1" --sigmoidalmidpoint "$Argpercent1" \
                                        --negate "$Argnumber2" )"
      }
      Maskgenerator="$Maskgenerator
  -size ${Fftsize}x${Fftsize}
  xc:black
  $Sourceimage
    #-virtual-pixel Mirror
    -fx 'v.p[-$(( (Fftsize-Maskwidth)/2 )),-$(( (Fftsize-Maskheight)/2 ))]'
    -fft
    ( -clone 0 -write mpr:fft0 -delete 0 )
    ( -clone 1 -write mpr:fft1 -delete 0 )
    -delete 0,1"
      Maskgenerator="$Maskgenerator
  mpr:fft0
  '$Fftmaskname1'
    -compose Multiply -composite
    -write mpr:fft0-radius1
    -delete 0"
      [ -n "$Argradius2" ] && Maskgenerator="$Maskgenerator
  mpr:fft0
  '$Fftmaskname2'
    -compose Multiply -composite
    -write mpr:fft0-radius2
    -delete 0"
      Maskgenerator="$Maskgenerator
  mpr:fft0-radius1
  mpr:fft1
    -ift
    -gravity center
    -crop ${Maskwidth}x${Maskheight}+0+0
    +repage
    +gravity"
      [ -n "$Argradius2" ] && Maskgenerator="$Maskgenerator
  (
  mpr:fft0-radius2
  mpr:fft1
    -ift
    -gravity center
    -crop ${Maskwidth}x${Maskheight}+0+0
    +repage
    +gravity
  )
    -alpha off
    -compose Difference -composite"
    ;;

    freichen)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -define convolve:scale=100%!
    -define morphology:compose=${Argword1}
    -define convolve:bias=10%
    -morphology Convolve FreiChen:${Argnumber1}>"
    ;;

    kirsch)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -define convolve:scale=50%!
    -define morphology:compose=Screen
    -define convolve:bias=50%
    -morphology Convolve Kirsch:>"
    ;;

    laplacian)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -define convolve:bias=50%
    -define convolve:scale=!
    -morphology Convolve Laplacian:${Argnumber1}"
    ;;

    log)  # LoG: Laplacian of Gaussian
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -define convolve:bias=50%
    -define convolve:scale=100%!
    -morphology Convolve LoG:${Argradius1}x${Argsigma1}"
    [ -n "$Argsigma2" ] && Maskgenerator="$Maskgenerator
  (
  $Sourceimage
    -define convolve:scale=100%
    -morphology Convolve LoG:${Argradius1}x${Argsigma2}
  )
    -compose Difference -composite"
    ;;

    morphology)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -morphology ${Argword1}:${Argradius2} ${Argword2}:${Argradius1}"
    ;;

    prewitt)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -define convolve:scale=100%!
    -define morphology:compose=Screen
    -define convolve:bias=10%
    -morphology Convolve Prewitt:>"
    ;;

    resize)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -interpolate $Argword1
    -interpolative-resize ${Argpercent1}%
    -interpolative-resize ${Maskwidth}x${Maskheight}
    +interpolate"
    ;;

    roberts)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -define convolve:scale=100%
    -define morphology:compose=Screen
    -define convolve:bias=10%
    -morphology Convolve Roberts:@"
    ;;

    sobel)
      Maskgenerator="$Maskgenerator
  $Sourceimage
    -define convolve:scale=100%!
    -define convolve:bias=10%
    -define morphology:compose=Screen
    -morphology Convolve Sobel:>"
    ;;

    statistic)
      case "$Argradius2" in
        "")
          Maskgenerator="$Maskgenerator
  $Sourceimage
    -statistic ${Argword1} ${Argradius1}x${Argradius1}"
        ;;
        *)
          Maskgenerator="$Maskgenerator
  (
  $Sourceimage
    -statistic ${Argword1} ${Argradius1}
  )
  (
  $Sourceimage
    -statistic ${Argword1} ${Argradius2}
  )
    -compose Difference -composite"
        ;;
      esac
    ;;

    wavelet)
      Maskgenerator="$Maskgenerator
  $Sourceimage 
    -wavelet-denoise ${Argpercent1}%"
      [ -n "$Argpercent2" ] && Maskgenerator="$Maskgenerator
  (
  $Sourceimage 
    -wavelet-denoise ${Argpercent2}%
  )
    -compose Difference -composite"
    ;;

  esac

  echo "$Maskgenerator"
}
focus_generate_masks() {
  local Maskgenerator Maskgenerator_all= Basenamelist= Maskready= Maskmax Mode Line Imagename Command= Maskmd5 Count Sourceimagecode Sourceimagepreparation=
  local Longoptions Parsedoptions
  local Firstimage Lastimage
  local Levelmin Levelmax

  Longoptions="first:,last:"
  Parsedoptions="$(getopt --options="" --longoptions "$Longoptions" -- "$@")"
  eval set -- "$Parsedoptions"
  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --first)    Firstimage="${2:-}" ; shift ;;
      --last)     Lastimage="${2:-}"  ; shift ;;
    esac
    shift
  done

  Firstimage="${Firstimage:-1}"
  Lastimage="${Lastimage:-$Sourceimagenumber}"
  sendmagickmessage "NOTE:Step 2: Generating masks"

  # Generate code to prepare source image
  Sourceimagepreparation="
  mpr:sourceimage"

  # --prepresize
  Maskwidth="$Imagewidth"
  Maskheight="$Imageheight"
  maskarg_single "prepresize" && {
    Sourceimagepreparation="$Sourceimagepreparation
    -interpolate $Argword1
    -interpolative-resize ${Argpercent1}%
    +interpolate"
    Maskwidth="$(magick -size ${Imagewidth}x10 xc: -resize "$Argpercent1"% -format "%w" info:)"
    Maskheight="$(magick -size 10x${Imageheight} xc: -resize "$Argpercent1"% -format "%h" info:)"
  }
  [ "$Maskwidth" -gt "$Maskheight" ] && Fftsize="$Maskwidth" || Fftsize="$Maskheight"
  Fftsize="$(calc "$Fftsize*1.1" | cut -d. -f1)"
  [ "$(( 2* (Fftsize/2) ))" != "$Fftsize" ] && Fftsize="$((Fftsize+1))"

  [ "$Testsetup" ] && Sourceimagepreparation="$Sourceimagepreparation
    -contrast-stretch 0
    -sharpen 0x4"
  Sourceimagepreparation="$Sourceimagepreparation
    -write mpr:sourceimage
    -delete 0"

  # Generate Code for mask generation
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"

    [ "$Argtype" = "mask" ] && {

      Sourceimagecode="
  mpr:sourceimage
    -alpha off
    # --colorspace
    -colorspace $Colorspace"
      [ -n "$Colorspacechannel" ] && Sourceimagecode="$Sourceimagecode
    -channel $Colorspacechannel
    -separate
    +channel"

      [ -n "$Argcolorspace" ] || [ -n "$Argchannel" ] && {
        Sourceimagecode="
  mpr:sourceimage
    -alpha off
    # =C
    -colorspace ${Argcolorspace:-"$Colorspace"}"
        [ -n "$Argchannel" ] && Sourceimagecode="$Sourceimagecode
    # =c
    -channel $Argchannel
    -separate
    +channel"
      }

      [ -n "$Argimage" ] && {
        ### FIXME colorspace
        generate_image --mode "$Argimage" --first "$Firstimage" --last "$Lastimage"
        Sourceimagecode="
  # =E
  mpr:sourceimage
  $(generate_image --mode "$Argimage" --showname --first "$Firstimage" --last "$Lastimage")
    -compose Difference -composite
    -negate"
      }

      Maskgenerator="
$(focus_maskmethod "$Sourceimagecode" "$Firstimage" "$Lastimage")"

      [ "$Argdiff" = "yes" ] && Maskgenerator="$Maskgenerator
    # =diff
  ( $Sourceimagecode
  )
    -compose Difference -composite"

      [ "$Argnegate" ] && Maskgenerator="$Maskgenerator
    # =neg
    -negate"

      [ -z "$Argchannel" ] && Maskgenerator="$Maskgenerator
    # combine colorspace channels
    -colorspace $Colorspace
    -evaluate Divide 4
    -separate
    -evaluate-sequence add"

      Maskmd5="$(md5cut "$Arglevel $Sourceimagepreparation $Maskgenerator $Sourcemd5")"
      Argbasename="$Argmethod.$Maskmd5"
      #Argbasename="${Argmethod}${Argoptions}.$Sourcemd5"
      maskarg_store "$Count"
  Maskgenerator="$Maskgenerator
    # level range
    -format '$Argbasename:MAX=%[fx:maxima*100]\n'
    -write info:
    -format '$Argbasename:MIN=%[fx:minima*100]\n'
    -write info:"

      ## Check if mask already exists
      Maskready="no"
      maskexist "$Argbasename" "$Firstimage" "$Lastimage" && {
        Maskready="yes"
        sendmagickmessage "NOTE:Skipping mask generation, already exists: $Argbasename"
      }
      grep -q -x "$Argbasename" <<< "$Basenamelist" && Maskready="yes"
      Basenamelist="$Basenamelist
$Argbasename"

      [ "$Maskready" = "no" ] && {
        case "$Argmethod" in
          compose|evaluate|min|max|mean|median|enfuse)
            generate_image --mode "$Argimage" --first "$Firstimage" --last "$Lastimage"
          ;;
        esac
        Maskgenerator_all="$Maskgenerator_all
$Maskgenerator
    $Tifstore
    -write '$(maskname "$Argbasename" NUMBER)'
    $(showimagecode "$(maskname "$Argbasename" NUMBER)")
    -delete 0"
      }
    }
  done

  # Generate masks
  [ -n "$Maskgenerator_all" ] && {
    grep -q "NUMBER" <<< "$Maskgenerator_all" && load_sourceimages "$Firstimage" "$Lastimage"
    sendmagickmessage "STOPWATCH"

    for Count in $(seq "$Lastimage" -1 "$Firstimage"); do

      sendmagickmessage "PROGRESS:Mask generation ETA:$Count"

      Command="
  '$(sourceimagename "$Count")'
    -write mpr:sourceimage
    -delete 0
${Sourceimagepreparation}
${Maskgenerator_all}"
      Command="${Command//NUMBER/$(printnum "$Count")}"
      Command="${Command//CLUTLEVEL/$(calc "100*($Sourceimagenumber-$Count)/$Sourceimagenumber")}"

      cmd "$Command"
    done

    sendmagickmessage "/PROGRESS"
    cmd_waitforready
    Command="
    +set registry:sourceimage"
    cmd "$Command"
    cmd_waitforready
  }

  # level and uniform masks
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    [ "$Argtype" = "mask" ] && {
      [ "$Arglevel" = "yes" ] && {
        [ "$Masktocache" = "yes" ] && [ -e "${Cachedir}/${Sourcemd5}.mask.${Argbasename}.levelclut${Argclut}" ] && {
          sendmagickmessage "NOTE:Skipping mask leveling, already done: $Argbasename"
        } || {
          Levelmax="$(grep "$Argbasename:MAX" "$Magickfifolog" | cut -d= -f2 | LC_ALL=C awk '{ OFMT="%f"; print $1*1 }' | sort -n | tail -n1)"
          Levelmin="$(grep "$Argbasename:MIN" "$Magickfifolog" | cut -d= -f2 | LC_ALL=C awk '{ OFMT="%f"; print $1*1 }' | sort -n | head -n1)"
          level_masks --name="$Argbasename" --first="$Firstimage" --last="$Lastimage" --clut="${Argclut:-yes}" --min="$Levelmin" --max="$Levelmax"
          [ "$Masktocache" = "yes" ] && :> "${Cachedir}/${Sourcemd5}.mask.${Argbasename}.levelclut${Argclut}"
        }
      }
    }
  done

  return 0
}
focus_merge_masks() {
  # Merge different masks.

  local Mergemask Mergecode Methodnumber= Mergemethod
  local Command Count
  local Levelmin Levelmax
  local Firstimage Lastimage

  [ "$Maskmethodnumber" = "0" ] && {
    verbose "Skipping merge, no mask options are specified."
    return 0
  }
  sendmagickmessage "NOTE:Step 3: Merging masks"

  Firstimage="${1:-1}"
  Lastimage="${2:-$Sourceimagenumber}"

  # --masklevel
  maskarg_single "masklevel" && {
    case "$Argword1" in
      all)
        Firstimage="1"
        Lastimage="$Sourceimagenumber"
      ;;
      substack)
      ;;
    esac
  }

  Mergemaskbasename="merge"

  maskarg_single "maskmerge" ||:
  Mergemethod="${Argword1:-"Screen"}"
  case "${Mergemethod,,}" in ### FIXME check further modes
    multiply|darken)
      Mergecode="
  mpr:white
    -alpha off"
    ;;
    blend|lighten|screen)
      Mergecode="
  mpr:black
    -alpha off"
    ;;
    *)
      Mergecode="
  mpr:gray50
    -alpha off"
    ;;
  esac
  Mergecode="$Mergecode
    -resize ${Maskwidth}x${Maskheight}"

  ### code for mask merging except those not to be postprocessed
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      mask)
        [ "${Argmask:-}" != "no" ] && {
          Mergecode="$Mergecode
  (
  $(maskname "$Argbasename" NUMBER)"

          [ -n "$Argthreshold1" ] && Mergecode="$Mergecode
    # =t
    -black-threshold ${Argthreshold1:-0}%"

          [ -n "$Argthreshold2" ] && Mergecode="$Mergecode
    # =T
    -negate
    -black-threshold $((100-${Argthreshold2:-100}))%
    -negate"

          [ "$Argweight" != "100" ] && Mergecode="$Mergecode
    # =w
    +level 0%,${Argweight}%"

          Mergecode="$Mergecode
  )
    -compose $Mergemethod -composite"
        }
      ;;
    esac
  done
  Mergecode="$Mergecode
    -write mpr:mergemask
    -delete 0"

  ### code for mask postprocessing
  Mergecode="$Mergecode
  mpr:mergemask"
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      merge)
        Mergecode="$Mergecode
    # --$Argmethod=${Argoptions%,TYPE*}"

        case "${Imoptions[$Count]}" in

          maskblur)
            Mergecode="$Mergecode
    -write mpr:mergemask"
            case "$Argword1" in
              ""|"blur") Mergecode="$Mergecode
    -blur ${Argradius1}x${Argsigma1}" ;;
              "gaussian") Mergecode="$Mergecode
    -gaussian-blur ${Argradius1}x${Argsigma1}" ;;
            esac
            [ "$Argpercent1,$Argpercent2" = "100,0" ] || {
              Mergecode="$Mergecode
  mpr:mergemask
    -compose Blend
    -define compose:args=$Argpercent2%,$Argpercent1%
    -composite"
            }
          ;;

          maskcmd)
            Mergecode="$Mergecode
    $Argword1"
          ;;

          maskdespeckle)
            for Count in $(seq "$Argnumber1"); do
              Mergecode="$Mergecode
    -despeckle"
            done
          ;;

          maskenhance)
            for Count in $(seq "$Argnumber1"); do
              Mergecode="$Mergecode
    -enhance"
            done
          ;;

          maskfft)
            Fftmask="$(generate_fftshape --shape "$Argword2" --conversion "${Argword1}" --radius "$Argradius1" \
                                        --sigmoidal "$Argsigma1" --sigmoidalmidpoint "$Argpercent1" \
                                        --negate "$Argnumber2" )"
            Mergecode="$Mergecode
    -write mpr:mergemask
  -size ${Fftsize}x${Fftsize}
  xc:black
    -swap 0,1
    -fx 'v.p[-$(( (Fftsize-Maskwidth)/2 )),-$(( (Fftsize-Maskheight)/2 ))]'
    -fft
  ( -clone 0 -write mpr:fft0 -delete 0 )
  ( -clone 1 -write mpr:fft1 -delete 0 )
    -delete 0,1
  mpr:fft0
  '$Fftmask'
    -compose Multiply -composite
  mpr:fft1
    -ift
    -gravity center
    -crop ${Maskwidth}x${Maskheight}+0+0
    +gravity
    +repage"
          ;;

          maskkuwahara)
    Mergecode="$Mergecode
    -kuwahara $Argradius1"
          ;;

          maskmorph)
    Mergecode="$Mergecode
    -morphology $Argword1:$Argradius2 ${Argword2:-Octagon}:$Argradius1"
          ;;

          maskstat)
            Mergecode="$Mergecode
    -write mpr:mergemask
    -statistic $Argword1 ${Argradius1}x${Argradius1}"
            case "${Argword1,,}" in
              contrast|mode|standarddeviationX)
                Mergecode="$Mergecode
  mpr:mergemask
    -alpha off
    -compose Difference -composite"
              ;;
            esac
          ;;

          masktest)
            Mergecode="$Mergecode
    -interpolate Spline
    -interpolative-resize ${Argpercent1:-50}%
    -interpolative-resize ${Maskwidth}x${Maskheight}
    +interpolate"
          ;;

          maskthreshold)
            Mergecode="$Mergecode
    -black-threshold ${Argthreshold1}%
    -negate
    -black-threshold $((100-Argthreshold2))%
    -negate"
    ;;

          maskwave)
            case "$Argpercent2" in
              "")
                Mergecode="$Mergecode
    -wavelet-denoise ${Argpercent1}%"
              ;;
              *)
                Mergecode="$Mergecode
    -write mpr:mergemask
  ( mpr:mergemask -wavelet-denoise ${Argpercent1}% )
  ( mpr:mergemask -wavelet-denoise ${Argpercent2}% )
    -compose Difference -composite"
              ;;
            esac
          ;;

        esac
      ;;
    esac
  done

  ### code for mask merging for those not to be postprocessed
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      mask)
        [ "${Argmask:-}" = "no" ] && {
          Mergecode="$Mergecode
  (
  $(maskname "$Argbasename" NUMBER)"

          [ -n "$Argthreshold1" ] && Mergecode="$Mergecode
    # =t
    -black-threshold ${Argthreshold1:-0}%"

          [ -n "$Argthreshold2" ] && Mergecode="$Mergecode
    # =T
    -negate
    -black-threshold $((100-${Argthreshold2:-100}))%
    -negate"

          [ "$Argweight" != "100" ] && Mergecode="$Mergecode
    # =w
    +level 0%,${Argweight}%"

          Mergecode="$Mergecode
  )
    -compose $Mergemethod -composite"
        }
      ;;
    esac
  done
  Mergecode="$Mergecode
    -write mpr:mergemask
    -delete 0"

  false && [ "$Testsetup" ] && Mergecode="$Mergecode
  ./face--bg=Difference_md3a7ecc.tif
    -grayscale RMS
  mpr:mergemask
    -compose Multiply -composite
    -write mpr:mergemask
    -delete 0"

  ### run

  Mergemaskbasename="$Mergemaskbasename.$(md5cut "$Mergecode $Sourcemd5" )"
  #Mergemaskbasename="$(unspecialstring "$Mergemaskbasename")"

  maskexist "$Mergemaskbasename" "$Firstimage" "$Lastimage"  && {
    verbose "Skipping merge, $Mergemaskbasename $Firstimage..$Lastimage already exists"
  } || {
    # merge masks
    sendmagickmessage "STOPWATCH"
    for Count in $(seq "$Lastimage" -1 "$Firstimage"); do
      Mergemask="$(maskname "$Mergemaskbasename" "$Count")"

      sendmagickmessage "PROGRESS:Mask merging $Mergemaskbasename ETA:$Count"

#$(sed "s/NUMBER/$(printnum $Count)/g" <<< "$Mergecode" )
      Command="
${Mergecode//"NUMBER"/"$(printnum "$Count")"}
  mpr:mergemask
    -clamp
    -format '$Mergemaskbasename:MAX=%[fx:maxima*100]\n'
    -write info:
    -format '$Mergemaskbasename:MIN=%[fx:minima*100]\n'
    -write info:
    $Tifstore
    -write '$Mergemask'
    $(showimagecode "$Mergemask")
    -delete 0"

      [ -f "$(maskname "$Mergemaskbasename" "$Count")" ] || cmd "$Command"
    done
    sendmagickmessage "/PROGRESS"
    cmd_waitforready
  }

  # --masklevel
  Levelmax="$(grep "$Mergemaskbasename:MAX" "$Magickfifolog" | cut -d= -f2 | LC_ALL=C awk '{ OFMT="%f"; print $1*1 }' | sort -n | tail -n1)"
  Levelmin="$(grep "$Mergemaskbasename:MIN" "$Magickfifolog" | cut -d= -f2 | LC_ALL=C awk '{ OFMT="%f"; print $1*1 }' | sort -n | head -n1)"
  maskarg_single "masklevel" && [ "$Argword1" != "off" ] && {
    Newbasename="${Sourcemd5}.mask.${Mergemaskbasename}.${Firstimage}..${Lastimage}.level-t${Argthreshold1}-${Argword1}"
    [ "$Masktocache" = "yes" ] && [ -e "${Cachedir}/$Newbasename" ] && {  
      sendmagickmessage "NOTE:--masklevel: Skipping, mask is already leveled."
    } || {
      level_masks --name="$Mergemaskbasename" --round="$Argthreshold1" --min="${Argpercent1:-$Levelmin}" --max="${Argpercent2:-$Levelmax}" --output="$Newbasename" --first="$Firstimage" --last="$Lastimage"
      [ "$Masktocache" = "yes" ] && :> "${Cachedir}/$Newbasename"
    }
    Mergemaskbasename="$Newbasename"
  }

  return 0
}
focus_montage() {
  # Finally: focus stacking

  local Resultcopy Maskmaxcopy= Depthmap=
  local Firstimage Lastimage
  local Focusimagebasename
  #local Focusimagefile Focusimagemaskfile Focusimagedepthfile
  local Command Count
  local Longoptions Parsedoptions
  local Focuscode= Interpolate

  Longoptions="maskmax:,depthmap:,output:,firstimage:,lastimage:"

  Parsedoptions="$(getopt --options="" --longoptions "$Longoptions" -- "$@")"
  eval set -- "$Parsedoptions"

  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --output)       Resultcopy="${2:-}"  ; shift ;;
      --maskmax)      Maskmaxcopy="${2:-}" ; shift ;;
      --depthmap)     Depthmap="${2:-}"    ; shift ;;
      --firstimage)   Firstimage="${2:-}"  ; shift ;;
      --lastimage)    Lastimage="${2:-}"   ; shift ;;
      --) ;;
    esac
    shift
  done

  Firstimage="${Firstimage:-1}"
  Lastimage="${Lastimage:-$Sourceimagenumber}"

  # Code for --cut* options
  Focuscode="
  mpr:cutmask
    -alpha off
    -write mpr:cutmask_hard"
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      focus)
        Focuscode="$Focuscode
  # --${Argmethod}=${Argoptions}"
        case "$Argmethod" in

          cutalpha)
            Focuscode="$Focuscode
  (
  mpr:mergemask
    +level ${Argpercent1}%x${Argpercent2}%
  )
    -compose Darken -composite"
          ;;

          cutbg)
            Argword2="${Sourcemd5}.cutbg.${Argcount}.$(md5cut "$Focuscode $Mergemaskbasename").${Cacheformat}"
            maskarg_store "$Argcount"
            generate_image --mode "transparent" --name "mpr:$Argword2"
            case "$Argword1" in
              background)
                Backgroundnumber="$((Backgroundnumber+1))"
                Backgroundimage[$Backgroundnumber]="${Cachedir}/$Argword2"
              ;;
            esac
            Focuscode="$Focuscode
    -write mpr:cutmask
    -delete 0
  mpr:sourceimage
  mpr:cutmask
    -alpha off
    -compose CopyOpacity -composite
  mpr:$Argword2
    -compose DstOver -composite
    -write mpr:$Argword2
    -delete 0
  mpr:cutmask"
          ;;

          cutblur)
            Focuscode="$Focuscode
    -write mpr:cutmask"
            case "$Argword1" in
              ""|"blur") Focuscode="$Focuscode
    -blur ${Argradius1}x${Argsigma1}" ;;
              "gaussian") Focuscode="$Focuscode
    -gaussian-blur ${Argradius1}x${Argsigma1}" ;;
            esac
            [ "$Argpercent1,$Argpercent2" = "100,0" ] || {
              Focuscode="$Focuscode
  mpr:cutmask
    -compose Blend
    -define compose:args=$Argpercent2%,$Argpercent1%
    -composite"
            }
          ;;

          cutcmd)
            Focuscode="$Focuscode
    $Argword1"
          ;;

          cutless)
            Focuscode="$Focuscode
  ### --cutless
    -write mpr:cutmask_max
    -delete 0

  # get part of actual maskmax
  mpr:focusmask
  mpr:cutmask_max
    -alpha off
    -compose CopyOpacity -composite
    -write mpr:maskmax_part
    -delete 0

  # paint actual mergemask part into lessmask
  mpr:lessthanmax
  mpr:maskmax_part
    -compose Over -composite
    -write mpr:lessthanmax
    -delete 0

  # get area of merge mask that is stronger (+offset) than lessmask)
  mpr:mergemask 
  ( mpr:lessthanmax -evaluate Add ${Argpercent1}% )
    -compose MinusSrc -composite
    -fill white +opaque black 
    -write mpr:cutmask_less
    -delete 0"

            [ -n "$Argthreshold1" ] && Focuscode="$Focuscode
  mpr:cutmask_less
    -threshold ${Argthreshold1}%
    -write mpr:cutmask_less
    -delete 0"
            Focuscode="$Focuscode
  # reduce max in focus mask where --lessthanmax takes over
  mpr:mergemask
  mpr:cutmask_less
    -alpha off
    -compose CopyOpacity -composite
    -write mpr:mergemask_part
    -delete 0
  mpr:focusmask
  mpr:mergemask_part
    -compose Over -composite
    -write mpr:focusmask
    -delete 0

  # darken lessmask with current merge mask to get a mask with decreasing sharpness after a max peak
  mpr:lessthanmax
  mpr:mergemask
    -alpha off
    -compose Darken -composite
    -write mpr:lessthanmax
    -delete 0

  # get area of merge mask equal or sharper than current max (same code as above, but now with less-adjusted maskmax.
  mpr:mergemask
  mpr:focusmask
    -alpha off
    -compose MinusDst -composite
    -fill white
    +opaque black
    -negate
    -write mpr:cutmask
    -delete 0"

            Focuscode="$Focuscode
  # drop areas with strength 0
  mpr:focusmask
    -threshold 0%
  mpr:cutmask
    -compose Darken -composite
    -write mpr:cutmask
    -delete 0"

            Focuscode="$Focuscode
  mpr:cutmask
  ###/ --cutless"
          ;;

          cutmax)
            Focuscode="$Focuscode
  ### --cutmax
    -write mpr:cutmask
    -delete 0
  # get part of mergemask that fits current cutmask
  mpr:mergemask
  mpr:cutmask
    -alpha off
    -compose CopyOpacity -composite
    -write mpr:mergemask_part
    -delete 0
  # get part of focus mask that fits current cutmask
  mpr:focusmask
  mpr:cutmask
    -alpha off
    -compose CopyOpacity -composite
    -write mpr:focusmask_part
    -delete 0
  # get area where cutmask area is stronger than focus mask
  mpr:mergemask_part
  ( mpr:focusmask_part -evaluate Subtract $Argpercent1% )
    -alpha off
    -compose Minus -composite
    -fill white
    +opaque black
    -negate
    -write mpr:cutmask_part
    -delete 0
  # restrict cutmask to area where it is stronger than focus mask
  mpr:cutmask
  mpr:cutmask_part
    -alpha off
    -compose Darken -composite
    -write mpr:cutmask
    -delete 0
  mpr:cutmask
  ###/ --cutmax"
          ;;

          cutmorph)
            Focuscode="$Focuscode
    -morphology ${Argword1}:${Argradius2} ${Argword2:-Octagon}:${Argradius1}"
          ;;

          cutsoft)
            Focuscode="$Focuscode
    -blur 0x$Argsigma1
  mpr:cutmask
    -compose Lighten -composite"
          ;;

          cuttest)
            # --cutmore: don't paint already painted area below threshold
            Focuscode="$Focuscode
    -write mpr:cutmask
    -delete 0
  mpr:focusimage
    -alpha extract
    -write mpr:focusalpha
    -delete 0
  mpr:mergemask
    -threshold ${Argthreshold1:-5}%
  ( mpr:focusalpha -negate )
    -compose Lighten -composite
  mpr:cutmask
    -compose Darken -composite"
          ;;

          cutthreshold)
            Focuscode="$Focuscode
  ( mpr:mergemask
      -threshold ${Argthreshold1}% )
    -compose Darken -composite"
          ;;

          cutwave)
            Focuscode="$Focuscode
    -wavelet-denoise ${Argpercent1}%"
          ;;

        esac
      ;;
    esac
  done
  Focuscode="$Focuscode
    -write mpr:cutmask
    -delete 0"
#    $(showimagecode mpr:cutmask)

  sendmagickmessage "NOTE:Step 4: Focus cut montage"

  Focusimagebasename="$Sourcemd5.focus.$Firstimage..$Lastimage.$(md5cut "$(masklist "$Mergemaskbasename") $Focuscode")"
  Focusimagefile="${Cachedir}/$Focusimagebasename.result.tif"
  Focusimagemaskfile="${Cachedir}/$Focusimagebasename.mask.tif"
  Focusimagedepthfile="${Cachedir}/$Focusimagebasename.depthmap.tif"

  [ -e "$Focusimagefile" ] && {
  note $Focusimagefile $Mergemaskbasename
    sendmagickmessage "NOTE:Skipping focus cut montage, image already exists."
    Command=""

    [ -n "$Maskmaxcopy" ] && [ -f "$Focusimagemaskfile" ] && Command="$Command
  '$Focusimagemaskfile'
    $Tifstore
    -write '$Maskmaxcopy'
    -delete 0"

    [ -n "$Depthmap" ] && [ -f "$Focusimagedepthfile" ] && Command="$Command
  '$Focusimagedepthfile'
    $Tifstorealpha
    -write '$Depthmap'
    -delete 0"

    Command="$Command
  '$Focusimagefile'
    -write mpr:focusimage
    -delete 0"
    for Count in $(seq "$Imoptionsnumber"); do
      maskarg_parse "$Count"
      case "$Argmethod" in
        cutbg)
          case "$Argword1" in
            "compose")
              Command="$Command
  # --cutbg
  ${Cachedir}/$Argword2
  mpr:focusimage
    -compose Over -composite
    -write mpr:focusimage
    -delete 0"
            ;;
          esac
        ;;
      esac
    done
    Command="$Command
  mpr:focusimage
    $Tifstorealpha
    -write '$Resultcopy'
    -delete 0"

    cmd "$Command"
    cmd_waitforready
    return 0
  }

  load_sourceimages "$Firstimage" "$Lastimage"

  Command="
  mpr:transparent
    -write mpr:focusimage
    -delete 0
  mpr:black
    -alpha off
    -write mpr:focusmask
    -delete 0
  mpr:transparent
    -write mpr:depthmap
    -delete 0
  mpr:black
    -write mpr:depthmapalpha
    -delete 0
  mpr:transparent
    -write mpr:lessthanmax
    -delete 0"
  cmd "$Command"

  maskarg_single "prepresize" && Interpolate="$Argword1" || Interpolate="Spline"

  sendmagickmessage "STOPWATCH"
  for Count in $(seq "$Lastimage" -1 "$Firstimage"); do

    sendmagickmessage "PROGRESS:Focus montage ETA:$Count"

    Command="
  # Load images
  '$(sourceimagename "$Count")'
    -alpha off
    -write mpr:sourceimage
    -delete 0
  '$(maskname "$Mergemaskbasename" "$Count")'
    -alpha off
    -interpolate $Interpolate
    -interpolative-resize ${Imagewidth}x${Imageheight}
    +interpolate
    -write mpr:mergemask
    -delete 0

  # get area of merge mask equal or sharper than current max
  mpr:mergemask
  mpr:focusmask
    -alpha off
    -compose Minus -composite -fill white +opaque black
    -negate
    -write mpr:cutmask
    -delete 0

  # increase max in focusmask
  mpr:mergemask
  mpr:focusmask
    -compose Lighten -composite
    -write mpr:focusmask
    -delete 0"

    Command="$Command
  # drop areas with strength 0
  mpr:focusmask
    -threshold 0%
  mpr:cutmask
    -compose Darken -composite
    -write mpr:cutmask
    -delete 0"

    # add --cut* options
    Command="$Command
  $Focuscode"

    Command="$Command
  # get part of source image
  mpr:sourceimage
  mpr:cutmask
    -alpha off
    -compose CopyOpacity -composite
    -write mpr:sourcepart
    $(showimagecode mpr:sourcepart)
    -delete 0

  # add part of source to result
  mpr:focusimage
  mpr:sourcepart
    -compose Over -composite
    -write mpr:focusimage
    -delete 0"
#    $(showimagecode "mpr:focusimage")

    [ -n "$Depthmap" ] && Command="$Command
  # depth map
  mpr:black
    +level $(calc "100*($Sourceimagenumber-$Count)/$Sourceimagenumber")%
  mpr:cutmask
    #-alpha off
    -compose CopyOpacity -composite
    -write mpr:depthmappart
    -delete 0
  mpr:depthmap
  mpr:depthmappart
    -compose Over -composite
    -write mpr:depthmap
    -delete 0"
#  mpr:depthmapalpha
#  mpr:cutmask
#    -compose Lighten -composite
#    -write mpr:depthmapalpha
#    -delete 0"

    cmd "$Command"
  done
  sendmagickmessage "/PROGRESS"
  Command=""

  Command="$Command
  # store result image and mask
  mpr:focusimage
    $Tifstorealpha
    -write '$Focusimagefile'
    -delete 0
  mpr:focusmask
    $Tifstore
    -write '$Focusimagemaskfile'
    -delete 0"

  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argmethod" in
      cutbg)
        Command="$Command
  # --cutbg
  mpr:$Argword2
    -write ${Cachedir}/$Argword2
    -delete 0"
        case "$Argword1" in
          "compose") # add background to result
            Command="$Command
  mpr:$Argword2
  mpr:focusimage
    -compose Over -composite
    -write mpr:focusimage
    -delete 0"
          ;;
        esac
      ;;
    esac
  done

  Command="$Command
  # write to output image
  mpr:focusimage
    $Tifstorealpha
    -write '$Resultcopy'
    -delete 0"

  [ -n "$Depthmap" ] && Command="$Command
  mpr:depthmap
#  mpr:depthmapalpha
#    -compose CopyOpacity -composite
    $Tifstorealpha
    -write '$Focusimagedepthfile'
    -write '$Depthmap'
    -delete 0"

  [ -n "$Maskmaxcopy" ] && Command="$Command
  # copy max mask
  mpr:focusmask
    $Tifstore
    -write '$Maskmaxcopy'
    -delete 0"

  cmd "$Command"
  cmd_waitforready

  Command="
  +set registry:cutmask
  +set registry:cutmask_less
  +set registry:cutmask_max
  +set registry:depthmap
  +set registry:focusimage
  +set registry:focusmask
  +set registry:lessthanmax
  +set registry:maskmax
  +set registry:maskmax_part
  +set registry:sourceimage
  +set registry:mergemask
  +set registry:mergemask_part
  +set registry:sourcepart"
  cmd "$Command"
  cmd_waitforready
  #+set registry:resultimage

  return 0
}
focus_main() {
  local Substackstep Substackbasename
  local Command Count Startzeit
  local Backgroundimage
  local Transparentlayers=

  sendmagickmessage "NOTE:Step 1: Generating background images"
  # helper images
  generate_image --mode "black"
  generate_image --mode "white"
  generate_image --mode "transparent"
  generate_image --mode "gray50"
  # --background
  Backgroundnumber="0"
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    case "$Argtype" in
      background)
        sendmagickmessage "NOTE:Processing --${Imoptions[$Count]}=${Imarguments["$Count"]%,TYPE*}"
        Backgroundnumber="$((Backgroundnumber+1))"
        Backgroundimage[$Backgroundnumber]="$(generate_image --showname --mode "$Argword1")"
        generate_image --mode "$Argword1"
      ;;
    esac
  done

  # Generate masks
  Startzeit="$(date +%s)"
  focus_generate_masks
  focus_merge_masks
  sendmagickmessage "VERBOSE:Generating masks ready after: $(date -u -d @$(($(date +%s)-Startzeit)) +"%T")"

  Command="
  # provide transparent mpr:resultimage to paint on
  mpr:transparent
    -write mpr:resultimage
    -delete 0"
  cmd "$Command"

  # Focus stacking
  [ "$Maskmethodnumber" -gt "0" ] && { 
    for Substackstep in $(seq "$Substacknumber" -1 1); do
      Substackbasename="${Cachedir}/${Sourcemd5}.substack${Substackstep}.${Substackfirstimage[$Substackstep]}..${Substacklastimage[$Substackstep]}.${Substackmd5}"
      Substackresult[$Substackstep]="${Substackbasename}.result.${Cacheformat}"
      Substackmask[$Substackstep]="${Substackbasename}.mask.${Cacheformat}"
      Substackdepthmap[$Substackstep]="${Substackbasename}.depthmap.${Cacheformat}"

      sendmagickmessage "NEWLINE"
      sendmagickmessage "NOTE:Generating substack $Substackstep / $Substacknumber: ${Substackfirstimage[$Substackstep]}..${Substacklastimage[$Substackstep]}"

      # focus
      Startzeit="$(date +%s)"
      case "$Extendedsave" in
        yes)
          focus_montage --maskmax "${Substackmask[$Substackstep]}" --output "${Substackresult[$Substackstep]}" \
                        --firstimage="${Substackfirstimage[$Substackstep]}" --lastimage="${Substacklastimage[$Substackstep]}" \
                        --depthmap "${Substackdepthmap[$Substackstep]}"
        ;;
        no)
          focus_montage --maskmax "${Substackmask[$Substackstep]}" --output "${Substackresult[$Substackstep]}" \
                        --firstimage="${Substackfirstimage[$Substackstep]}" --lastimage="${Substacklastimage[$Substackstep]}"
        ;;
      esac
      sendmagickmessage "VERBOSE:focus ready after: $(date -u -d @$(($(date +%s)-Startzeit)) +"%T")"
    done

    Startzeit="$(date +%s)"
    for Substackstep in $(seq "$Substacknumber" -1 1); do
      sendmagickmessage "NOTE:Step 5: Final postprocessing substack $Substackstep / $Substacknumber: ${Substackfirstimage[$Substackstep]}..${Substacklastimage[$Substackstep]}"

      Substackbasename="${Cachedir}/${Sourcemd5}.substack${Substackstep}.${Substackfirstimage[$Substackstep]}..${Substacklastimage[$Substackstep]}.${Finalmd5}"
      Substackresultpost[$Substackstep]="${Substackbasename}.result.final.${Cacheformat}"
      Substackmaskpost[$Substackstep]="${Substackbasename}.mask.final.${Cacheformat}"
      Substackdepthmappost[$Substackstep]="${Substackbasename}.depthmap.final.${Cacheformat}"

      #[ -e "${Substackresultpost[$Substackstep]}" ] && {
      #  sendmagickmessage "NOTE:Skipping --final* options, already done."
      #} || {
      {

        Command="
'${Substackresult[$Substackstep]}'
  $Tifstorealpha
  -write '${Substackresultpost[$Substackstep]}'
  -delete 0
'${Substackmask[$Substackstep]}'
  $Tifstore
  -write '${Substackmaskpost[$Substackstep]}'
  -delete 0"
        case "$Extendedsave" in
          "yes")
            Command="$Command
'${Substackdepthmap[$Substackstep]}'
  $Tifstorealpha
  -write '${Substackdepthmappost[$Substackstep]}'
  -delete 0"
          ;;
        esac
        cmd "$Command"

        for Count in $(seq "$Imoptionsnumber"); do
          maskarg_parse "$Count"
          Option="${Imoptions[$Count]}"
          case "$Argtype" in
            postfocus)
              sendmagickmessage "NOTE:Applying --${Imoptions[$Count]}=${Imarguments["$Count"]%,TYPE*}"
              case "$Option" in
                finalalpha)
                  case "$Argword1" in
                    "")
                      alphalevel --image "${Substackresultpost[$Substackstep]}" --mask "${Substackmaskpost[$Substackstep]}" \
                                 --percent1 "${Argpercent1}" --percent2 "${Argpercent2}"
                      case "$Extendedsave" in
                        yes)
                          alphalevel --image "${Substackdepthmappost[$Substackstep]}" --mask "${Substackmaskpost[$Substackstep]}" \
                                     --percent1 "${Argpercent1}" --percent2 "${Argpercent2}"
                        ;;
                      esac
                    ;;
                    off)
                      Command="
  '${Substackresultpost[$Substackstep]}'
    -alpha off
    $Tifstore
    -write '${Substackresultpost[$Substackstep]}'
    -delete 0"
                      [ "$Extendedsave" = "yes" ] && Command="$Command
  ${Substackdepthmappost[$Substackstep]}
    -alpha off
    $Tifstore
    -write '${Substackdepthmappost[$Substackstep]}'
    -delete 0"
                      cmd "$Command"
                    ;;
                  esac
                ;;
                finalblur)
                  finalblur  --image "${Substackresultpost[$Substackstep]}" --mask "${Substackmaskpost[$Substackstep]}" \
                             --threshold "$Argthreshold1" --sigma1 "$Argsigma1" --sigma2 "$Argsigma2"
                ;;
                finalblur2)
                  finalblur2 --image "${Substackresultpost[$Substackstep]}" --mask "${Substackmaskpost[$Substackstep]}" \
                             --threshold "$Argthreshold1" --radius1 "$Argradius1" --radius2 "$Argradius2"
                ;;
                finalcmd)
                  Command="
  ${Substackresultpost[$Substackstep]}
    $Argword1
    $Tifstorealpha
    -write ${Substackresultpost[$Substackstep]}
    -delete 0"
                  cmd "$Command"
                ;;
                finalgamma)
                  case "$Argword1" in
                    auto)
                      Command="
  ${Substackresultpost[$Substackstep]}
    -auto-gamma" 
                    ;;
                    *)
                      Command="
  ${Substackresultpost[$Substackstep]}
    -gamma $Argword1"
                    ;;
                  esac
                  Command="$Command
    $Tifstorealpha
    -write ${Substackresultpost[$Substackstep]}
    -delete 0"
                  cmd "$Command"
                ;;
                finalsharpen)
                  Command="
  ${Substackresultpost[$Substackstep]}
    -adaptive-sharpen ${Argradius1}x${Argsigma1}
    $Tifstorealpha
    -write ${Substackresultpost[$Substackstep]}
    -delete 0"
                  cmd "$Command"
                ;;
                finaltest)
                  Command="
  ${Substackdepthmappost[$Substackstep]}
    -threshold 5%
  (
  ${Substackmaskpost[$Substackstep]}
    -threshold 10%
  )"
                ;;
                finalthreshold)
                  threshold  --image "${Substackresultpost[$Substackstep]}" --mask "${Substackmaskpost[$Substackstep]}" \
                             --threshold1 "$Argthreshold1" --threshold2 "$Argthreshold2" --sigma1 "$Argsigma1" \
                             --cutimage="soft" --cutmask="soft"
                  case "$Extendedsave" in
                    yes)
                      threshold  --image "${Substackdepthmappost[$Substackstep]}" --mask "${Substackmaskpost[$Substackstep]}" \
                                 --threshold1 "$Argthreshold1" --threshold2 "$Argthreshold2" \
                                 --cutimage="soft"
                    ;;
                  esac
                ;;
              esac
            ;;
          esac
        done
      }
    done

    cmd_waitforready
    sendmagickmessage "NOTE:Final postprocessing ready after: $(date -u -d @$(($(date +%s)-Startzeit)) +"%T")"

    Command="
  # composing substack results
  mpr:resultimage"
    for Substackstep in $(seq "$Substacknumber" -1 1); do
      Command="$Command
  '${Substackresultpost[$Substackstep]}'
    -compose Over -composite"
    done
    Command="$Command
    -write mpr:resultimage
    $(showimagecode)
    -delete 0"
    cmd "$Command"
    cmd_waitforready
  }

  # Generate result image
  case "$Storelayered" in
    yes)
      # --layered
      sendmagickmessage "NOTE:Generating layered tif"
      { maskarg_single "threshold" || maskarg_single "finalalpha" ; } && Transparentlayers="yes"
      generate_image --mode cyan
      generate_image --mode magenta
      generate_image --mode yellow
      generate_image --mode green
      Command="
  # --layered
  mpr:resultimage
  mpr:cyan
  mpr:magenta
  mpr:yellow
  mpr:green"
      for Count in $(seq "$Backgroundnumber"); do
        Command="$Command
  '${Backgroundimage[$Count]}'"
      done
      for Substackstep in $(seq "$Substacknumber" -1 1); do
        [ "$Transparentlayers" = "yes" ] && Command="$Command
  '${Substackresult[$Substackstep]}'"
        Command="$Command
  '${Substackresultpost[$Substackstep]}'"
      done
      Command="$Command
    $Tifstorealpha
    #-define tiff:write-layers=yes
    -write '$Resultimage'
    $(showimagecode "$Resultimage")
    -delete 0"
    ;;

    no)
      Command="
  mpr:transparent"
      for Count in $(seq "$Backgroundnumber"); do
        Command="$Command
  '${Backgroundimage[$Count]}'
    -compose Over -composite"
      done
      Command="$Command
  mpr:resultimage
    -compose Over -composite
    -write mpr:resultimage
    -delete 0
  mpr:resultimage
    $Tifstorealpha
    -write '$Resultimage'
    -delete 0"
    ;;
  esac

  cmd "$Command"
  cmd_waitforready

  [ "$Maskmethodnumber" = "0" ] && [ "$Backgroundnumber" = "0" ] && {
    note "No mask option and no background has been specified.
  imfuse has generated an empty transparent result image.
  Suggestion: Try at least '--background=enfuse', or short: --bg"
    return 0
  }

  # -X: Save mask and colorize depth map
  [ "$Extendedsave" = "yes" ] && [ "$Substacknumber" -gt "0" ] && {
    case "$Substacknumber" in
      1)
        Command="
  '${Substackmaskpost[1]}'
    $Tifstorealpha
    -write '$Resultmask'
    -delete 0
  '${Substackdepthmappost[1]}'
    -write mpr:depthmap
    -delete 0"
      ;;
      *)
        evaluate max "$Resultmask" "${Substackmaskpost[@]}"
        Command="
  # composing substack depth maps
  mpr:transparent"
        for Substackstep in $(seq "$Substacknumber" -1 1); do
          Command="$Command
  '${Substackdepthmappost[$Substackstep]}'
    -compose Over -composite"
        done
        Command="$Command
    -write mpr:depthmap
    -delete 0"
      ;;
    esac
    Command="$Command
  mpr:depthmap
    -alpha extract
    -write mpr:alpha
    -delete 0
  mpr:depthmap
  mpr:rainbow
    -clut
  mpr:alpha
    -compose CopyOpacity -composite
    $Tifstorealpha
    -write '$Resultdepthmap'
    $(showimagecode "$Resultdepthmap")
    -delete 0"
    cmd "$Command"
    cmd_waitforready
  }

  false && [ "$Testsetup" ] && {
    Command="
  $Resultdepthmap
  ( $Resultdepthmap -blur 0x0.5 \)
    -compose Difference -composite
  ( $Resultmask -negate )
    -compose Multiply -composite
    -negate
    -statistic mean 10x10
    -write show:
    -threshold ${Testarg:-90}%
    -write mpr:filter
    -delete 0
  $Resultimage
  mpr:filter
    -compose CopyOpacity -composite
    -write $Resultimage
    -delete 0"
    cmd "$Command"
    cmd_waitforready
  }

  return 0
}

### mask args

maskarg_add() {
  Imoptionsnumber="$((Imoptionsnumber+1))"
  Argmethod="${1#--}"
  [ "$Argmethod" = "background" ] && Argmethod="bg"
  Imoptions[$Imoptionsnumber]="$Argmethod"
  Imarguments[$Imoptionsnumber]="${2:-}"
  maskarg_parse "$Imoptionsnumber"
  maskarg_check "$Imoptionsnumber"
  maskarg_store "$Imoptionsnumber"
  case "$Argtype" in
    mask)
      Maskmethodnumber="$((Maskmethodnumber+1))"
    ;;
  esac

  # to add a new option:
  # parse_options():        add to $Longoptions
  # parse_options():        add it to big collection entry)
  # maskarg_defaultvalue(): specify type
  # maskarg_defaultvalue(): add default values (if any)
  # maskarg_checkallowed(): add name to all allowed arguments
  # check_outputname():     type single needs a custom entry
}
maskarg_check() {
  # check for allowed values, set defaults.
  # maskarg_parse() must run beforehand.
  # use maskarg_store() afterwards.

  Argtype="$(maskarg_defaultvalue "$Argmethod" type)"
  [ -z "$Argchannel"  ]    && Argchannel="$(maskarg_defaultvalue    "$Argmethod" channel)"
  [ -z "$Argcolorspace"  ] && Argcolorspace="$(maskarg_defaultvalue "$Argmethod" colorspace)"
  [ -z "$Argimage"  ]      && Argimage="$(maskarg_defaultvalue      "$Argmethod" image)"
  [ -z "$Arglevel"  ]      && Arglevel="$(maskarg_defaultvalue      "$Argmethod" level)"
  [ -z "$Argclut"  ]       && Argclut="$(maskarg_defaultvalue       "$Argmethod" clut)"
  [ -z "$Argnegate"  ]     && Argnegate="$(maskarg_defaultvalue     "$Argmethod" negate)"
  [ -z "$Argmask"  ]       && Argmask="$(maskarg_defaultvalue       "$Argmethod" mask)"
  [ -z "$Argnumber1"  ]    && Argnumber1="$(maskarg_defaultvalue    "$Argmethod" number1)"
  [ -z "$Argnumber2"  ]    && Argnumber2="$(maskarg_defaultvalue    "$Argmethod" number2)"
  [ -z "$Argpercent1" ]    && Argpercent1="$(maskarg_defaultvalue   "$Argmethod" percent1)"
  [ -z "$Argpercent2" ]    && Argpercent2="$(maskarg_defaultvalue   "$Argmethod" percent2)"
  [ -z "$Argradius1" ]     && Argradius1="$(maskarg_defaultvalue    "$Argmethod" radius1)"
  [ -z "$Argradius2" ]     && Argradius2="$(maskarg_defaultvalue    "$Argmethod" radius2)"
  [ -z "$Argsigma1" ]      && Argsigma1="$(maskarg_defaultvalue     "$Argmethod" sigma1)"
  [ -z "$Argsigma2" ]      && Argsigma2="$(maskarg_defaultvalue     "$Argmethod" sigma2)"
  [ -z "$Argthreshold1" ]  && Argthreshold1="$(maskarg_defaultvalue "$Argmethod" threshold1)"
  [ -z "$Argthreshold2" ]  && Argthreshold2="$(maskarg_defaultvalue "$Argmethod" threshold2)"
  [ -z "$Argword1" ]       && Argword1="$(maskarg_defaultvalue      "$Argmethod" word1)"
  [ -z "$Argword2" ]       && Argword2="$(maskarg_defaultvalue      "$Argmethod" word2)"
  [ -z "$Argdiff"  ]       && Argdiff="$(maskarg_defaultvalue       "$Argmethod" diff)"
  [ -n "$Argchannel" ]     && maskarg_checkallowed "$Argmethod" channel    "$Argchannel"
  [ -n "$Argcolorspace" ]  && maskarg_checkallowed "$Argmethod" colorspace "$Argcolorspace"
  [ -n "$Argimage" ]       && maskarg_checkallowed "$Argmethod" image      "$Argimage"
  [ -n "$Arglevel" ]       && maskarg_checkallowed "$Argmethod" level      "$Arglevel"
  [ -n "$Argclut" ]        && maskarg_checkallowed "$Argmethod" clut       "$Argclut"
  [ -n "$Argmask" ]        && maskarg_checkallowed "$Argmethod" mask       "$Argmask"
  [ -n "$Argnegate" ]      && maskarg_checkallowed "$Argmethod" negate     "$Argnegate"
  [ -n "$Argnumber1" ]     && maskarg_checkallowed "$Argmethod" number1    "$Argnumber1"
  [ -n "$Argnumber2" ]     && maskarg_checkallowed "$Argmethod" number2    "$Argnumber2"
  [ -n "$Argradius1" ]     && maskarg_checkallowed "$Argmethod" radius1    "$Argradius1"
  [ -n "$Argradius2" ]     && maskarg_checkallowed "$Argmethod" radius2    "$Argradius2"
  [ -n "$Argsigma1" ]      && maskarg_checkallowed "$Argmethod" sigma1     "$Argsigma1"
  [ -n "$Argsigma2" ]      && maskarg_checkallowed "$Argmethod" sigma2     "$Argsigma2"
  [ -n "$Argpercent1" ]    && maskarg_checkallowed "$Argmethod" percent1   "$Argpercent1"
  [ -n "$Argpercent2" ]    && maskarg_checkallowed "$Argmethod" percent2   "$Argpercent2"
  [ -n "$Argthreshold1" ]  && maskarg_checkallowed "$Argmethod" threshold1 "$Argthreshold1"
  [ -n "$Argthreshold2" ]  && maskarg_checkallowed "$Argmethod" threshold2 "$Argthreshold2"
  [ -n "$Argweight" ]      && maskarg_checkallowed "$Argmethod" weight     "$Argweight"
  [ -n "$Argword1" ]       && maskarg_checkallowed "$Argmethod" word1      "$Argword1"
  [ -n "$Argword2" ]       && maskarg_checkallowed "$Argmethod" word2      "$Argword2"
  [ -n "$Argdiff" ]        && maskarg_checkallowed "$Argmethod" diff       "$Argdiff"

  case "$Argtype" in
    single)
      grep -q -w "$Argmethod" <<< "$Singleoptionlist" && error "Option --$Argmethod can be specified only once."
      Singleoptionlist="$Singleoptionlist $Argmethod"
    ;;
  esac

  case $Argmethod in
    compose|evaluate)
      Argimage="${Argimage:-$Argword1}"
      Argword1=""
    ;;
  esac
}
maskarg_parse() {
  local Key Line Value

  eval $Arglist
  Argcount="${1:-}"
  Argmethod="${Imoptions[$Argcount]}"
  Argoptions="${Imarguments[$Argcount]}"

  while read -d, Line; do
    [[ "$Line" == *"="* ]] && {
      Value="${Line#*"="}"
      Key="${Line%"=${Value}"}"
    } || {
      Key="${Line//[0-9-+.]/}" # remove digits including +-.
      [ "$Key" = "%" ] && Key="p"
      Key="${Key%"%"}"
      #Value="${Line#${Key:-NOKEY}}"
      Value="${Line/${Key:-NOKEY}/}"
    }
    Value="${Value//%/}"
    Value="${Value//+/}"
    case $Key in
      "w")                Argweight="$Value" ;;
      "r")                [ -z "$Argradius1" ]  && Argradius1="$Value"  || Argradius2="$Value" ;;
      "R")                Argradius2="${Value}" ;;
      "s")                [ -z "$Argsigma1" ]   && Argsigma1="$Value"   || Argsigma2="$Value" ;;
      "S")                Argsigma2="${Value}" ;;
      "p")                [ -z "$Argpercent1" ] && Argpercent1="$Value" || Argpercent2="$Value" ;;
      "P")                Argpercent2="$Value" ;;
      "t")                Argthreshold1="$Value" ;;
      "T")                Argthreshold2="$Value" ;;
      "n")                [ -z "$Argnumber1" ]  && Argnumber1="$Value"  || Argnumber2="$Value" ;;
      "N")                Argnumber2="$Value" ;;
      "I")                Argimage="$Value" ;;
      "C")                Argcolorspace="$Value" ;;
      "c")                Argchannel="$Value" ;;
      "mask")             Argmask="${Value:-yes}" ;;
      "diff")             Argdiff="${Value:-yes}" ;;
      "neg")              Argnegate="${Value:-yes}" ;;
      "level")            Arglevel="${Value:-yes}" ;;
      "clut")             Argclut="${Value:-yes}" ;;
      "TYPE")             Argtype="$Value" ;;
      "AUTOWEIGHT")       Argautoweight="$Value" ;;
      "BASENAME")         Argbasename="$Value" ;;
      "W1"|"WORD1")       Argword1="$Value" ;;
      "W2"|"WORD2")       Argword2="$Value" ;;
      *)                  [ -z "$Argword1" ] && Argword1="$Line" || Argword2="$Line" ;;
    esac
  done <<< "$Argoptions,"

  return 0
}
maskarg_store() {
  Argoptions=""
  Argcount="${1:-}"
  [ "$Argweight" ]             && Argoptions="$Argoptions,w$Argweight"
  [ "$Argmask" ]               && Argoptions="$Argoptions,mask=$Argmask"
  [ "$Argradius1" ]            && Argoptions="$Argoptions,r$Argradius1"
  [ "$Argradius2" ]            && Argoptions="$Argoptions,R$Argradius2"
  [ "$Argsigma1" ]             && Argoptions="$Argoptions,s$Argsigma1"
  [ "$Argsigma2" ]             && Argoptions="$Argoptions,S$Argsigma2"
  [ "$Argpercent1" ]           && Argoptions="$Argoptions,p$Argpercent1"
  [ "$Argpercent2" ]           && Argoptions="$Argoptions,P$Argpercent2"
  [ "$Argnumber1" ]            && Argoptions="$Argoptions,n$Argnumber1"
  [ "$Argnumber2" ]            && Argoptions="$Argoptions,N$Argnumber2"
  [ "$Argimage" ]              && Argoptions="$Argoptions,I=$Argimage"
  [ "$Argcolorspace" ]         && Argoptions="$Argoptions,C=$Argcolorspace"
  [ "$Argchannel" ]            && Argoptions="$Argoptions,c$Argchannel"
  [ "$Argdiff" ]               && Argoptions="$Argoptions,diff=$Argdiff"
  [ "$Argnegate" ]             && Argoptions="$Argoptions,neg=$Argnegate"
  [ "$Arglevel" ]              && Argoptions="$Argoptions,level=$Arglevel"
  [ "$Argclut" ]               && Argoptions="$Argoptions,clut=$Argclut"
  [ "$Argthreshold1" ]         && Argoptions="$Argoptions,t$Argthreshold1"
  [ "$Argthreshold2" ]         && Argoptions="$Argoptions,T$Argthreshold2"
  [ "$Argword1" ]              && Argoptions="$Argoptions,W1=$Argword1"
  [ "$Argword2" ]              && Argoptions="$Argoptions,W2=$Argword2"
  [ "$Argtype" ]               && Argoptions="$Argoptions,TYPE=$Argtype"
  [ "$Argautoweight" ]         && Argoptions="$Argoptions,AUTOWEIGHT=yes"
  [ "$Argbasename" ]           && Argoptions="$Argoptions,BASENAME=$Argbasename"
  Argoptions="${Argoptions#,}"
  Imarguments[$Argcount]="$Argoptions"
}
maskarg_checkallowed() {
  local Argmethod Option Arg
  Argmethod="${1:-}"
  Option="${2:-}"
  Arg="${3:-}"

  case "$Argmethod" in
    experimental|cuttest|masktest) return 0 ;;
  esac
  case "$Option" in
    channel)
      case "$Argtype" in
        mask) ;;
        *) 
          case "${1:-}" in
            colorspace) ;;
            *) error "--$Argmethod does not take argument colorspace channel: c$Arg" ;;
          esac
        ;;
      esac
    ;;
    clut|diff|level|mask|negate)
      case "$Argtype" in
        mask)
          case "$Arg" in
            yes|no) ;;
            *) error "--$Argmethod: Argument $Option does not take value '$Arg'. Allowed values are yes|no" ;;
          esac
        ;;
        *) error "--$Argmethod does not take argument '$Option'." ;;
      esac
    ;;
    colorspace)
      case "$Argtype" in
        mask)
          checkmagicklist colorspace "$Arg" || error "--$Argmethod: unknown colorspace for C=: $Arg 
  Please choose one out of 'magick -list colorspace'"
          Argcolorspace="$(checkmagicklist colorspace "$Arg" print)"
        ;;
        *) 
          case "${1:-}" in
            colorspace) 
              checkmagicklist colorspace "$Arg" || error "--$Argmethod: unknown colorspace for C=: $Arg 
  Please choose one out of 'magick -list colorspace'"
              Argcolorspace="$(checkmagicklist colorspace "$Arg" print)"
            ;;
            *) error "--$Argmethod does not take argument colorspace: C=$Arg" ;;
          esac
        ;;
      esac
    ;;
    image)
      case "$Argtype" in
        mask)
          generate_image --mode "$Arg" --showname >/dev/null || error "--$Argmethod: unknown image generation format for Option -I='$Arg'.
  Please choose one out of:
  enfuse max min mean median
  'magick -list evaluate'
  'magick -list compose'
  'magick -list color"
          Argimage="$(generate_image --mode "$Argimage" --showmode)"
        ;;
        *) error "--$Argmethod does not take argument image comparision: I=$Arg" ;;
      esac
    ;;
    number1)
      case "$Argmethod" in
        freichen|laplacian|maskdespeckle|maskenhance|substacks) ;;
        *) error "--$Argmethod does not take argument number1: n$Arg" ;;
      esac
    ;;
    number2)
      case "$Argmethod" in
        fft|maskfft) ;;
        substacks) ;;
        *) error "--$Argmethod does not take argument number2: n$Arg" ;;
      esac
    ;;
    percent1)
      case "$Argmethod" in
        cutalpha|cutblur|cutless|cutmax|cutwave|fft|finalalpha|maskblur|maskfft|masklevel|maskwave|prepresize|resize|substacks|wavelet) ;;
        *) error "--$Argmethod does not take argument percent1: p$Arg" ;;
      esac
    ;;
    percent2)
      case "$Argmethod" in
        cutalpha|cutblur|finalalpha|maskblur|masklevel|maskwave|substacks|wavelet) ;;
        *) error "--$Argmethod does not take argument percent2: P$Arg" ;;
      esac
    ;;
    radius1)
      case "$Argmethod" in
        blur|comet|cutblur|dog|fft|finalblur2|finalsharpen|log|maskblur|maskfft|maskkuwahara) ;;
        morphology|cutmorph|maskmorph) ;;
        diffstat|statistic|maskstat) ;;
        substacks) ;;
        *) error "--$Argmethod does not take argument radius1: r$Arg" ;;
      esac
    ;;
    radius2)
      case "$Argmethod" in
        diffstat|fft|statistic) ;;
        morphology|cutmorph|maskmorph) ;;
        *) error "--$Argmethod does not take argument radius2: R$Arg" ;;
      esac
    ;;
    short)
    ;;
    sigma1)
      case "$Argmethod" in
        blur|comet|cutblur|cutsoft|dog|fft|finalblur|finalsharpen|finalthreshold|log|maskblur|maskfft) ;;
        *) error "--$Argmethod does not take argument sigma1: s$Arg" ;;
      esac
    ;;
    sigma2)
      case "$Argmethod" in
        blur) [ "$Arg" = "0" ] && Argsigma2="" ;;
        finalblur|dog|log) ;;
        *) error "--$Argmethod does not take argument sigma2: S$Arg" ;;
      esac
    ;;
    threshold1)
      case "$Argtype" in
        mask) ;;
        *)
          case "$Argmethod" in
            cutless|cutthreshold|finalblur|finalblur2|finalthreshold|masklevel|maskthreshold) ;;
            *) error "--$Argmethod does not take argument threshold1: t$Arg" ;;
          esac
        ;;
      esac
    ;;
    threshold2)
      case "$Argtype" in
        mask) ;;
        *)
          case "$Argmethod" in
            finalthreshold|maskthreshold) ;;
            *) error "--$Argmethod does not take argument threshold2: T$Arg" ;;
          esac
        ;;
      esac
    ;;
    weight)
      case "$Argtype" in
        mask) ;;
        *) error "--$Argmethod does not take argument weight: w$Arg" ;;
      esac
    ;;
    word1)
      case "$Argmethod" in
        bg)
          generate_image --mode "$Arg" --showname >/dev/null || error "--background: unknown image generation mode: '$Arg'.
  Please choose one out of:
  enfuse max min mean median
  'magick -list evaluate'
  'magick -list compose'
  'magick -list color"
          Argword1="$(generate_image --mode "$Arg" --showmode)"
        ;;
        blur|maskblur|cutblur)
          Argword1="${Argword1,,}"
          case "$Argword1" in
            ""|"gaussian") ;;
            *) error "--$Argmethod does not know argument: $Argword1" ;;
          esac
        ;;
        channel|colorspace) 
          checkmagicklist colorspace "$Arg" || error "--$Argmethod: unknown colorspace: $Arg
  Please choose one out of 'magick -list colorspace'"
          Argcolorspace="$(checkmagicklist colorspace "$Arg" print)"
          Argword1=""
        ;;
        cmd|cmddiff|cutcmd|finalcmd|maskcmd) ;;
        cutbg)
          case "$Argword1" in
            background|compose|off) ;;
            *) error "--$Argmethod: unknown argument for word1: $Arg" ;;
          esac
        ;;
        depthmap)
          [ -f "$Arg" ] || error "--$Argmethod: File not found: $Arg"
        ;;
        compose|comet|compass|freichen|maskmerge)
          checkmagicklist compose "${Arg%2}" || error "--$Argmethod: unknown argument for word1: $Arg
  Please choose one out of 'magick -list compose'"
          Argword1="$(checkmagicklist compose "${Arg%2}" print)$(tr -d -c "2" <<< "$Arg")"
        ;;
        evaluate)
          checkmagicklist evaluate "$Arg" || error "--$Argmethod: unknown argument for word1: $Arg
  Please choose one out of 'magick -list evaluate'"
          Argword1="$(checkmagicklist evaluate "$Arg" print)"
        ;;
        fft|maskfft)
          case "$Argword1" in
            cos*|gaussian|gradient|inverselog*|log*|pow*|solid) ;;
            *) error "--$Argmethod: unknown argument for word1: $Arg" ;;
          esac
        ;;
        finalalpha)
          case "$Argword1" in
            ""|"off") ;;
            *) error "--$Argmethod: unknown argument for word1: $Arg" ;;
          esac
        ;;
        finalgamma)
        ;;
        masklevel)
          case "$Arg" in
            all|off|substack) ;;
            *) error "--$Argmethod does not take argument: $Arg" ;;
          esac
        ;;
        morphology|cutmorph|maskmorph)
          checkmagicklist "morphology" "$(tr -d '2' <<< "$Arg")" || error "--$Argmethod:  unknown argument for word1: $Arg
  Please choose one out of 'magick -list morphology'"
          Argword1="$(checkmagicklist morphology "$Arg" print)"
        ;;
        prepresize|resize)
          checkmagicklist interpolate "$Arg" || error "--$Argmethod: unknown argument for word1: $Arg
  Please choose one out of 'magick -list interpolate'"
          Argword1="$(checkmagicklist interpolate "$Arg" print)"
        ;;
        substacks) 
          case "$Arg" in
            kurt) ;;
            *) error "--$Argmethod does not take argument: $Arg" ;;
          esac
        ;;
        statistic|diffstat|maskstat)
          case "${Arg,,}" in
            stdev|dev) Arg="StandardDeviation" ;;
            min)       Arg="Minimum" ;;
            max)       Arg="Maximum" ;;
            grad)      Arg="Gradient" ;;
          esac
          checkmagicklist statistic "$Arg" || error "--$Argmethod: unknown argument for word1: $Arg
  Please choose out of 'magick -list statistic'"
          Argword1="$(checkmagicklist statistic "$Arg" print)"
        ;;
        *) error "--$Argmethod does not take argument word1: $Arg" ;;
      esac
    ;;
    word2)
      case "$Argmethod" in
        channel|colorspace)
          Argchannel="$Arg"
          Argword2=""
        ;;
        diffstat)
          case "${Arg,,}" in
            stdev|dev) Arg="StandardDeviation" ;;
            min)       Arg="Minimum" ;;
            max)       Arg="Maximum" ;;
            grad)      Arg="Gradient" ;;
          esac
          checkmagicklist statistic "$Arg" || error "--$Argmethod: unknown argument for word2: $Arg
  Please choose out of 'magick -list statistic'"
          Argword2="$(checkmagicklist statistic "$Arg" print)"
        ;;
        fft|maskfft)
          case "$Argword2" in
            circle|cross|xcross|rhombus|square) ;;
            *) error "--$Argmethod: unknown argument for word2: $Arg" ;;
          esac
        ;;
        morphology|cutmorph|maskmorph)
          checkmagicklist kernel "$Arg" || error "--$Argmethod: unknown kernel: $Arg
  Please choose out of 'magick -list kernel'"
          Argword2="$(checkmagicklist kernel "$Arg" print)"
        ;;
        *) error "--$Argmethod does not take argument word2: $Arg" ;;
      esac
    ;;
  esac

  return 0
}
maskarg_defaultvalue() {
  local Argmethod Argument Output=
  Argmethod="${1:-}"
  Argument="${2:-}"
  case "$Argument" in
    channel)
      case "$Argmethod" in
        channel)        Output="" ;;
        chroma)         Output="1" ;;
        colorspace)     Output="" ;;
        darkness)       Output="2" ;;
        lightness)      Output="2" ;;
        saturation)     Output="1" ;;
      esac
    ;;
    colorspace)
      case "$Argmethod" in
        channel)        Output="" ;;
        chroma)         Output="HCL" ;;
        colorspace)     Output="sRGB" ;;
        darkness)       Output="HSL" ;;
        lightness)      Output="HSL" ;;
        saturation)     Output="HSL" ;;
      esac
    ;;
    clut)
    ;;
    diff)
      case "$Argmethod" in
        blur) [ -z "$Argsigma2" ] && Output="yes" ;;
        cmddiff)        Output="yes" ;;
        comet)          Output="yes" ;;
        fft)
          case "${Argnumber2,,}" in
            0)          Output="yes" ;;
            1)          Output="no" ;;
          esac
          [ -n "$Argradius2" ] && Output="no"
        ;;
        morphology)
          case ${Argword1,,} in
            close|edge|edgein|edgeout|bottomhat|tophat) ;;
            *)          Output="yes" ;; ### FIXME really?
          esac
        ;;
        resize)         Output="yes" ;;
        statistic)
          case ${Argword1,,} in
            gradient|standarddeviation) ;;
            *)          Output="yes" ;;
          esac
        ;;
        wavelet)
          [ -z "$Argpercent2" ] && Output="yes" ;;
        *)
          case "$Argtype" in
            mask)       Output="no" ;;
          esac
        ;;
      esac
    ;;
    image)
      case "$Argmethod" in
        enfuse)         Output="enfuse" ;;
        max)            Output="Max" ;;
        mean)           Output="Mean" ;;
        median)         Output="Median" ;;
        min)            Output="Min" ;;
      esac
    ;;
    level)
      case "$Argmethod" in
        channel)        Output="no" ;;
        chroma)         Output="no" ;;
        compose)        Output="no" ;;
        darkness)       Output="no" ;;
        depthmap)       Output="no" ;;
        enfuse)         Output="no" ;;
        evaluate)       Output="no" ;;
        lightness)      Output="no" ;;
        max)            Output="no" ;;
        mean)           Output="no" ;;
        median)         Output="no" ;;
        min)            Output="no" ;;
        saturation)     Output="no" ;;
        *)
          case "$Argtype" in
            mask)       Output="yes" ;;
          esac
        ;;
      esac
    ;;
    mask)
      case "$Argmethod" in
        channel)        Output="no" ;;
        chroma)         Output="no" ;;
        compose)        Output="no" ;;
        darkness)       Output="no" ;;
        depthmap)       Output="no" ;;
        enfuse)         Output="no" ;;
        evaluate)       Output="no" ;;
        lightness)      Output="no" ;;
        max)            Output="no" ;;
        mean)           Output="no" ;;
        median)         Output="no" ;;
        min)            Output="no" ;;
        saturation)     Output="no" ;;
        *)
          case "$Argtype" in
            mask)       Output="yes" ;;
          esac
        ;;
      esac
    ;;
    negate)
      case "$Argmethod" in
        darkness)       Output="yes" ;;
      esac
    ;;
    number1)
      case "$Argmethod" in
        freichen)       Output="0" ;;
        laplacian)      Output="3" ;;
        maskdespeckle|maskenhance)
                        Output="1" ;;
      esac
    ;;
    number2)
      case "$Argmethod" in
        fft|maskfft)    Output="0" ;;
      esac
    ;;
    percent1)
      case "$Argmethod" in
        cutalpha)       Output="0" ;;
        cutblur)        Output="100" ;;
        cutless)        Output="20" ;;
        cutmax)         Output="0" ;;
        cutwave)        Output="25" ;;
        fft)            Output="50" ;;
        finalalpha)     Output="0" ;;
        maskblur)       Output="100" ;;
        maskfft)        Output="50" ;;
        masklevel)      Output="" ;;
        maskwave)       Output="20" ;;
        prepresize)     Output="50" ;;
        resize)         Output="50" ;;
        wavelet)        Output="5" ;;
      esac
    ;;
    percent2)
      case "$Argmethod" in
        cutalpha)       Output="100" ;;
        cutblur)        Output="$((100-Argpercent1))" ;;
        finalalpha)     Output="100" ;;
        maskblur)       Output="$((100-Argpercent1))" ;;
        masklevel)      Output="" ;;
        wavelet)        Output="" ;;
      esac
    ;;
    radius1)
      case "$Argmethod" in
        blur)           Output="0" ;;
        comet)          Output="0" ;;
        cutblur)        Output="0" ;;
        cutmorph)       Output="1" ;;
        diffstat)       Output="3" ;;
        dog)            Output="0" ;;
        fft)            Output="75" ;;
        finalblur2)     Output="4" ;;
        finalsharpen)   Output="0" ;;
        log)            Output="0" ;;
        maskblur)       Output="0" ;;
        maskfft)        Output="25" ;;
        maskkuwahara)   Output="4" ;;
        maskmorph)      Output="1" ;;
        maskstat)       Output="5" ;;
        morphology)     Output="1" ;;
        statistic)      Output="2" ;;
      esac
    ;;
    radius2)
      case "$Argmethod" in
        cutmorph)       Output="1" ;;
        diffstat)       Output="$Argradius1" ;;
        fft)            Output=""  ;;
        maskmorph)      Output="1" ;;
        morphology)     Output="1" ;;
        statistic) ;;
      esac
    ;;
    sigma1)
      case "$Argmethod" in
        blur)           Output="0.3" ;;
        comet)          Output="5" ;;
        cutblur)        Output="1" ;;
        cutsoft)        Output="1" ;;
        dog)            Output="0.3" ;;
        fft)            Output="" ;;
        finalblur)      Output="5" ;;
        finalsharpen)   Output="1" ;;
        finalthreshold) Output="0" ;;
        log)            Output="0.2" ;;
        maskblur)       Output="1" ;;
        maskfft)        Output="" ;;
      esac
    ;;
    sigma2)
      case "$Argmethod" in
        blur)           Output="$(calc "$Argsigma1 * 1.6")"  ;;
        finalblur)      Output="2" ;;
        dog)            Output="$(calc "$Argsigma1 * 1.6")"  ;;
        log)            Output="" ;;
      esac
    ;;
    threshold1)
      case "$Argmethod" in
        cutthreshold)   Output="20" ;;
        finalblur)      Output="20" ;;
        finalblur2)     Output="20" ;;
        finalthreshold) Output="20" ;;
        masklevel)      Output="5" ;;
        maskthreshold)  Output="50" ;;
      esac
    ;;
    threshold2)
      case "$Argmethod" in
        finalthreshold) Output="100" ;;
        maskthreshold)  Output="100" ;;
      esac
    ;;
    type)
      case "$Argmethod" in
        blur|cmd|cmddiff|comet|compass|diffstat|dog|fft|freichen|kirsch|laplacian|log|morphology|prewitt|resize|roberts|sobel|statistic|wavelet)
                        Output="mask" ;;
        channel|chroma|darkness|lightness|saturation)
                        Output="mask" ;;
        compose|depthmap|enfuse|evaluate|max|mean|median|min)
                        Output="mask" ;;
        experimental)   Output="mask" ;;
        maskblur|maskcmd|maskdespeckle|maskenhance|maskfft|maskkuwahara|maskmorph|maskstat|masktest|maskthreshold|maskwave)
                        Output="merge" ;;
        cutalpha|cutbg|cutblur|cutcmd|cutless|cutmax|cutmorph|cutsoft|cuttest|cutthreshold|cutwave)
                        Output="focus" ;;
        finalalpha|finalblur|finalblur2|finalcmd|finalgamma|finalsharpen|finalthreshold)
                        Output="postfocus" ;;
        bg)             Output="background" ;;
        colorspace|masklevel|maskmerge|prepresize)
                        Output="single" ;;
        substacks)      Output="substack" ;;
        *) error "maskarg_defaultvalue(): Unknown type for method $Argmethod" ;;
      esac
    ;;
    word1)
      case "$Argmethod" in
        bg)             Output="enfuse" ;;
        blur)           Output="" ;;
        cmd|maskcmd|cutcmd|finalcmd)
                        Output="" ;;
        comet)          Output="Lighten" ;;
        compass)        Output="Lighten" ;;
        compose)        Output="Overlay" ;;
        cutbg)          Output="compose" ;;
        cutblur)        Output="" ;;
        cutmorph)       Output="Erode" ;;
        depthmap)       Output="NO_FILE_SPECIFIED" ;;
        diffstat)       Output="Median" ;;
        evaluate)       Output="Max" ;;
        fft)            Output="gradient" ;;
        finalgamma)     Output="auto" ;;
        freichen)       Output="Screen" ;;
        maskblur)       Output="" ;;
        maskfft)        Output="log1000" ;;
        masklevel)      Output="all" ;;
        maskmerge)      Output="Screen" ;;
        maskmorph)      Output="Close" ;;
        maskstat)       Output="Mean" ;;
        morphology)     Output="Edge" ;;
        prepresize)     Output="Spline" ;;
        resize)         Output="Spline" ;;
        statistic)      Output="StandardDeviation" ;;

        format)         Output="tif" ;;
        cacheformat)    Output="mpc" ;;
      esac
    ;;
    word2)
      case "$Argmethod" in
        diffstat)       Output="Mean" ;;
        fft|maskfft)    Output="circle" ;;
        morphology|cutmorph|maskmorph)
                        Output="Octagon" ;;
      esac
    ;;
  esac
  echo "$Output"
}
maskarg_single() {
  # provide arguments of an option $1 that can be specified only once
  local Count=
  eval $Arglist
  for Count in $(seq "$Imoptionsnumber"); do
    [ "${Imoptions[$Count]}" = "${1:-}" ] && maskarg_parse "$Count" && break
  done
  [ "${Imoptions[$Count]:-}" = "${1:-}" ]
}
maskarg_short() {
  local Output= Argument Append

  maskarg_parse "${1:-}"

  for Argument in $Arglist; do
    Argument="${Argument%'=""'}"
    Argument="${Argument#Arg}"
    Append=""
    case "$Argument" in
      weight)
        #[ -n "$Argweight" ]      && [ "$Argweight" != "100" ] && Append=",w$Argweight"
        [ -n "$Argweight" ]      && [ -z "$Argautoweight" ] && Append=",w$Argweight"
      ;;
      radius1)
        [ -n "$Argradius1" ]     && Append=",r$Argradius1"
        [ "$Argradius1" = "$(maskarg_defaultvalue "$Argmethod" radius1)" ] && case "$Argmethod" in
          blur|comet|cutblur|dog|finalsharpen|log|maskblur) Append="" ;;
        esac
      ;;
      radius2)
        [ -n "$Argradius2" ]     && Append=",R$Argradius2"
        [ "$Argradius2" = "$(maskarg_defaultvalue "$Argmethod" radius2)" ] && case "$Argmethod" in
          diffstat) Append="" ;;
        esac
      ;;
      sigma1)
        [ -n "$Argsigma1" ]      && Append=",s$Argsigma1"
        [ "$Argsigma1" = "$(maskarg_defaultvalue "$Argmethod" sigma1)" ] && case "$Argmethod" in
          #fft) Append="" ;;
        esac
      ;;
      sigma2)
        [ -n "$Argsigma2" ]      && Append=",S$Argsigma2"
      ;;
      percent1)
        [ -n "$Argpercent1" ]    && Append=",p$Argpercent1"
        [ "$Argpercent1" = "$(maskarg_defaultvalue "$Argmethod" percent1)" ] && case "$Argmethod" in
          maskblur|cutalpha|cutblur|fft|finalalpha|maskfft) Append="" ;;
        esac
      ;;
      percent2)
        [ -n "$Argpercent2" ]    && Append=",P$Argpercent2"
        [ "$Argpercent2" = "$(maskarg_defaultvalue "$Argmethod" percent2)" ] && case "$Argmethod" in
          cutalpha|cutblur|finalalpha|maskblur) Append="" ;;
        esac
      ;;
      number1)
        [ -n "$Argnumber1" ]     && Append=",n$Argnumber1"
        [ "$Argnumber1" = "$(maskarg_defaultvalue "$Argmethod" number1)" ] && case "$Argmethod" in
          fft|freichen) Append="" ;;
        esac
      ;;
      number2)
        [ -n "$Argnumber2" ]     && Append=",N$Argnumber2"
        [ "$Argnumber2" = "$(maskarg_defaultvalue "$Argmethod" number2)" ] && case "$Argmethod" in
          fft|maskfft) Append="" ;;
        esac
      ;;
      image)
        [ -n "$Argimage" ]       && Append=",I=$Argimage"
        [ "$Argimage" = "$(maskarg_defaultvalue "$Argmethod" image)" ] && case "$Argmethod" in
          enfuse|max|min|mean|median) Append="" ;;
        esac
      ;;
      colorspace)
        [ -n "$Argcolorspace" ]  && Append=",C=$Argcolorspace"
        [ "$Argcolorspace" = "$(maskarg_defaultvalue "$Argmethod" colorspace)" ] && case "$Argmethod" in
          chroma|darkness|lightness|saturation) Append="" ;;
        esac
        case "$Argmethod" in
          colorspace) Append=",${Append#,C=}" ;;
        esac
      ;;
      channel)
        [ -n "$Argchannel" ]     && Append=",c$Argchannel"
        [ "$Argchannel" = "$(maskarg_defaultvalue "$Argmethod" channel)" ] && case "$Argmethod" in
          chroma|darkness|lightness|saturation) Append="" ;;
        esac
      ;;
      diff)
        [ "$Argdiff" = "yes" ]   && Append=",diff"
        [ "$Argdiff" = "no"  ]   && Append=",diff=no"
        [ "$Argdiff" = "$(maskarg_defaultvalue "$Argmethod" diff)" ] && case "$Argmethod" in
          *) Append="" ;;
        esac
      ;;
      negate)
        [ "$Argnegate" = "yes" ]   && Append=",neg"
        [ "$Argnegate" = "no"  ]   && Append=",neg=no"
        [ "$Argnegate" = "$(maskarg_defaultvalue "$Argmethod" negate)" ] && case "$Argmethod" in
          darkness) Append="" ;;
        esac
      ;;
      word1)
        [ -n "$Argword1" ]       && case "$Argmethod" in
          #cmd|maskcmd|cutcmd|depthmap|finalcmd)
          #                          Append=",'$Argword1'" ;;
          *)                        Append=",$Argword1" ;;
        esac
        #[ "${Argword1,,}" = "standarddeviation" ] && Append=",StDev"
        [ "$Argword1" = "$(maskarg_defaultvalue "$Argmethod" word1)" ] && case "$Argmethod" in
          comet|compass|cutbg|diffstat|fft|finalgamma|freichen|statistic|masklevel|maskmerge) Append="" ;;
        esac
      ;;
      word2)
        [ -n "$Argword2" ]       && Append=",W2=$Argword2"
        [ "$Argword2" = "$(maskarg_defaultvalue "$Argmethod" word2)" ] && case "$Argmethod" in
          cutmorph|diffstat|fft|maskfft|maskmorph|morphology) Append="" ;;
        esac
      ;;
      level)
        [ -n "$Arglevel" ] && case "$Arglevel" in
          yes)                      Append=",level" ;;
          no)                       Append=",level=no" ;;
        esac
        [ "$Arglevel" = "$(maskarg_defaultvalue "$Argmethod" level)" ] && case "$Argmethod" in
          enfuse|evaluate|max|mean|median|min|compose) Append="" ;;
          channel|chroma|darkness|lightness|saturation) Append="" ;;
          depthmap) Append="" ;;
          *) Append="" ;;
        esac
      ;;
      clut)
        [ -n "$Argclut" ] && [ -n "$Arglevel" ] && case "$Argclut" in
          yes)                      Append=",clut" ;;
          no)                       Append=",clut=no" ;;
        esac
      ;;
      threshold1)
        [ -n "$Argthreshold1" ]  && Append=",t$Argthreshold1"
      ;;
      threshold2)
        [ -n "$Argthreshold2" ]  && Append=",T$Argthreshold2"
        [ "$Argthreshold2" = "$(maskarg_defaultvalue "$Argmethod" threshold2)" ] && case "$Argmethod" in
          finalthreshold|maskthreshold) Append="" ;;
        esac
      ;;
      mask)
        [ "$Argmask" = "yes" ]   && Append=",mask"
        [ "$Argmask" = "no"  ]   && Append=",mask=no"
        [ "$Argmask" = "$(maskarg_defaultvalue "$Argmethod" mask)" ] && case "$Argmethod" in
          *) Append="" ;;
        esac
      ;;
    esac
    Output="$Output$Append"
  done

  Output="${Output#,}"
  [ -n "$Output" ] && Output="=${Output}"
  Output="--${Argmethod}${Output}"

  echo "$Output"
}

### magick -script interaction

cmd() {
  local Command
  Command="${1:-}"
  [ -n "$Magickfifo" ] && {
    echo "$Command" >> "$Magickfifo"
    echo "$Command" >> "$Magickscriptlog"
    echo "$Command" >> "/tmp/imfuse.log"
  }
  return 
}
cmd_waitforkeyvalue() {
  # wait for keyword $1 in output of magick. Prints output after first :
  local Key Line
  Key="${1:-}"
  tail -f "$Magickfifolog" | while read Line; do
    grep -q "$Key" <<< "$Line" && break
  done
  echo "$(cut -d: -f2- <<< "$Line")"
  return 0
}
cmd_waitforready() {
  # wait for magick command toolchain to be ready
  local Key
  Key="$(generate_key)"
  sendmagickmessage "$Key"
  cmd_waitforkeyvalue "$Key" >/dev/null
  return
}
readmagickmessage() {
  # read and parse messages from magick
  local Line Code Content
  local Stopwatch Etacount Etanumber Etaduration Etatime Etaline Etapos
  local Esc Colnorm Colgreenbg

  Esc="$(printf '\033')"
  Colnorm="${Esc}[0m"
  Colgreenbg="${Esc}[42m"

  tail -f "$Magickfifolog" | while read Line; do
    Code="$(cut -d: -f1 <<< "$Line")"
    Content="$(cut -d: -f2- <<< "$Line")"

    Columns="${COLUMNS:-80}"
    grep -q "ETA:" <<< "$Content" && {
      Columns="$((Columns))"
      Etacount="${Content#*ETA:}"
      grep -q "/" <<< "$Etacount" && Etanumber="${Etacount#*/}"
      Etacount="${Etacount%/*}"
      Etanumber="${Etanumber:-$Sourceimagenumber}"
      Etaduration="$(( $(date +%s) - Stopwatch ))"
      Etatime="$(( Etacount * Etaduration / (Etanumber-Etacount+1) ))"
      Etaline="$Etacount/$Etanumber ETA: $(date -u -d @$Etatime +"%T"), DUR: $(date -u -d @$Etaduration +"%T")"
      Content="${Content%ETA:*}$Etaline"
      Content="$(printf "%-${Columns}s" "$Content")"
      Content="$(cut -c1-${Columns} <<< "imfuse$Subprocess: $Content")"
      Etapos="$((Columns*Etacount/Etanumber))"
      Content="${Colgreenbg}${Content:0:$Etapos}${Colnorm}${Content:$Etapos}"
    }

    case "$Code" in
      PROGRESS)
        printsameline "$Content"
      ;;
      /PROGRESS|NEWLINE)
        echo "$Colnorm" >&2
      ;;
      NOTE)
        note "$Content"
      ;;
      VERBOSE)
        verbose "$Content"
      ;;
      SHOW)
        showimage "$Content"
      ;;
      STOPWATCH)
        Stopwatch="$(date +%s)"
      ;;
    esac
    case "$Line" in
      *"@ warning"*)
        note "magick WARNING:
$Line"
      ;;
      *"@ error"*)
        error "magick ERROR:
$Line"
        break
      ;;
    esac
  done
}
sendmagickmessage() {
  # send message $1 to output of magick
  local Command=
  case "$(cut -d: -f1 <<< "${1:-}")" in
    PROGRESS)
      Command="$Command
# $(cut -d: -f2- <<< "${1:-}")"
    ;;
    NOTE)
      Command="$Command
#### $(cut -d: -f2- <<< "${1:-}") ####
#"
    ;;
    *)
      Command="$Command
  # sending message ${1:-}"
    ;;
  esac
  Command="$Command
  rose:
    -format '${1:-}\n'
    -write info:
    -delete -1"
  cmd "$Command"
  return 0
}
showimagecode() {
  local Image Imageformat
  # magick code to store current image and print a showimage message for readmagickmessage()
  [ "$Showimageprocessing" = "yes" ] && {
    Image="${1:-}"
    Imageformat="$(rev <<< "$Image" | cut -d. -f1 | rev)"
    [ "$(cut -c1-4 <<< "$Image")" = "mpr:" ] && Imageformat="mpr"
    case "$Imageformat" in
      ""|"mpr"|"mpc"|"miff")
        echo "# show image
    -compress none
    -depth 8
    -write '$Showimage'
    -format 'SHOW:$Showimage\n'
    -write info:"
      ;;
      *)
        sendmagickmessage "SHOW:$Image"
      ;;
    esac
  }
  return 0
}
getmagickinfo() {
  # get info output for image $1 in format $2
  local Key1 Key2 Command
  Key1="$(generate_key)"
  Key2="$(generate_key)"
  # get info ${2:-} about image ${1:-}
  Command="
  '${1:-}'
    -format '$Key1\n${2:-}\n$Key2\n'
    -write info:
    -delete -1"
  cmd "$Command"
  cmd_waitforready
  cmd_waitforkeyvalue "$Key2" >/dev/null
  sed -n "/$Key1/,/$Key2/p" "$Magickfifolog" | sed '1d ; $d'
}

### main

trap_sigint() {
  local Count=0
  trap - ERR
  set +x
  [ "$$" = "$Imfusepid" ] && {
    note "Received SIGINT"
    [ -n "$Magickscriptpid" ] && kill "$Magickscriptpid"
    multicore_break
    finish 1
    :
  } || {
    note "Received SIGINT in subshell $SHLVL: $$"
    kill -s SIGINT "$Imfusepid"
    trap - EXIT
    exit 130
  }
}
finish() {
  local Count=0
  trap - ERR
  set +x

  multicore_wait
  break 2>/dev/null
  cmd '-exit'
  while ps -p "$Magickscriptpid" >/dev/null 2>&1; do
    sleep 1
    Count="$((Count+1))"
    [ "$Count" -gt "3" ] && printsameline "Waiting infinitely since $Count seconds for magick pid $Magickscriptpid to terminate"
  done
  echo "" >&2

  # magick -script
  [ -n "$Magickmessagepid" ] && kill "$Magickmessagepid"
  exec 3>&-
  rm -f "$Magickfifo" "$Magickfifolog" >/dev/null 2>&1

  #[ -f "$Magickscriptlog" ] && cp "$Magickscriptlog" .

  # cache
  [ -f "${Showimage:-}" ] && rm "$Showimage"
  [ "$Keepcache" = "no" ] && [ -d "${Cachedir}" ] && rm -R "${Cachedir}"
  [ -n "$Cachebasedir" ] && case "$Singleaction" in
    --rmcache)
      note "Option --rmcache: Cleaning cache: $Cachebasedir"
      [ -d "$Cachebasedir" ] && rm -R "$Cachebasedir"
    ;;
    *)
      note "Current cache size: $(du -hs "$Cachebasedir" 2>/dev/null | tr "\t" " " || echo "0")
  You can clean the cache folder with option --rmcache."
    ;;
  esac

  # jobs
  trap - SIGINT
  trap - EXIT
  jobs -l -r
  wait

  exit "${1:-0}"
}
declare_variables() {
  Arglist='
  Argweight=""
  Argradius1=""
  Argradius2=""
  Argsigma1=""
  Argsigma2=""
  Argnumber1=""
  Argnumber2=""
  Argpercent1=""
  Argpercent2=""
  Argimage=""
  Argcolorspace=""
  Argchannel=""
  Argnegate=""
  Argthreshold1=""
  Argthreshold2=""
  Argword1=""
  Argword2=""
  Argcount=""
  Argdiff=""
  Arglevel=""
  Argclut=""
  Argmask=""
  Argmethod=""
  Argoptions=""
  Argtype=""
  Argautoweight=""
  Argbasename=""
  '
  eval $Arglist

  Align=""
  Cachebasedir=""
  Cachedir=""
  Colorspace=""
  Colorspacechannel=""
  #Exiflist=""
  Exifsourceimage=""
  Extendedsave="no"
  Fftsize=""
  Finalmd5=""
  Firstimage=""
  Force=""
  Freemem=""
  Image=""
  Imageformat=""
  Imageheight=""
  Imagelistmemsize=""
  Imagememsize=""
  Imagewidth=""
  #Imarguments=""
  Imfusepid="$$"
  #Imoptions=""
  Imoptionsnumber="0"
  #Imsourceimagelist=""
  Keepcache="no"
  Limitmemory=""
  Line=""
  Loadsourceimages=""
  Longname=""
  Magickbin=""
  Magickfifo=""
  Magickfifolog=""
  Magickmessagepid=""
  Magickpixelmemory=""
  Magickscriptlog=""
  Magickscriptpid=""
  Magickversion=""
  Maskmethodnumber="0"
  Cacheformat=""
  Masktocache="no"
  Mergemaskbasename=""
  Optionmd5=""
  Outputbasename=""
  Outputdir=""
  Parsedoptions=""
  Revertimagelist=""
  Resultbasename=""
  Resultdepthmap=""
  Resultimage=""
  Resultmask=""
  Resultsearchmask=""
  Resulttimestamp=""
  Showimage=""
  Showimageprocessing="no"
  Singleaction=""
  Singleoptionlist=""
  Softmode="no"
  Sourceimage=""
  #Sourceimagelist=""
  Sourceimagenumber="0"
  Sourceimagepath=""
  Sourcemd5=""
  Startzeit="$(date +%s)"
  Storelayered="no"
  Subprocess=""
  Substackautoall="no"
  Substackfirstimage=""
  Substacklastimage=""
  #Substackmask=""
  #Substackmaskpost=""
  Substackmd5=""
  #Substackresult=""
  #Substackresultpost=""
  Substacknumber="0"
  Substacksize=""
  Substackstep=""
  Testarg=""
  Testimage=""
  Testsetup=""
  Tifstore="-alpha off +repage -depth 16 -quality 100% -compress lzw -type optimize"
  Tifstorealpha="+repage -depth 16 -quality 100% -compress lzw -type TrueColorAlpha"
  Verbose=""
  Video=""
  Videoframecount=""
  Viewnior=""

  return 0
}
parse_options() {
  local Shortoptions Longoptions Parsedoptions

  Shortoptions="BCfhLo:vVWX"
  Longoptions="cache::,help,license,limit-memory::,mask2cache,cacheformat::,rmcache,sub::,test::,verbose,version"
  Longoptions="$Longoptions,basename::,exif::,force::,format::,longname,output:,showname,video"
  Longoptions="$Longoptions,align,revert"
  Longoptions="$Longoptions,colorspace::,prepresize::"
  Longoptions="$Longoptions,blur::,comet::,compass::,diffstat::,dog::,fft::,freichen::,kirsch::,laplacian::,log::,morphology::,prewitt::,resize::,roberts::,sobel::,statistic::,wavelet::"  
  Longoptions="$Longoptions,compose::,depthmap::,enfuse::,evaluate::,max::,mean::,median::,min::"
  Longoptions="$Longoptions,channel::,chroma::,darkness::,lightness::,saturation::"
  Longoptions="$Longoptions,cmd::,cmddiff::,experimental::"
  Longoptions="$Longoptions,maskblur::,maskcmd::,maskdespeckle::,maskenhance::,maskfft::,maskkuwahara::,masklevel::,maskmerge::,maskmorph::,maskstat::,masktest::,maskthreshold::,maskwave::"
  Longoptions="$Longoptions,cutalpha::,cutbg::,cutblur::,cutcmd::,cutless::,cutmax::,cutmorph::,cutsoft::,cuttest::,cutthreshold::,cutwave::"
  Longoptions="$Longoptions,substacks::"
  Longoptions="$Longoptions,finalalpha::,finalblur::,finalblur2::,finalcmd::,finalgamma::,finalsharpen::,finalthreshold::"
  Longoptions="$Longoptions,background::,bg::,layered"

  Parsedoptions="$(getopt --options "$Shortoptions" --longoptions "$Longoptions" --name "$0" -- "$@")" || error "Error while parsing options."
  eval set -- "$Parsedoptions"

  while [ $# -gt 0 ]; do
    case "${1:-}" in
      --align)             Align="yes"                         ;;
      -B)                  Outputbasename="auto"               ;;
      --basename)          Outputbasename="${2:-auto}"         ; shift ;;
      -C)                  Cachedir="auto"                     ;;
      --cache)             Cachedir="${2:-auto}"               ; shift ;;
      --cacheformat)       Cacheformat="${2:-mpc}"             ; shift ;;
      --exif)              Exifsourceimage="${2:-auto}"        ; shift ;;
      -f)                  Force="yes"                         ;;
      --force)             Force="${2:-yes}"                   ; shift ;;
      --format)            Imageformat="${2:-tif}"             ; shift ;;
      --layered)           Storelayered="yes"                  ;;
      --limit-memory)      Limitmemory="${2:-"80%"}"           ; shift ;;
      -L|--longname)       Longname="yes"                      ;;
      --mask2cache)        Masktocache="yes"                   ;;
      -o|--output)         Resultimage="${2:-}"                ; shift ;;
      --revert)            Revertimagelist="-r"                ;;
      --sub)               Subprocess=" ${2:-sub}"             ; shift ;;
      --test)              Testsetup="test" ; Testarg="${2:-}" ; shift ;;
      -v|--verbose)        Verbose="yes"                       ;;
      --video)             Video="auto"                        ;;
      -V)                  Showimageprocessing="yes"           ;;
      -W)                  Viewnior="yes"                      ;;
      -X)                  Extendedsave="yes"                  ;;
      -h|--help|--license|--rmcache|--showname|--version) Singleaction="${1:-}" ;;

--colorspace|--prepresize|\
--cmd|--cmddiff|--experimental|\
--blur|--comet|--compass|--diffstat|--dog|--fft|--freichen|--kirsch|--laplacian|--log|--morphology|--prewitt|--resize|--roberts|--sobel|--statistic|--wavelet|\
--channel|--chroma|--darkness|--lightness|--saturation|\
--compose|--depthmap|--enfuse|--evaluate|--max|--mean|--median|--min|\
--maskblur|--maskcmd|--maskdespeckle|--maskenhance|--maskfft|--maskkuwahara|--masklevel|--maskmerge|--maskmorph|--maskstat|--masktest|--maskthreshold|--maskwave|\
--cutalpha|--cutbg|--cutblur|--cutcmd|--cutless|--cutmax|--cutmorph|--cutsoft|--cuttest|--cutthreshold|--cutwave|\
--finalalpha|--finalblur|--finalblur2|--finalcmd|--finalgamma|--finalsharpen|--finalthreshold|\
--bg|--background|\
--substacks)
                           maskarg_add "${1:-}" "${2:-}"       ; shift ;;
      --) ;;
      *)
        [ -f "${1:-}" ] || error "File not found: ${1:-}"
        Sourceimagenumber="$((Sourceimagenumber+1))"
        Sourceimagelist[$Sourceimagenumber]="${1:-}"
      ;;
    esac
    shift
  done
  #IFS=$'\n' Sourceimagelist=( '' $(sort -V $Revertimagelist <<< "${Sourceimagelist[@]:-}") ) ; unset IFS
  IFS=$'\n' Sourceimagelist=( '' $(sort -V $Revertimagelist <<< "${Sourceimagelist[*]:-}")) ; unset IFS
  unset Sourceimagelist[0]
  return 0
}
check_options() {
  local Arg Weightsum= Weightrest Masknoweightcount=
  local Firstimage Lastimage Substacksize Position

  # w, weight
  for Count in $(seq $Imoptionsnumber); do
    maskarg_parse "$Count"
    case $Argtype in
      mask)
        Weightsum="$((Weightsum + Argweight))"
        [ -z "$Argweight" ]   && Masknoweightcount="$((Masknoweightcount+1))"
      ;;
    esac
  done
  Weightrest="$((100-Weightsum))"
  LC_ALL=C awk 'BEGIN {exit !('${Weightsum:-100}' > 100)}' && error "Sum of weight arguments exceed 100%."
  for Count in $(seq $Imoptionsnumber); do
    maskarg_parse "$Count"
    case $Argtype in
      mask)
        [ -z "$Argweight" ] && {
          LC_ALL=C awk 'BEGIN {exit !('${Weightrest:-0}' <= 0)}' && error "Sum of weight arguments exceed 100%, nothing left for --$Argmethod."
          Argweight="$(calc "$Weightrest / $Masknoweightcount ")"
          Argweight="${Argweight%.*}"
          Argautoweight="yes"
        }
        maskarg_store "$Count"
      ;;
    esac
  done

  # --align
  [ "$Align" = "yes" ] && {
    command -v focus-stack >/dev/null || error "--align: focus-stack not found.
  https://github.com/PetteriAimonen/focus-stack"
  }

  # --basename
  case $Outputbasename in
    auto) Outputbasename="$(basename "$(pwd)")" ;;
  esac
  [ -d "$Outputbasename" ] && {
    Outputdir="$Outputbasename"
    Outputbasename=""
  } || {
    [[ "$Outputbasename" = *"/"* ]] && {
      Outputdir="$(dirname "$Outputbasename")"
      Outputbasename="$(basename "$Outputbasename")"
    }
  }

  # --cacheformat
  Cacheformat="${Cacheformat:-"$(maskarg_defaultvalue cacheformat word1)"}"

  # --colorspace
  maskarg_single "colorspace" && {
    Colorspace="${Argcolorspace:-}"
    Colorspacechannel="${Argchannel:-}"
  }
  Colorspace="${Colorspace:-"$(maskarg_defaultvalue colorspace colorspace)"}"

  # --exif
  [ "$Exifsourceimage" ] && {
    [ "$Exifsourceimage" = "auto" ] && { 
      Exifsourceimage="${Sourceimagelist[1]}"
      [ "$Revertimagelist" ] && Exifsourceimage="${Sourceimagelist[$Imagenumber]}"
    }
    command -v exiftool >/dev/null || error "Option --exif: exiftool not found."
  }

  # --format
  Imageformat="${Imageformat:-"$(maskarg_defaultvalue format word1)"}"

  # --masklevel
  maskarg_single "masklevel" || {
    grep -q "TYPE=mask" <<< "${Imarguments[*]}" && maskarg_add "masklevel"
  }

  # --substacks
  maskarg_single "substacks" || {
    [ "$Maskmethodnumber" -gt "0" ] && {
      maskarg_add "substacks" "n1,N${Sourceimagenumber}"
      Substackautoall="yes"
    }
  }
  for Count in $(seq "$Imoptionsnumber"); do
    maskarg_parse "$Count"
    [ "$Argtype" = "substack" ] && {
      [ -z "$Argnumber1" ] && [ -n "$Argpercent1" ] && Argnumber1="$(numberofpercent "$Argpercent1")"
      [ -z "$Argnumber2" ] && [ -n "$Argpercent2" ] && Argnumber2="$(numberofpercent "$Argpercent2")"
      grep -q "%" <<< "$Argradius1" && Argradius1="$(numberofpercent "$Argradius1")"
      [ -z "$Argnumber2" ] && [ -z "$Argradius1" ]  && Argradius1="$(numberofpercent "5")" # default radius 5%
      [ -z "$Argnumber1" ] && Argnumber1="$(numberofpercent "14")"                         # default ~7 substacks
      maskarg_store "$Count"
      # single substack
      [ -n "$Argnumber1" ] && [ -n "$Argnumber2" ] && {
        Argradius1="${Argradius1:-0}"
        Firstimage="$((Argnumber1-Argradius1))"
        Lastimage="$((Argnumber2+Argradius1))"
        [ "$Firstimage" -lt "1" ]                 && Firstimage="1"
        [ "$Lastimage" -gt "$Sourceimagenumber" ] && Lastimage="$Sourceimagenumber"
        Substacknumber="$((Substacknumber+1))"
        Substackfirstimage[$Substacknumber]="$Firstimage"
        Substacklastimage[$Substacknumber]="$Lastimage"
      }

      # generate set of substacks
      [ -n "$Argnumber1" ] && [ -z "$Argnumber2" ] && {
        Substacksize="$Argnumber1"
        Position="0"
        while [ "${Lastimage:-0}" -lt "$Sourceimagenumber" ]; do
          Firstimage="$((Position-Argradius1))"
          Lastimage="$((Position+Substacksize+Argradius1))"
          [ "$Firstimage" -lt "1" ]                 && Firstimage="1"
          [ "$Argword1" = "kurt" ]                  && Firstimage="1"
          [ "$Lastimage" -gt "$Sourceimagenumber" ] && Lastimage="$Sourceimagenumber"
          Substacknumber="$((Substacknumber+1))"
          Substackfirstimage[$Substacknumber]="$Firstimage"
          Substacklastimage[$Substacknumber]="$Lastimage"
          Position="$((Position+Substacksize))"
        done
      }
    }
  done

  # -V
  [ "$Showimageprocessing" = "yes" ] && {
    command -v geeqie >/dev/null || error "Option -V: geeqie not found."
  }

  # -W
  [ "$Viewnior" ] && {
    command -v feh >/dev/null || error "Option -W: image viewer feh not found."
  }

  ### FIXME dependency checks for enfuse

  return 0
}
check_magick() {
  # magick
  Magickbin="$(command -v magick)"
  false && [ -e "/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4.5.6" ] && {
    # https://imagemagick.org/script/openmp.php
    # https://goog-perftools.sourceforge.net/doc/tcmalloc.html
    Magickbin="env LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4.5.6 $Magickbin"
  }
  [ -z "$Magickbin" ] && error "Command 'magick' not found. Please install ImageMagick version 7."

  # check magick version
  Magickversion="$($Magickbin -version)"

  # Bytesperpixel*(channels)*unknownfactor
  grep -q " Q8 "        <<< "$Magickversion" && Magickpixelmemory="$(calc "1*(5) *1.2")"
  grep -q " Q8-HDRI "   <<< "$Magickversion" && Magickpixelmemory="$(calc "2*(5) *1.2")"
  grep -q " Q16 "       <<< "$Magickversion" && Magickpixelmemory="$(calc "2*(5) *1.2")"
  grep -q " Q16-HDRI "  <<< "$Magickversion" && Magickpixelmemory="$(calc "4*(5) *1.2")"
  grep -q " Q32 "       <<< "$Magickversion" && Magickpixelmemory="$(calc "4*(5) *1.2")"
  grep -q " Q32-HDRI "  <<< "$Magickversion" && Magickpixelmemory="$(calc "8*(5) *1.2")"
  Magickversion="$(head -n1 <<< "$Magickversion" | cut -d' ' -f3)"
}
main_setup() {
  local Resultimage_check

  # --cache
  Cachebasedir="$HOME/.cache/imfuse"
  [ -n "${Cachedir}" ] && {
    Masktocache="yes"
    Keepcache="yes"
    [ "${Cachedir}" = "auto" ] && Cachedir=""
  }
  Cachedir="${Cachedir//"~"/"$HOME"}"
  [ -n "${Cachedir}" ] && Cachebasedir="${Cachedir}"
  case "$Masktocache" in
    yes) [ -z "${Cachedir}" ] && Cachedir="$Cachebasedir" ;;
    no)  [ -z "${Cachedir}" ] && Cachedir="$Cachebasedir/$Imfusepid" ;;
  esac
  mkdir -p "${Cachedir}"   || error "Error creating cache folder ${Cachedir}"
  Testimage="${Cachedir}/test.png"
  Showimage="${XDG_RUNTIME_DIR:-${Cachedir}}/imfuse.showimage.tif"

  # check RAM
  Freemem="$(printfreememory)"
  [ -z "$Freemem" ] && {
    note "WARNING: failed to estimate free memory.
  Blindly guessing 1GB."
    Freemem="1000000"
  }
  Limitmemory="${Limitmemory:-"80%"}"
  grep -q "%" <<< "$Limitmemory" && {
    Limitmemory="$(tr -d "%" <<< "$Limitmemory")"
    Limitmemory="$((Freemem*Limitmemory/100))"
  } || {
    Limitmemory="$((Limitmemory*1000))"
  }

  # Image properties
  [ "$Sourceimagenumber" -gt "0" ] && {
    Image="${Sourceimagelist[1]}"
    Imagewidth="$($Magickbin identify -format '%w' "$Image")"
    Imageheight="$($Magickbin identify -format '%h' "$Image")"
    Imagememsize="$((Imagewidth*Imageheight*Magickpixelmemory/1000))"
    Imagememsize="$((Imagememsize*125/100))" # by observation. Alpha channel?
    Imagelistmemsize="$((Sourceimagenumber*Imagememsize))"
    Sourcemd5="$(md5cut "$( ls -l --full-time "${Sourceimagelist[@]}" )" )"

    # --output, --basename
    check_exifstring
    check_outputname
  }

  # check if source images should be loaded to RAM
  case "$Masktocache" in
    yes) compare "$((Imagelistmemsize))" -lt "$(calc "$Limitmemory*0.9")" && Loadsourceimages="yes" || Loadsourceimages="no" ;;
    no)  compare "$((Imagelistmemsize))" -lt "$(calc "$Limitmemory*0.3")" && Loadsourceimages="yes" || Loadsourceimages="no" ;;
  esac

  verbose "
  imfuse Version:                     $Version
  Imagemagick version:                $Magickversion
  Image number:                       $Sourceimagenumber
  Image width:                        $Imagewidth px
  Image height:                       $Imageheight px
  Image memory size (estimated):      $Imagememsize KB
  Image list memory size (estimated): $((Imagelistmemsize/1024)) MB
  Memory total (including zram):      $(($(printtotalmemory)/1000)) MB
  Memory currently free:              $((Freemem/1000)) MB
  Memory limit for imagemagick:       $((Limitmemory/1000)) MB
  Loading source images to RAM:       $Loadsourceimages"
  note "Parsed image processing options:
$Parsedoptions"

  # start magick -script in background
  Magickfifo="${Cachedir}/magickfifo.$Imfusepid"
  Magickfifolog="${Cachedir}/magickmessage.$Imfusepid.log"
  Magickscriptlog="${Cachedir}/magickscript.log"
  :> "$Magickscriptlog"
  :> /tmp/imfuse.log
  mkfifo "$Magickfifo"
  exec 3<>"$Magickfifo"
  nice magick -limit memory ${Limitmemory}KB -define registry:temporary-path="${Cachedir}" -script - <&3 >"$Magickfifolog" 2>&1 & Magickscriptpid="$!"
  readmagickmessage & Magickmessagepid="$!"

  Command="
#! /usr/bin/magick -script
# Generated by imfuse $Version
# Date:    $(date)
# Image:   $Resultbasename
# Options: $Parsedoptions
  ( wizard:
      -colorspace $Colorspace"
      [ -n "$Colorspacechannel" ] && Command="$Command
      -channel $Colorspacechannel
      -separate
      +channel"
      Command="$Command
  )
    -statistic StandardDeviation 2
    -evaluate Divide 3
    -separate
    -evaluate-sequence Add
    -auto-level
    $Tifstorealpha
    -write '$Showimage'
    -delete 0

  # clut for depth map
  -size 1x512
  gradient:white-red
  gradient:red-orange
  gradient:orange-yellow
  gradient:yellow-green
  gradient:green-blue
  gradient:blue-indigo
  #gradient:indigo-black
    -append
    -rotate 90
    -flip
    -write mpr:rainbow
    -delete 0
    "
  cmd "$Command"
  cmd_waitforready

  # start geeqie
  [ "$Showimageprocessing" = "yes" ] && nohup geeqie -t -r --File:"$Showimage" </dev/null >/dev/null 2>&1

  # --force
  case "$Force" in
    "")
      Resultimage_check="$(find . -name "${Resultsearchmask}.result.*" | tail -n1)"
      [ -n "$Resultimage_check" ] && {
        Resultimage="$Resultimage_check"
        Resultdepthmap="$(find . -name "${Resultsearchmask}.depthmap.*" | tail -n1)"
        Resultmask="$(find . -name "${Resultsearchmask}.mask.*" | tail -n1)"
        note "Output image already exists with matching md5sum.
  You can force imfuse to run nonetheless with option --force."
        showresult
        finish
      }
    ;;
    mask)
      forcemask
      forcelevel
      forcemerge
      forcefocus
      forcepost
    ;;
    level)
      forcelevel
      forcemerge
      forcefocus
      forcepost
    ;;
    merge)
      forcemerge
      forcefocus
      forcepost
    ;;
    focus)
      forcefocus
      forcepost
    ;;
    post|postfocus)
      forcepost
    ;;
  esac

  return 0
}
main() {
  set -Eu
  trap trap_sigint SIGINT
  trap finish      EXIT
  trap 'traperror $? $LINENO $BASH_LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]})'  ERR
  shopt -s lastpipe

  declare_variables
  check_magick
  parse_options "$@"
  check_options

  case "$Singleaction" in
    -h|--help)
      usage
      finish
    ;;
    --license)
      license
      finish
    ;;
    --version)
      note "$Version"
      finish
    ;;
  esac

  [ "$Sourceimagenumber" = "0" ] && [ -z "$Singleaction" ] && error "No source images provided."
  main_setup
  multicore_init

  case "$Singleaction" in
    --rmcache)
      finish
    ;;
    --showname)
      echo "$Resultimage"
      finish
    ;;
  esac

  # --align
  [ "$Align" = "yes" ] && {
    align || error "Error in align()."
  }

  # run
  focus_main

  # --exif: transfer EXIF metadata from first image to result, store imfuse version and parsed options
  [ "$Exifsourceimage" ] && exiftransfer "$Exifsourceimage" "$Resultimage"
  command -v exiftool && {
    case "$Extendedsave" in
      yes)
        Exiflist=("$Resultimage" "$Resultmask" "$Resultdepthmap")
        #exiftool -overwrite_original -Software="imfuse v$Version" -ImageDescription="$Parsedoptions" -Make="$Sourceimagepath" "$Resultimage" "$Resultmask" "$Resultdepthmap"
      ;;
      no)
        Exiflist=("$Resultimage")
        #exiftool -overwrite_original -Software="imfuse v$Version" -ImageDescription="$Parsedoptions" -Make="$Sourceimagepath" "$Resultimage"
      ;;
    esac
    for Image in "${Exiflist[@]}"; do
      [ -f "$Image" ] && exiftool -overwrite_original -Software="imfuse v$Version" -ImageDescription="$Parsedoptions" -Make="$Sourceimagepath" "$Image"
    done
    :
  } || {
    note "Failed to store metadata in output image. Is exiftool installed?"
  }

  # throw out
  cmd "-exit"
  note "Ready after $(date -u -d @$(($(date +%s)-Startzeit)) +"%T")"
  showresult

  # --video
  [ "$Video" ] && generate_video

  return 0
}
main "$@"
finish