<?xml version="1.0" encoding="utf-8"?>

<Pipelines xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation=""
>
    <Arguments>
        <Argument flag="-to" action="store" dest="To" default=""
                  help="E-Mail addresses for reports. Use with pipelines that emit notifications." nargs="*"/>
        <Argument flag="-cc" action="store" dest="CC" default=""
                  help="E-Mail addresses for reports. Use with pipelines that emit notifications." nargs="*"/>
    </Arguments>

    <Pipeline Name="ImportIDoc" Help="Import SerialEM IDOC into a volume." Description="Imports SerialEM IDOC metadata and tile images into VolumeData.xml sections/channels. Use this as the normal first step for IDOC datasets before prune/mosaic alignment." Epilog="Example: nornir-build ImportIDoc /data/volume /data/idoc -Sections 1-10 -CameraBpp 14">
        <Arguments>
            <Argument flag="ImportDir"
                      help="Directory to import from.  The .idoc files in subdirectories should be named with the desired section number after import. i.e. 0005.idoc to import the file as section #5."/>
            <Argument flag="-ext" dest="extension" default="*.idoc"
                      help="Extension of idoc files, default is idoc"/>
            <Argument flag="-Min" dest="MinValue" type="float"
                      help="Min intensity cutoff.  A value from 0.0 to 1.0" required="False"
                      default="0.0001"/>
            <Argument flag="-Max" dest="MaxValue" type="float"
                      help="Max intensity cutoff.  A value from 0.0 to 1.0" required="False"
                      default=".9999"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-CameraBpp" dest="CameraBpp" type="int"
                      help="The actual bits-per-pixel of the camera.  Limits the range of image histograms being expanded beyond reason due to errors in input image data or meta-data."
                      required="False"/>
        </Arguments>

        <PythonCall Module="nornir_buildmanager.importers.idoc"
                    Function="Import" ImportPath="#ImportDir" extension="#extension" Min="#MinValue" Max="#MaxValue"
                    CameraBpp="#CameraBpp" Sections="#Sections"/>
    </Pipeline>

    <Pipeline Name="ImportPMG" Help="Import PMG file into a volume" Description="Imports PMG files named with section/channel metadata into a volume. Use -Scale to provide nanometers-per-pixel for downstream geometry and reporting." Epilog="Example: nornir-build ImportPMG /data/volume /data/pmg -Scale 4.0">
        <Arguments>
            <Argument flag="ImportDir"
                      help="Directory to import from.  The PMG file should follow this format: Slide#_Block#_Section#_Initials_Mag_Spot_Probe.  However only Section and Probe are used as the section number and channel name respectively.  The rest is stored as meta-data."/>
            <Argument flag="-Scale" type="float" dest="scaleValueInNm"
                      help="Pixel size in nanometers" required="True"/>
            <Argument flag="-ext" dest="extension" default="pmg"
                      help="Extension of pmg files, default is pmg"/>
        </Arguments>

        <PythonCall Module="nornir_buildmanager.importers.pmg"
                    Function="Import" ImportPath="#ImportDir" extension="#extension"
                    scaleValueInNm="#scaleValueInNm"/>
    </Pipeline>

    <Pipeline Name="ImportMRC" Help="Import SerialEM MRC files into a volume.">
        <Arguments>
            <Argument flag="ImportDir"
                      help="Directory to import from.  The .mrc files in subdirectories should be named with the desired section number after import. i.e. 0005.mrc to import the file as section #5."/>
            <Argument flag="-ext" dest="extension" default="mrc"
                      help="Extension of mrc files, default is mrc"/>
            <Argument flag="-Min" dest="MinValue" type="float"
                      help="Min intensity cutoff.  A value from 0.0 to 1.0" required="False"
                      default="0.0001"/>
            <Argument flag="-Max" dest="MaxValue" type="float"
                      help="Max intensity cutoff.  A value from 0.0 to 1.0" required="False"
                      default=".9999"/>
            <Argument flag="-CameraBpp" dest="CameraBpp" type="int"
                      help="The actual bits-per-pixel of the camera.  Limits the range of image histograms being expanded beyond reason due to errors in input image data or meta-data."
                      required="False"/>
        </Arguments>

        <PythonCall Module="nornir_buildmanager.importers.mrc"
                    Function="Import" ImportPath="#ImportDir" extension="#extension"
                    Min="#MinValue" Max="#MaxValue" CameraBpp="#CameraBpp"/>
    </Pipeline>

    <Pipeline Name="ImportImages"
              Help="Import single images that represent an entire section in a volume">
        <Arguments>
            <Argument flag="ImportDir" help="Directory to import from"/>
            <Argument flag="-Scale" type="float" dest="scaleValueInNm"
                      help="Pixel size in nanometers" required="True"/>
            <Argument flag="-ext" dest="extension" default="png"
                      help="Extension of image files, default is png"/>
        </Arguments>

        <PythonCall Module="nornir_buildmanager.importers.sectionimage"
                    Function="Import" ImportPath="#ImportDir" extension="#extension" scaleValueInNm="#scaleValueInNm"/>

    </Pipeline>

    <Pipeline Name="ImportDM4" Help="Import Digital Micrograph 4" Description="Imports DM4 tile datasets into channels/sections and records overlap metadata for mosaic registration." Epilog="Example: nornir-build ImportDM4 /data/volume /data/dm4 -Sections 1-20 -overlap 10,20">
        <Arguments>
            <Argument flag="ImportDir" help="Directory to import from"/>
            <Argument flag="-ext" dest="extension" default="dm4"
                      help="Extension of digital micrograph version 4 files, default is dm4"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-overlap" dest="tile_overlap" required="False" type="FloatPair"
                      help="Overlap of tiles as a percentage, either a single value or a comma-delimited pair (X%%,Y%%).\n\tex: 15 would be a 15 overlap on both axis.\n\tex:  10,20 would be a 10%% X overlap and 20%% Y overlap."/>
        </Arguments>

        <PythonCall Module="nornir_buildmanager.importers.dm4"
                    Function="Import" ImportPath="#ImportDir" extension="#extension" tile_overlap="#tile_overlap"
                    Sections="#Sections"/>
    </Pipeline>

    <Pipeline Name="AdoptVikingVolume"
              Help="Adopt a Viking-layout RC1 clone into a modern VolumeData.xml tree."
              Description="Moves and renames files from a Viking volume clone (RC1_Original) into TEM/NNNN/TEM with locked mosaics, stos, and filters. Image bytes are not rewritten. Do not point this at the live Rabbit volume."
              Epilog="Example: nornir-build AdoptVikingVolume Y:\Volumes\RC1 Y:\Volumes\RC1_Original -Sections 1,2,47 -DryRun">
        <Arguments>
            <Argument flag="ImportDir"
                      help="Viking-layout source volume (the RC1_Original clone). Must not be Rabbit or a folder still named RC1."/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to adopt. If omitted all sections are adopted. Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-DryRun" action="store_true" dest="DryRun"
                      help="Print planned moves without moving files or writing filter/transform nodes."
                      required="False"/>
        </Arguments>

        <PythonCall Module="nornir_buildmanager.importers.vikingvolume"
                    Function="Import" ImportPath="#ImportDir" Sections="#Sections" DryRun="#DryRun"/>
    </Pipeline>

    <Pipeline Name="Prune"
              Help="Removes tiles without enough features for proper alignment." Description="Scores tiles for texture complexity and removes low-information tiles from a mosaic transform. Run before Mosaic to improve registration robustness." Epilog="Example: nornir-build Prune /data/volume -InputFilter Raw8 -DefaultThreshold 0.2 -Sections 1-10">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-DefaultThreshold" dest="DefaultPruneThreshold"
                      type="float"
                      help="Texture complexity threshold, this value is used if not user defined value is set and no previous prune operation has been run.  To change this value for an already built section, use SetPruneCutoff"
                      required="True"/>
            <Argument flag="-InputFilter" dest="InputFilter" default="Raw8"
                      help="Filter to calculate complexity upon" required="False"/>
            <Argument flag="-Downsample" dest="Downsample" type="int"
                      default="1" help="Tiles are downsampled by this factor before being scored"
                      required="False"/>
            <Argument flag="-InputTransform" dest="InputTransform"
                      default="Stage"
                      help="Mosaic transform to prune.  The transform provides the list of tiles in the mosaic."
                      required="False"/>
            <Argument flag="-OutputTransform" dest="OutputTransform"
                      default="Prune"
                      help="Name of output transform.  Contains tiles from input transform minus tiles below threshold."
                      required="False"/>
            <Argument flag="-overlap" dest="Overlap" default="0.1" type="float"
                      help="Percentage overlap between tiles, 0.0 to 1.0" required="False"/>

        </Arguments>

        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>
                <!--
                                <Select VariableName="Pyramid"
                                    XPath="Filter[@Name='#InputFilter']/TilePyramid" />
                                <PythonCall Function="tile.VerifyImages" tile_pyramid_node="#Pyramid" />
                -->

                <Select VariableName="TransformNode" XPath="Transform[@Name='#InputTransform']"/>
                <PythonCall Function="registration.CompressTransforms"
                            TransformNode="#TransformNode"/>

                <!-- Assign a number to each tile describing if there are interesting 
                    features to the image -->
                <Select VariableName="FilterNode" Root="ChannelNode"
                        XPath="Filter[@Name='#InputFilter']"/>
                <PythonCall Function="pruneobj.PruneObj.CalculatePruneScores"
                            Downsample="#Downsample" OutputFile="PruneScores">
                    <Parameters>
                        <Entry Name="Overlap" Value="#Overlap"/>
                    </Parameters>
                </PythonCall>

                <!-- Remove tiles that are featureless from the mosaic -->
                <Select VariableName="PruneNode" Root="FilterNode"
                        XPath="Prune[@Overlap='#Overlap']"/>
                <PythonCall Function="pruneobj.PruneObj.PruneMosaic"
                            OutputTransformName="#OutputTransform">
                    <Parameters>
                        <Entry Name="Threshold" Value="#DefaultPruneThreshold"/>
                    </Parameters>
                </PythonCall>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ListPruneCutoff" Help="List the prune thresholds set for a filter">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7].  Defaults to every section"
                      required="False"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be edited" required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="*"
                      help="Regular expression describing filters to be edited" required="False"/>
            <Argument flag="-header_fill" dest="header_fill" default="12" type="int"
                      help="The amount of padding to use for names in the list" required="False"/>
            <Argument flag="-fill" dest="value_fill" default="6" type="int"
                      help="The amount of padding to use for prune values in the list" required="False"/>
        </Arguments>

        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Iterate VariableName="FilterNode" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>

                    <Select VariableName="PruneNode" XPath="Prune"/>

                    <PythonCall Function="diagnostics.PrintNodeTreeAndAttributes"
                                node="#PruneNode" attributes="UserRequestedCutoff" header_fill="#header_fill"
                                value_fill="#value_fill"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="SetPruneCutoff"
              Help="Set the prune threshold for a filter.  The new value will not update the volume until the Prune operation is run.">
        <Arguments>
            <Argument flag="-Value" dest="PruneVal" type="float"
                      help="Prune threshold to set" required="True" default="0"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="True"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be edited" required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="*"
                      help="Regular expression describing filters to be edited" required="False"/>
        </Arguments>

        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Iterate VariableName="FilterNode" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>

                    <Select VariableName="PruneNode" XPath="Prune"/>

                    <PythonCall Function="setters.SetPruneThreshold"
                                PruneNode="#PruneNode" Value="#PruneVal"/>

                    <PythonCall Function="diagnostics.PrintNodeTreeAndAttributes"
                                node="#PruneNode" attributes="UserRequestedCutoff" header_fill="12" value_fill="12"/>
                </Iterate>

                <Iterate VariableName="FilterNode" XPath="Filter">
                    <!-- This should be more refined to only unlock filters built upon the 
                        filter whose contrast has been changed -->
                    <PythonCall Function="setters.SetFilterContrastLocked"
                                FilterNode="#FilterNode" Locked="0"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>


    <Pipeline Name="ShadeCorrect"
              Help="Brightfield microscopy shading correction.  Uses the max tile intensity across all tiles to determine the shading gradient.  Produces an inverted output filter with the shading subtracted.">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-Filters" default="(?![M|m]ask)" dest="FiltersRegEx"
                      type="str"
                      help="Regular expression describing filters to include, * processes all filters"
                      required="False"/>
            <Argument flag="-OutputFilter" dest="OutputFilter" default="ShadingCorrected"
                      help="Prefix added to output filters" required="False"/>
            <Argument flag="-Correction" dest="Correction" default="brightfield"
                      help="Brightfield images have a light background with darker features.  Darkfield images have a dark background with light features."
                      choices="brightfield,darkfield" required="True"/>
        </Arguments>
        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>
                <Iterate VariableName="InputFilterNode" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FiltersRegEx"/>

                    <Iterate VariableName="tile_pyramid_node" XPath="TilePyramid">
                        <PythonCall Function="tile.VerifyImages"/>
                    </Iterate>

                    <PythonCall Function="tile.CorrectTiles" filter_node="#InputFilterNode"
                                OutputFilterName="#OutputFilter" InvertSource="True"
                                CorrectionType="#Correction"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="InvertFilter" Help="Produce a new filter, inverted from the input">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-InputFilter" dest="FiltersRegEx" type="str"
                      help="The filter to invert" required="True"/>
            <Argument flag="-OutputFilter" dest="OutputFilter" default="Inverted"
                      help="Prefix added to output filters" required="True"/>
        </Arguments>
        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>
                <Iterate VariableName="InputFilterNode" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FiltersRegEx"/>

                    <Iterate VariableName="tile_pyramid_node" XPath="TilePyramid">
                        <PythonCall Function="tile.VerifyImages"/>
                    </Iterate>

                    <PythonCall Function="tile.InvertFilter"
                                InputFilterNode="#InputFilterNode" OutputFilterName="#OutputFilter"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <!-- <Pipeline Name="MigrateMultipleImageSets" Help="Migrate meta-data to 
        latest version"> <Iterate VariableName="FilterNode" XPath="Block/Section/Channel/Filter"> 
        <PythonCall Function="tile.MigrateMultipleImageSets"> </PythonCall> </Iterate> 
        </Pipeline> <Pipeline Name="MigrateToZeroOrigin" Help="Migrate mosaic transforms 
        to zero origins"> <Iterate VariableName="TransformNode" XPath="Block/Section/Channel/Transform"> 
        <RequireMatch Attribute="Name" RegEx="Grid"/> <PythonCall Function="registration.TransformNodeToZeroOrigin" 
        transform_node="#TransformNode"> </PythonCall> </Iterate> </Pipeline> <Pipeline 
        Name="FixTilePyramidNumbering" Help="Migrate mosaic transforms to zero origins"> 
        <Iterate VariableName="TilePyramidNode" XPath="Block/Section/Channel/Filter/TilePyramid"> 
        <PythonCall Function="migration.MoveTilesToStartAtZero" tile_pyramid_node="#TilePyramidNode"> 
        </PythonCall> </Iterate> </Pipeline> <Pipeline Name="FixTransformNumbering" 
        Help="Migrate mosaic transforms tile numbering to zero"> <Iterate VariableName="TransformNode" 
        XPath="Block/Section/Channel/Transform"> <PythonCall Function="migration.RenumberTransformTilesToStartAtZero" 
        transform_node="#TransformNode"> </PythonCall> </Iterate> </Pipeline> <Pipeline 
        Name="MigrateTo1.3" Help="Migrate mosaic transforms to zero origins"> <Iterate 
        VariableName="ChannelNode" XPath="Block/Section/Channel"> <PythonCall Function="migration.MigrateChannel_1p2_to_1p3" 
        channel_node="#ChannelNode"> </PythonCall> </Iterate> </Pipeline> <Pipeline 
        Name="ListOutdatedImagesets" Help="Print a list of all image sets that have 
        older timestamps than the input tile pyramid."> <Arguments> <Argument flag="-Sections" 
        dest="Sections" type="IntegerList" help="Section numbers to process. If omitted 
        all sections are processed. Values are separated with commas and ranges are 
        indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]" required="False"/> <Argument 
        flag="-Channels" default="*" dest="ChannelsRegEx" type="str" help="Regular 
        expression describing channels to include, * processes all channels" required="False"/> 
        <Argument flag="-Filters" default="(?![M|m]ask)" dest="FiltersRegEx" type="str" 
        help="Regular expression describing filters to include, * processes all filters" 
        required="False"/> </Arguments> <Iterate VariableName="section_node" XPath="Block/Section"> 
        <RequireSetMembership Attribute="Number" List="#Sections"/> <Iterate VariableName="ChannelNode" 
        XPath="Channel"> <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/> 
        <Iterate VariableName="FilterNode" XPath="Filter"> <RequireMatch Attribute="Name" 
        RegEx="#FiltersRegEx"/> <PythonCall Function="diagnostics.PrintImageSetsOlderThanTilePyramids" 
        node="#FilterNode"/> </Iterate> </Iterate> </Iterate> </Pipeline> -->

    <Pipeline Name="RenameFilter" Help="Renames a filter" Description="Renames a filter across channels/sections and updates linked filter paths.">
        <Arguments>
            <Argument flag="-InputFilter" dest="InputFilter" help="Existing filter name to rename (exact name match, not regex)"
                      required="True"/>
            <Argument flag="-OutputFilter" dest="NewName" help="New filter name to assign (exact value)"
                      required="True"/>
        </Arguments>
        <Iterate VariableName="FilterNode"
                 XPath="Block/Section/Channel/Filter[@Name='#InputFilter']">
            <PythonCall Function="general.Rename" OldNode="#FilterNode"
                        NewName="#NewName"/>
            <PythonCall Function="general.MovePath" Node="#FilterNode"
                        NewPath="#NewName"/>
        </Iterate>

        <Iterate VariableName="FilterNode"
                 XPath="Block/Section/Channel/Filter[@Name='#NewName']">
            <PythonCall Function="general.MovePath" Node="#FilterNode"
                        NewPath="#NewName"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="SetFilterMaskName"
              Help="Specify another filter in the same channel to use as a mask when using this filter.">
        <Arguments>
            <Argument flag="-InputFilter" dest="InputFilter" help="Existing filter name to rename (exact name match, not regex)"
                      required="True"/>
            <Argument flag="-MaskName" dest="NewName"
                      help="New Mask Name for filter name" required="True"/>
        </Arguments>
        <Iterate VariableName="FilterNode"
                 XPath="Block/Section/Channel/Filter[@Name='#InputFilter']">
            <PythonCall Function="setters.SetFilterMaskName"
                        FilterNode="#FilterNode" MaskName="#NewName"/>
        </Iterate>
    </Pipeline>

    <!-- <Pipeline Name="RemoveDuplicateFilters" Help="For debugging. Removes 
        filters from meta-data that have duplicate names."> <Arguments> <Argument 
        flag="-Attrib" dest="ChildAttrib" default="Name" help="Attrib to check for 
        duplicates" required="False"/> <Argument flag="-ChildNode" dest="ChildNodeName" 
        default="Filter_Link" help="Child node to check for duplicates" required="False"/> 
        </Arguments> <Iterate VariableName="ParentNode" XPath="Block/Section/Channel"> 
        <PythonCall Function="general.RemoveDuplicateLinks" ChildNodeName="#ChildNodeName" 
        ChildAttrib="#ChildAttrib"/> </Iterate> </Pipeline> -->

    <Pipeline Name="Histogram"
              Help="Calculates the histogram from all tiles in a mosaic">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-Filters" default="(?![M|m]ask)" dest="FiltersRegEx"
                      type="str"
                      help="Regular expression describing filters to include, * processes all filters"
                      required="False"/>
            <Argument flag="-InputTransform" dest="InputTransform"
                      default="Prune" help="The transform provided the list of tiles in the mosaic."
                      required="True"/>
            <Argument flag="-Downsample" dest="Downsample" type="int"
                      default="1" help="Use downsampled tiles for faster histogram calculation"
                      required="False"/>
        </Arguments>
        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>

                <Select VariableName="TransformNode" XPath="Transform[@Name='#InputTransform']"/>

                <Iterate VariableName="FilterNode" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FiltersRegEx"/>

                    <!-- Calculate the intensity histogram for the entire mosaic -->
                    <PythonCall Function="tile.HistogramFilter" filter_node="#FilterNode"
                                Downsample="#Downsample" transform_node="#TransformNode">
                    </PythonCall>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="AdjustContrast"
              Help="Creates a new filter with adjusted contrast.  The cutoffs can be adjusted within the meta-data by altering the AutoLevelHint element of the input filters volume.xml meta-data file.">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-Gamma" dest="Gamma"
                      help="Gamma value for intensity auto-level" required="False"/>
            <Argument flag="-MinCutoff" dest="MinCutoff" default="0.1"
                      help="Min pixel intensity cutoff as a percentage, 0 to 100"
                      required="False"/>
            <Argument flag="-MaxCutoff" dest="MaxCutoff" default="0.5"
                      help="Max pixel intensity cutoff as a percentage, 0 to 100. Specifying '1' puts the cutoff at 99%% of the maximum pixel intensity value."
                      required="False"/>
            <Argument flag="-InputFilter" dest="InputFilter" default="Raw8"
                      help="Filter to draw images from" required="True"/>
            <Argument flag="-OutputFilter" dest="OutputFilter" default="Leveled"
                      help="Filter to draw images from" required="False"/>
            <Argument flag="-OutputBpp" dest="OutputBpp" type="int"
                      help="Bits-per-pixel of output filter images" required="False"/>
            <Argument flag="-InputTransform" dest="InputTransform"
                      default="Prune"
                      help="Mosaic transform to prune.  The transform provides the list of tiles in the mosaic."
                      required="True"/>
        </Arguments>

        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">

                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>

                <Select VariableName="transform_node" XPath="Transform[@Name='#InputTransform']"/>

                <Select VariableName="FilterNode" XPath="Filter[@Name='#InputFilter']"/>
                <PythonCall Function="tile.AutolevelTiles" InputFilter="#FilterNode"
                            OutputFilterName="#OutputFilter" OutputBpp="#OutputBpp">
                    <Parameters>
                        <Entry Name="Gamma" Value="#Gamma"/>
                        <Entry Name="MinCutoff" Value="#MinCutoff"/>
                        <Entry Name="MaxCutoff" Value="#MaxCutoff"/>
                    </Parameters>
                </PythonCall>

                <Select VariableName="PyramidNode" Root="ChannelNode"
                        XPath="Filter[@Name='#OutputFilter']/TilePyramid"/>
                <PythonCall Function="tile.BuildTilePyramids"/>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ListFilterContrast"
              Help="Print a list of the contrast settings used for all sections.">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-Filters" default="(?![M|m]ask)" dest="FiltersRegEx"
                      type="str"
                      help="Regular expression describing filters to include, * processes all filters"
                      required="False"/>
        </Arguments>
        <PythonCall Function="diagnostics.PrintContrastValuesHeader"/>
        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>
                <Iterate VariableName="FilterNode" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FiltersRegEx"/>
                    <PythonCall Function="diagnostics.PrintContrastValues"
                                node="#FilterNode"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ListMissingTilesets"
              Help="Print a list of filters without tilesets">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-Filters" default="*" dest="FiltersRegEx"
                      type="str"
                      help="Regular expression describing filters to include, * processes all filters"
                      required="False"/>
        </Arguments>
        <PythonCall Function="diagnostics.Print" val="Section Channel Filter"/>
        <Iterate VariableName="section_node" XPath="Block/Section" Verify="False">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel" Verify="False">
                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>
                <Iterate VariableName="FilterNode" XPath="Filter" Verify="False">
                    <RequireMatch Attribute="Name" RegEx="#FiltersRegEx"/>
                    <PythonCall Function="diagnostics.PrintIfMissingTileset"
                                filter_node="#FilterNode"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>


    <Pipeline Name="SetContrast" Help="Set the contrast parameters for a filter">
        <Arguments>
            <Argument flag="-Min" dest="MinValue" type="float"
                      help="Min intensity value, set to 0 in output" required="False"
                      default="NaN"/>
            <Argument flag="-Max" dest="MaxValue" type="float"
                      help="Max intensity value, set to 1 in output" required="False"
                      default="NaN"/>
            <Argument flag="-Gamma" dest="GammaValue" type="float"
                      help="Gamma adjustment" required="False" default="NaN"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="True"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be edited" required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="*"
                      help="Regular expression describing filters to be edited" required="False"/>
        </Arguments>
        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>
            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>
                <Iterate VariableName="FilterNode" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>
                    <Select VariableName="HistogramNode" XPath="Histogram"/>
                    <PythonCall Function="setters.SetContrastRange"
                                HistogramElement="#HistogramNode" MinValue="#MinValue" MaxValue="#MaxValue"
                                GammaValue="#GammaValue"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="PrintContrast" Help="Print the contrast parameters for a filter">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="True"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be edited" required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="*"
                      help="Regular expression describing filters to be edited" required="False"/>
        </Arguments>
        <Iterate VariableName="SectionNode" XPath="Block/Section" Verify="False">
            <RequireSetMembership Attribute="Number" List="#Sections"/>
            <Iterate VariableName="ChannelNode" XPath="Channel" Verify="False">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>
                <Iterate VariableName="FilterNode" XPath="Filter" Verify="False">
                    <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>
                    <PythonCall Function="setters.PrintContrast"
                                FilterElement="#FilterNode"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>


    <Pipeline Name="SetFilterLock"
              Help="Toggle the locked flag for a filter.  Locked filters will not have images recalculated.">
        <Arguments>
            <Argument flag="-Locked" dest="Locked" type="int"
                      help="Lock value, 0 = unlocked, 1 = locked" required="True"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="True"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be edited" required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="*"
                      help="Regular expression describing filters to be edited" required="False"/>
        </Arguments>

        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Iterate VariableName="FilterNode" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>

                    <PythonCall Function="setters.SetLocked" Node="#FilterNode"
                                Locked="#Locked"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="SetTransformLock"
              Help="Toggle the locked flag for a filter.  Locked filters will not have images recalculated.">
        <Arguments>
            <Argument flag="-Locked" dest="Locked" type="int"
                      help="Lock value, 0 = unlocked, 1 = locked" required="True"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="True"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be edited" required="False"/>
            <Argument flag="-Transform" dest="TransformRegEx" default="*"
                      help="Name of the transform to lock/unlock" required="False"/>
        </Arguments>

        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Iterate VariableName="TransformNode" XPath="Transform" Validate="False">
                    <RequireMatch Attribute="Name" RegEx="#TransformRegEx"/>

                    <PythonCall Function="setters.SetLocked" Node="#TransformNode"
                                Locked="#Locked"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ForceUpdateInputTransform"
              Help="Tells a transform to find its input transform by name, and update its input transform checksum to the current input transform's checksum.  Used is very rare cases where a change adjusts the checksum of an input transform but the content is the same and does not require rebuilding an entire chain of transforms.">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="True"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be edited" required="False"/>
            <Argument flag="-Transform" dest="TransformRegEx" default="*"
                      help="Name of the transform to lock/unlock" required="False"/>
        </Arguments>

        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Iterate VariableName="TransformNode" XPath="Transform" Validate="False">
                    <RequireMatch Attribute="Name" RegEx="#TransformRegEx"/>

                    <PythonCall Function="setters.ForceUpdateInputTransformNode" Node="#TransformNode"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="RemoveTransform"
              Help="Remove a transform from the volume entirely">
        <Arguments>
            <Argument flag="-IgnoreLock" action="store_true" dest="IgnoreLock"
                      help="Ignore Filter and Transform Locks, use with care." required="True"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="True"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be edited" required="False"/>
            <Argument flag="-Transform" dest="TransformRegEx" default="*"
                      help="Name of the transform to lock/unlock" required="True"/>
        </Arguments>

        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Iterate VariableName="TransformNode" XPath="Transform">
                    <RequireMatch Attribute="Name" RegEx="#TransformRegEx"/>

                    <PythonCall Function="general.RemoveChannelChildNode" Node="#TransformNode"
                                IgnoreLock="#IgnoreLock"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>


    <Pipeline Name="Mosaic"
              Help="Aligns tiles of a filter into a single mosaic.  Produces an assembled mosaic image." Description="Runs grid registration/refinement to produce an output transform and assembled mosaic for each selected section/channel." Epilog="Example: nornir-build Mosaic /data/volume -InputFilter Raw8 -InputTransform Prune -OutputTransform Grid -RegistrationDownsample 4">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-RegistrationDownsample" dest="Downsample"
                      default="4" type="int" help="Downsample level to use for ir-tools"
                      required="False"/>
            <Argument flag="-InputFilter" dest="InputFilter" default="Raw8"
                      help="Filter to use for registration" required="True"/>
            <Argument flag="-InputTransform" dest="InputTransformName"
                      default="Prune" help="Name of the input transform" required="False"/>
            <Argument flag="-OutputTransform" dest="OutputTransformName"
                      default="Grid" help="Name of output transform." required="False"/>
            <Argument flag="-RefineIterations" default="10" dest="RefineIterations"
                      type="int" help="Maximum number of refinement passes to run"
                      required="False"/>
            <Argument flag="-Threshold" default="0.25" dest="DisplacementThreshold"
                      type="float"
                      help="Displacement threshold for control point movement (in pixels).  Refinement stops below this value"
                      required="False"/>
            <Argument flag="-MinOverlap" default="0.05" dest="min_overlap"
                      type="float"
                      help="Minimum amount of overlap tiles must have to contribute to the final mosaic."
                      required="False"/>
            <Argument flag="-ExpandOverlapScalar" default="1.0" dest="excess_scalar"
                      type="float"
                      help="How much additional area should we pad the overlapping regions with."
                      required="False"/>
            <Argument flag="-first_pass_inter_tile_distance" default=".75" dest="first_pass_inter_tile_distance"
                      type="float"
                      help="A value from 0 to 1 that scales the distance between the centers of the two tiles.  A value of 0 considers the full images when registering.  A value of 1.0 only considers the overlapping regions according to the tile transforms.  Reduce this value in the first registration pass to increase the search area."
                      required="False"/>
            <Argument flag="-inter_tile_distance" default="1" dest="inter_tile_distance" type="float"
                      help="A value from 0 to 1 that scales the distance between the centers of the two tiles.  A value of 0 considers the full images when registering.  A value of 1.0 only considers the overlapping regions according to the tile transforms.  Reduce this value in early registration passes to increase the search area."
                      required="False"/>
            <Argument flag="-MinFeatureScore" dest="feature_score_threshold"
                      type="float"
                      help="Minimum average FFT power spectral density.  Determines if overlapping have enough texture for registration to be accurate."
                      required="False"/>
            <Argument flag="-MinTranslateIterations" default="1" dest="min_translate_iterations"
                      type="int" help="Minimum number of iterations phase correlation and relaxation"
                      required="False"/>
            <Argument flag="-MaxTranslateIterations" default="5" dest="max_translate_iterations"
                      type="int" help="Maximum number of iterations phase correlation and relaxation"
                      required="False"/>
            <Argument flag="-RelaxIterations" default="500" dest="max_relax_iterations"
                      type="int" help="Maximum number of relaxation passes to in translation only placement phase"
                      required="False"/>
            <Argument flag="-OffsetChangeTolerance" default="1" dest="offset_acceptance_threshold"
                      type="float"
                      help="Do not re-register (recalculate phase correlation of) a pair of overlapping tiles if the change in overlap position compared to the previous iteration is less than this distance in pixels."
                      required="False"/>
            <Argument flag="-PhaseCorrelationExponent" default=".65" dest="offset_acceptance_threshold"
                      type="float"
                      help="Setting this value to 1 is equivalent to using phase correlation.  Setting it to 0 is equivalent to using Pierson Correlation.  The default is .65.  If you have a difficult to register section changing this value to 1 may help."
                      required="False"/>
            <Argument flag="-RelaxThreshold" default="0.5" dest="max_relax_tension_cutoff"
                      type="float"
                      help="Stop iteration of relaxation phase if the maximum weighted tension vector on all tiles is below this value (in pixels)."
                      required="False"/>
            <Argument flag="-CellArea" default="128" dest="CellArea"
                      type="int" help="Size of region around each grid point to register, in pixels"
                      required="False"/>
            <Argument flag="-MeshSize" default="8" dest="MeshSize"
                      type="int"
                      help="How many registration points are spaced along the images, the same value is used for X and Y.  This should be a large enough number to provide 25%% or ideally 50%% overlap of grid cells for the expected image size."
                      required="False"/>
            <Argument flag="-IncludeDiagonalOverlaps" dest="exclude_diagonal_overlaps" action="store_false"
                      default="True"
                      help="Setting this flag includes tiles that only have a diagonal overlap, that is the center of the tile is outside the X/Y boundaries of the paired tile.  This can introduce extra calculation time and possibly noise but be helpful if the tiles moved considerably during capture or are not laid out on a grid."
                      required="False"/>
            <Argument flag="-ScaleWeightByFeatureScore" dest="scale_weight_by_feature_score" action="store_true"
                      default="False"
                      help="Setting this flag multiplies the alignment weights by the feature score of the overlapping region in the translate phase.  This can sometimes reduce the impact of large featureless areas but seems to introduce noise in many other cases."
                      required="False"/>
        </Arguments>

        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">

                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>

                <Select VariableName="TransformNode" XPath="Transform[@Name='#InputTransformName']"/>
                <Select VariableName="FilterNode" XPath="Filter[@Name='#InputFilter']"/>

                <!-- ir-translate, calculate translation for each tile into the greater 
                    mosaic -->
                <PythonCall Function="registration.TranslateTransform"
                            TransformNode="#TransformNode" OutputTransform="Translated_#InputTransformName"
                            FilterNode="#FilterNode" RegistrationDownsample="#Downsample"
                            excess_scalar="#excess_scalar"
                            min_overlap="#min_overlap" feature_score_threshold="#feature_score_threshold"
                            min_translate_iterations="#min_translate_iterations"
                            max_translate_iterations="#max_translate_iterations"
                            offset_acceptance_threshold="#offset_acceptance_threshold"
                            max_relax_iterations="#max_relax_iterations"
                            max_relax_tension_cutoff="#max_relax_tension_cutoff"
                            inter_tile_distance="#inter_tile_distance"
                            exclude_diagonal_overlaps="#exclude_diagonal_overlaps"
                            scale_weight_by_feature_score="#scale_weight_by_feature_score"
                >
                    <Parameters>
                        <Entry Name="MinOverlap" Value="#min_overlap"/>
                        <Entry Name="FeatureScore" Value="#feature_score_threshold"/>
                        <Entry Name="it" Value="#min_translate_iterations"/>
                        <Entry Name="excess" Value="#excess_scalar"/>
                        <Entry Name="accept_threshold" Value="#offset_acceptance_threshold"/>
                        <Entry Name="relax" Value="#max_relax_iterations"/>
                        <Entry Name="TensionCutoff" Value="#max_relax_tension_cutoff"/>
                        <Entry Name="itd" Value="#inter_tile_distance"/>
                    </Parameters>
                </PythonCall>

                <PythonCall Function="diagnostics.PlotMosaicOverlaps"
                            ChannelNode="#ChannelNode"
                            Transform="Translated_#InputTransformName"
                            OutputFilename="Layout_Translated_#InputTransformName"
                            Filter="#FilterNode"
                            Downsample="#Downsample"
                            exclude_diagonal_overlaps="#exclude_diagonal_overlaps"
                            ShowFeatureScores="#scale_weight_by_feature_score"
                >
                </PythonCall>


                <!-- ir-refine-grid, lay a mesh over each tile and align points to overlapping 
                    tiles -->
                <Select VariableName="TranslatedTransformNode" Root="ChannelNode"
                        XPath="Transform[@Name='Translated_#InputTransformName']"/>
                <PythonCall Function="registration.CompressTransforms"
                            TransformNode="#TranslatedTransformNode"/>
                <PythonCall Function="registration.GridTransform"
                            OutputTransform="#OutputTransformName" TransformNode="#TranslatedTransformNode"
                            FilterNode="#FilterNode" RegistrationDownsample="#Downsample">
                    <Parameters>
                        <Entry Name="MeshWidth" Value="#MeshSize"/>
                        <Entry Name="MeshHeight" Value="#MeshSize"/>
                        <Entry Name="Cell" Value="#CellArea"/>
                        <Entry Name="Threshold" Value="#DisplacementThreshold"/>
                        <Entry Name="it" Value="#RefineIterations"/>
                    </Parameters>
                </PythonCall>

                <Select VariableName="RefinedTransformNode" Root="ChannelNode"
                        XPath="Transform[@Name='#OutputTransformName']"/>
                <PythonCall Function="registration.CompressTransforms"
                            TransformNode="#RefinedTransformNode"/>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="MosaicNoTranslate"
              Help="Aligns tiles of a filter into a single mosaic.  Produces an assembled mosaic image.  Assumes stage coordinates are correct and refines distortions only without translating tiles.">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-RegistrationDownsample" dest="Downsample"
                      default="4" type="int" help="Downsample level to use for ir-tools"
                      required="False"/>
            <Argument flag="-RefineIterations" default="10" dest="RefineIterations"
                      type="int" help="Maximum number of refinement passes to run"
                      required="False"/>
            <Argument flag="-CellArea" default="128" dest="CellArea"
                      type="int" help="Size of region around each grid point to register, in pixels"
                      required="False"/>
            <Argument flag="-Threshold" default="0.1" dest="DisplacementThreshold"
                      type="float"
                      help="Displacement threshold for control point movement (in pixels).  Refinement stops below this value"
                      required="False"/>
            <Argument flag="-MaxOffset" dest="MaxOffset" default="0.5"
                      type="float"
                      help="Comma separated list of downsample levels used for assembling output images"
                      required="False"/>
            <Argument flag="-InputFilter" dest="InputFilter" default="Raw8"
                      help="Filter to use for registration" required="True"/>
            <Argument flag="-InputTransform" dest="InputTransformName"
                      default="Prune" help="Name of the input transform" required="False"/>
            <Argument flag="-OutputTransform" dest="OutputTransformName"
                      default="Grid" help="Name of output transform." required="False"/>
        </Arguments>

        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">

                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>

                <Select VariableName="TransformNode" XPath="Transform[@Name='#InputTransformName']"/>
                <Select VariableName="FilterNode" XPath="Filter[@Name='#InputFilter']"/>

                <PythonCall Function="registration.GridTransform"
                            OutputTransform="#OutputTransformName" TransformNode="#TransformNode"
                            FilterNode="#FilterNode" RegistrationDownsample="#Downsample">
                    <Parameters>
                        <Entry Name="MeshWidth" Value="8"/>
                        <Entry Name="MeshHeight" Value="8"/>
                        <Entry Name="Cell" Value="#CellArea"/>
                        <Entry Name="Threshold" Value="#DisplacementThreshold"/>
                        <Entry Name="it" Value="#RefineIterations"/>
                    </Parameters>
                </PythonCall>

                <Select VariableName="RefinedTransformNode" Root="ChannelNode"
                        XPath="Transform[@Name='#OutputTransformName']"/>
                <PythonCall Function="registration.CompressTransforms"
                            TransformNode="#RefinedTransformNode"/>
            </Iterate>
        </Iterate>
    </Pipeline>


    <Pipeline Name="SetMosaicLock"
              Help="Toggle the locked flag for a .mosaic transform to prevent or enable regeneration.">
        <Arguments>
            <Argument flag="-Locked" dest="Locked" type="int"
                      help="Lock value, 0 = unlocked, 1 = locked" required="True"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="True"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be edited" required="False"/>
            <Argument flag="-Transforms" dest="TransformRegEx"
                      help="Regular expression describing transform names to be edited"
                      required="True"/>
        </Arguments>

        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Iterate VariableName="TransformNode" XPath="Transform">
                    <RequireMatch Attribute="Name" RegEx="#TransformRegEx"/>

                    <PythonCall Function="setters.SetLocked" Node="#TransformNode"
                                Locked="#Locked"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>


    <Pipeline Name="GenerateMosaicLayoutSVG"
              Help="Generates SVG files showing the layout of a given transform">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str"
                      help="Regular expression describing channels to include, * processes all channels"
                      required="False"/>
            <Argument flag="-Downsample" dest="Downsample"
                      type="int" help="Downsample level to use for ir-tools"
                      required="False"/>
            <Argument flag="-Filter" dest="InputFilterName" required="False"
                      help="Filter to use for layouts"/>
            <Argument flag="-Transform" dest="InputTransformName"
                      default="Prune" help="Name of the input transform" required="True"/>
            <Argument flag="-OutputFilename" dest="OutputFilename"
                      default="Layout" help="Prefix of output image." required="False"/>
            <Argument flag="-MinOverlap" default="0.05" dest="min_overlap"
                      type="float"
                      help="Minimum amount of overlap tiles must have to contribute to the final mosaic."
                      required="False"/>
            <Argument flag="-IncludeDiagonalOverlaps" dest="exclude_diagonal_overlaps" action="store_false"
                      default="True"
                      help="Setting this flag includes tiles that only have a diagonal overlap, that is the center of the tile is outside the X/Y boundaries of the paired tile.  This can introduce extra calculation time and possibly noise but be helpful if the tiles moved considerably during capture or are not laid out on a grid."
                      required="False"/>
            <Argument flag="-ShowFeatureScores" dest="ShowFeatureScores" action="store_true"
                      default="False"
                      help="Setting this flag multiplies the alignment weights by the feature score of the overlapping region in the translate phase.  This can sometimes reduce the impact of large featureless areas but seems to introduce noise in many other cases."
                      required="False"/>
        </Arguments>

        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">

                <RequireMatch Attribute="Name" RegEx="#ChannelsRegEx"/>

                <Select VariableName="TransformNode" XPath="Transform[@Name='#InputTransformName']"/>

                <!-- ir-translate, calculate translation for each tile into the greater 
                    mosaic -->
                <PythonCall Function="diagnostics.PlotMosaicOverlaps"
                            ChannelNode="#ChannelNode"
                            Transform="#TransformNode" OutputFilename="#OutputFilename"
                            Filter="#InputFilterName"
                            Downsample="#Downsample"
                            exclude_diagonal_overlaps="#exclude_diagonal_overlaps"
                            ShowFeatureScores="#ShowFeatureScores"
                >
                </PythonCall>
            </Iterate>
        </Iterate>
    </Pipeline>


    <Pipeline Name="Assemble"
              Help="Creates an assembled image from a transform and filter. Optionally creates a new channel for output.">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Downsample" dest="Levels" default="1"
                      help="Comma separated list of downsample levels for assembling images.  If output is desired only one downsample level can be used."
                      required="False"/>
            <Argument flag="-NoInterlace" dest="Interlace" action="store_false"
                      default="True"
                      help="Setting this flag skips the interlace step after assemble.  Interlaced images load faster on web pages, but are larger than non-interlaced images."
                      required="False"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be assembled"
                      required="False"/>
            <Argument flag="-ChannelPrefix" dest="OutputChannelPrefix"
                      default="" type="str"
                      help="Create a new channel for the output with this prefix and the name of the input."
                      required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="(?![M|m]ask)"
                      help="Regular expression describing filters to be assembled"
                      required="False"/>
            <Argument flag="-Transform" dest="TransformName" default="Grid"
                      help="Directory to move assembled images to" required="False"/>
            <Argument flag="-Crop" dest="CropBox" type="Quadruple"
                      default=""
                      help="Bounding box of region to assemble.  Default is no cropping.  Form is MinX,MinY,MaxX,MaxY with no spaces."
                      required="False"/>
            <Argument flag="-ext" dest="image_ext" default="png"
                      help="Format to save, default is png format. Use npy for Numpy arrays."/>
        </Arguments>
        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">

                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <!-- Build a downsampled image of the mosaic -->
                <Select VariableName="transform_node" XPath="Transform[@Name='#TransformName']"/>

                <Iterate VariableName="filter_node" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>

                    <PythonCall Function="tile.AssembleTransform"
                                OutputChannelPrefix="#OutputChannelPrefix" Interlace="#Interlace"
                                Levels="#Levels" CropBox="#CropBox" image_ext="#image_ext">
                        <Parameters>
                            <Entry Name="feathering" Value="binary"/>
                        </Parameters>
                    </PythonCall>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="BuildImagePyramids"
              Help="Rebuild derived ImageSet pyramid levels when a parent image checksum is stale. Does not assemble from tiles. Use after Assemble or to repair coarser levels left behind.">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to process"
                      required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="*"
                      help="Regular expression describing filters to process.  Default includes Mask filters."
                      required="False"/>
            <Argument flag="-Downsample" dest="Levels" default=""
                      help="Comma separated downsample levels to consider together with existing ImageSet levels.  If omitted, all existing levels are used."
                      required="False"/>
            <Argument flag="-NoInterlace" dest="Interlace" action="store_false"
                      default="True"
                      help="Setting this flag skips the interlace step after shrink.  Interlaced images load faster on web pages, but are larger than non-interlaced images."
                      required="False"/>
        </Arguments>
        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">

                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Iterate VariableName="filter_node" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>

                    <Select VariableName="image_set_node" Root="filter_node" XPath="ImageSet"/>
                    <PythonCall Function="tile.BuildImagePyramid"
                                Levels="#Levels" Interlace="#Interlace"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="AssembleTiles"
              Help="Build small full-resolution tiles, usually 256x256, that allow regions of the mosaic to be rapidly loaded">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be assembled"
                      required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="(?![M|m]ask)"
                      help="Regular expression describing filters to be assembled"
                      required="False"/>
            <Argument flag="-Transform" dest="TransformName" default="Grid"
                      help="Transform defining images into mosaic" required="False"/>
            <Argument flag="-HighestDownsample" dest="HighestDownsample"
                      type="int"
                      help="Highest level downsample to generate.  Defaults to the level required to show entire section on one tile."
                      required="False"/>
            <Argument flag="-Shape" dest="shape" default="512,512" type="IntegerPair"
                      help="The size the tiles passed as a comma-delimited pair of integers.  For example 256,512.  If a single number is passed it is used for both dimensions"
                      required="False"/>
            <Argument flag="-MaxWorkingImageArea" dest="max_temp_image_area" type="float"
                      help="Determines the amount of memory the system should use to generate the tiles.  Suggested value is total system memory in bytes divided by four"
                      required="False"/>
            <Argument flag="-IgnoreStale" dest="ignore_stale" action="store_true"
                      default="False"
                      help="Setting this flag skips testing existing tiles to ensure they were created after the input transform."
                      required="False"/>
        </Arguments>

        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="channel_node" XPath="Channel">

                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Select VariableName="transform_node" XPath="Transform[@Name='#TransformName']"/>
                <Iterate VariableName="filter_node" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>

                    <Select VariableName="pyramid_node" Root="filter_node" XPath="TilePyramid"/>
                    <PythonCall Function="tile.AssembleTilesetNumpy" TileShape="#shape"
                                max_temp_image_area="#max_temp_image_area" ignore_stale="#ignore_stale"/>

                    <Select VariableName="tile_set_node" Root="filter_node" XPath="Tileset"/>
                    <PythonCall Function="tile.BuildTilesetPyramid"
                                HighestDownsample="#HighestDownsample"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="AssembleTilesFromImage"
              Help="Build small full-resolution tiles, usually 256x256, that allow regions of the mosaic to be rapidly loaded.  Uses the filter image as source instead of transforms.">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be assembled"
                      required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="(?![M|m]ask)"
                      help="Regular expression describing filters to be assembled"
                      required="False"/>
            <Argument flag="-HighestDownsample" dest="HighestDownsample"
                      type="int"
                      help="Highest level downsample to generate.  Defaults to the level required to show entire section on one tile."
                      required="False"/>
            <Argument flag="-Shape" dest="shape" default="512,512" type="IntegerPair"
                      help="The size the tiles passed as a comma-delimited pair of integers.  For example 256,512.  If a single number is passed it is used for both dimensions"
                      required="False"/>

        </Arguments>

        <Iterate VariableName="section_node" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="chennel_node" XPath="Channel">

                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Iterate VariableName="filter_node" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>

                    <Select VariableName="image_set_node" Root="filter_node" XPath="ImageSet"/>
                    <PythonCall Function="filter.AssembleTilesetFromImageSet"
                                TileShape="#shape"/>

                    <Select VariableName="tile_set_node" Root="filter_node" XPath="Tileset"/>
                    <PythonCall Function="tile.BuildTilesetPyramid"
                                HighestDownsample="#HighestDownsample"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="MosaicReport"
              Help="Generate a web page summarizing the output of 2D section mosaics.  Includes prune scores, contrast histograms, and assembled images if any.">
        <Arguments>
            <Argument flag="-PruneFilter" dest="PruneFilter" default="Raw8"
                      help="Input filter passed to Prune pipeline" required="False"/>
            <Argument flag="-ContrastFilter" dest="ContrastFilter"
                      default="Raw8" help="Input filter passed to Adjust Contrast pipeline"
                      required="False"/>
            <Argument flag="-AssembleFilter" dest="AssembleFilter"
                      default="Raw8" help="Input filter passed to Assemble or Mosaic pipeline"
                      required="False"/>
            <Argument flag="-AssembleDownsample" dest="AssembleDownsample"
                      type="int" default="1"
                      help="Downsample level to build assembled image thumbnails from"
                      required="False"/>
            <Argument flag="-RowsPerPage" dest="RowsPerPage"
                      type="int" default="50"
                      help="How many rows to place on a page before dividing into multiple pages"
                      required="False"/>
            <Argument flag="-Output" dest="OutputPath" type="str"
                      default="ImageReport.html" help="Name of report html file" required="False"/>
            <Argument flag="-BuilderEndpoint" dest="BuilderEndpoint" type="str"
                      help="Endpoint for builder" required="False"/>

        </Arguments>
        <Select VariableName="ReportingElement" XPath="Block"/>
        <PythonCall Function="reporting.GenerateTableReport"
                    OutputFile="#OutputPath" RowXPath="Section" RowLabelAttrib="Number"
                    MaxImageWidth="512" MaxImageHeight="512" RowsPerPage="#RowsPerPage"
                    BuilderEndpoint="#BuilderEndpoint"
                    ColumnXPaths="Channel/Filter[@Name='#AssembleFilter'],Channel/TransformData,Channel/Notes,Channel/Data,Channel/Filter[@Name='#ContrastFilter']/Histogram/Image,Channel/Filter[@Name='#PruneFilter']/Prune/Image"/>
        <PythonCall Module="nornir_buildmanager.VolumeFinder"
                    Function="EmailIndex" subject="Volume Report" to="#To" cc="#CC"/>
    </Pipeline>

    <Pipeline Name="StosReport"
              Help="Generate a web page containing a summary of the alignments between sections in a 3D volume">
        <Arguments>
            <Argument flag="-StosGroup" dest="StosGroup"
                      help="Stos group to pull images from" required="True"/>
            <Argument flag="-Output" dest="OutputPath" type="str"
                      default="StosReport.html" help="Name of report html file" required="False"/>
        </Arguments>
        <Select VariableName="ReportingElement" XPath="Block/StosGroup[@Name='#StosGroup']"/>
        <PythonCall Function="reporting.GenerateTableReport"
                    OutputFile="#OutputPath" RowXPath="SectionMappings"
                    RowLabelAttrib="MappedSectionNumber" MaxImageWidth="512"
                    MaxImageHeight="512" ColumnXPaths="Image,Transform,Histogram/Image"/>

        <PythonCall Module="nornir_buildmanager.VolumeFinder"
                    Function="EmailIndex" subject="Volume Report" to="#To" cc="#CC"/>
    </Pipeline>

    <Pipeline Name="ReportVolumeBounds"
              Help="Generate a web page summarizing the output of 2D section mosaics.  Includes prune scores, contrast histograms, and assembled images if any.">
        <Arguments>
            <Argument flag="-Channels" dest="ChannelRegEx"
                      help="Regular expression describing channels to be assembled"
                      required="True"/>
            <Argument flag="-Transforms" dest="TransformsRegEx"
                      help="Transform defining images into mosaic" required="True"/>
            <Argument flag="-StosMap" dest="StosMap"
                      help="Slice-to-slice mapping of transforms.  If not specified only unregistered mosaics are included."
                      required="False"/>
        </Arguments>
        <Select VariableName="BlockNode" XPath="Block"/>
        <Iterate VariableName="VolumeStosMap" XPath="Block/StosMap[@Name='#StosMap']">

            <PythonCall Function="block.StosBrute" Root="BlockNode"
                        OutputGroup="StosBrute#StosBruteDownsample" ChannelsRegEx="#ChannelsRegEx"
                        FiltersRegEx="#FiltersRegEx" AngleSearchRange="#AngleSearchRange">
                <Parameters>
                    <Entry Name="Downsample" Value="#StosBruteDownsample"/>
                    <Entry Name="refine"/>
                    <Entry Name="regularize"/>
                    <Entry Name="UseMasks" Value="#UseMasks"/>
                </Parameters>
            </PythonCall>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ListDamagedSections"
              Help="Print a list of all damaged sections.  Damaged sections are never used as control sections when chaining slice-to-slice transforms together to build slice-to-volume transforms">
        <Arguments>
            <Argument flag="-Block" default="*" dest="BlockRegEx" type="str"
                      help="Regular expression describing blocks to include, * processes all blocks"
                      required="False"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockRegEx"/>
            <PythonCall Function="setters.PrintSectionsDamaged"
                        block_node="#BlockNode"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="MarkSectionsDamaged"
              Help="Mark sections as damaged.  Damaged sections are never used as control sections when chaining slice-to-slice transforms together to build slice-to-volume transforms">
        <Arguments>
            <Argument flag="-Block" default="*" dest="BlockRegEx" type="str"
                      help="Regular expression describing blocks to include, * processes all blocks"
                      required="False"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers that should not be used as control sections.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockRegEx"/>
            <PythonCall Function="setters.MarkSectionsAsDamaged"
                        block_node="#BlockNode" SectionNumbers="#Sections"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="MarkSectionsUndamaged"
              Help="Remove sections from the damaged list.  Damaged sections are never used as control sections when chaining slice-to-slice transforms together to build slice-to-volume transforms">
        <Arguments>
            <Argument flag="-Block" default="*" dest="BlockRegEx" type="str"
                      help="Regular expression describing blocks to include, * processes all blocks"
                      required="False"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to remove from the damaged list.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockRegEx"/>
            <PythonCall Function="setters.MarkSectionsAsUndamaged"
                        block_node="#BlockNode" SectionNumbers="#Sections"/>
        </Iterate>
    </Pipeline>

    -->

    <Pipeline Name="CreateVikingXML" Help="Create a vikingxml file for use in Viking" Description="Exports volume metadata and image references into a VikingXML manifest for Viking clients." Epilog="Example: nornir-build CreateVikingXML /data/volume -OutputFile MyVolume -StosMap FinalStosMap -StosGroup Grid32 -StosGroup SliceToVolume1">
        <Arguments>
            <Argument flag="-StosGroup" dest="StosGroup" action="append"
                      help="Stos group to include (repeat for multiple groups). If not specified only unregistered mosaics are included."
                      required="False"/>
            <Argument flag="-StosMap" dest="StosMap"
                      help="Slice-to-slice mapping of transforms.  If not specified only unregistered mosaics are included."
                      required="False"/>
            <Argument flag="-Host" dest="Host"
                      help="Url of volume, overrides the about.xml settings" required="False"/>
            <Argument flag="-OutputFile" dest="OutputFile"
                      help="Name of file to write .VikingXML is appended to name automatically"
                      required="True"/>
        </Arguments>

        <PythonCall Function="vikingxml.CreateVikingXML"
                    StosMapName="#StosMap" StosGroupName="#StosGroup" Host="#Host"
                    OutputFile="#OutputFile"/>
    </Pipeline>

    <Pipeline Name="CreateStosGroup" Help="Create a SliceToSlice transform group">
        <Arguments>
            <Argument flag="-StosGroup" dest="StosGroup" type="str"
                      help="Name of the StosGroup to create" required="True"/>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Block to create the StosGroup within" required="False"/>
            <Argument flag="-Downsample" dest="Downsample" type="int"
                      default="1" help="Downsample level of stos files" required="False"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosgroup.CreateStosGroup"
                        BlockNode="#BlockNode" GroupName="#StosGroup#Downsample" Downsample="#Downsample"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="RemoveStosGroup" Help="Remove a SliceToSlice transform group">
        <Arguments>
            <Argument flag="-StosGroup" dest="StosGroup" type="str"
                      help="Name of the StosGroup to remove" required="True"/>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Block to create the StosGroup within" required="False"/>
            <Argument flag="-Downsample" dest="Downsample" type="int"
                      default="1" help="Downsample level of stos files" required="False"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosgroup.RemoveStosGroup"
                        BlockNode="#BlockNode" GroupName="#StosGroup#Downsample" Downsample="#Downsample"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="CopyStosGroup" Help="Create a SliceToSlice transform group">
        <Arguments>
            <Argument flag="-Input" dest="SourceGroupName" type="str"
                      help="Source StosGroup to copy transforms from." required="True"/>
            <Argument flag="-Output" dest="TargetGroupName" type="str"
                      help="Target StosGroup to copy transform to." required="True"/>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Block to create the StosGroup within" required="False"/>
            <Argument flag="-Downsample" dest="Downsample" type="int"
                      default="1" help="Downsample level of stos files" required="False"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosgroup.CopyStosGroup" BlockNode="#BlockNode"
                        SourceGroupName="#SourceGroupName#Downsample" TargetGroupName="#TargetGroupName#Downsample"
                        Downsample="#Downsample"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ListStosGroups" Help="List all SliceToSlice transform groups">
        <Arguments>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Block to create the StosGroup within" required="False"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosgroup.ListStosGroups" BlockNode="#BlockNode"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ListGroupSectionMappings" Help="List all SliceToSlice transform groups">
        <Arguments>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Block to create the StosGroup within" required="False"/>
            <Argument flag="-StosGroup" dest="StosGroup" type="str"
                      help="Name of the StosGroup" required="True"/>
            <Argument flag="-Downsample" dest="Downsample" type="int"
                      help="Downsample level of stos files" required="True"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosgroup.ListGroupSectionMappings"
                        BlockNode="#BlockNode" GroupName="#StosGroup#Downsample" Downsample="#Downsample"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ListBlockStosMaps" Help="List all SliceToSlice maps in a block">
        <Arguments>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Name of block" required="False"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosmap.ListBlockStosMaps"
                        BlockNode="#BlockNode"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ListStosMap"
              Help="List all SliceToSlice mappings within a stos map">
        <Arguments>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Block to create the StosGroup within" required="False"/>
            <Argument flag="-Name" dest="StosMapName" type="str"
                      help="Name of the StosMap" required="True"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosmap.ListStosMap" BlockNode="#BlockNode"
                        StosMapName="#StosMapName"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="AddToStosMap"
              Help="List all SliceToSlice mappings within a stos map">
        <Arguments>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Block to create the StosGroup within" required="False"/>
            <Argument flag="-Name" dest="StosMapName" type="str"
                      help="Name of the StosMap" required="True"/>
            <Argument flag="-ControlSection" dest="ControlSection" type="int"
                      help="Control Section Number" required="True"/>
            <Argument flag="-MappedSection" dest="MappedSection" type="int"
                      help="Mapped Section Number" required="True"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosmap.AddMapping" BlockNode="#BlockNode"
                        StosMapName="#StosMapName" ControlSection="#ControlSection"
                        MappedSection="#MappedSection"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="RemoveFromStosMap"
              Help="List all SliceToSlice mappings within a stos map">
        <Arguments>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Block to create the StosGroup within" required="False"/>
            <Argument flag="-Name" dest="StosMapName" type="str"
                      help="Name of the StosMap" required="True"/>
            <Argument flag="-ControlSection" dest="ControlSection" type="int"
                      help="Control Section Number" required="True"/>
            <Argument flag="-MappedSection" dest="MappedSection" type="int"
                      help="Mapped Section Number" required="True"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosmap.RemoveMapping" BlockNode="#BlockNode"
                        StosMapName="#StosMapName" ControlSection="#ControlSection"
                        MappedSection="#MappedSection"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="AddStos"
              Help="Import a Slice-to-slice transformation to a StosGroup.">
        <Arguments>
            <Argument flag="-File" dest="InputStosFullPath" type="str"
                      help="Path to the .stos file to import" required="True"/>
            <Argument flag="-Block" default="TEM" dest="BlockName" type="str"
                      help="Block to create the StosGroup within" required="False"/>
            <Argument flag="-StosGroup" dest="StosGroup" type="str"
                      help="Name of the StosGroup" required="True"/>
            <Argument flag="-ControlSection" dest="ControlSection" type="int"
                      help="Control Section Number of the Stos Transform" required="True"/>
            <Argument flag="-ControlChannel" dest="ControlChannel" type="str"
                      help="Control Channel of the Stos Transform" required="True"/>
            <Argument flag="-ControlFilter" dest="ControlFilter" type="str"
                      help="Control Filter of image used for transform" required="True"/>
            <Argument flag="-ControlDownsample" dest="ControlDownsample"
                      type="int" help="Downsample level of controlled section's image"
                      required="True"/>
            <Argument flag="-MappedSection" dest="MappedSection" type="int"
                      help="Mapped Section Number of the Stos Transform" required="True"/>
            <Argument flag="-MappedChannel" dest="MappedChannel" type="str"
                      help="Mapped Channel of the Stos Transform" required="True"/>
            <Argument flag="-MappedFilter" dest="MappedFilter" type="str"
                      help="Mapped Filter of image used for transform" required="True"/>
            <Argument flag="-MappedDownsample" dest="MappedDownsample"
                      type="int" help="Downsample level of mapped section's image"
                      required="True"/>
            <Argument flag="-Type" dest="Type" type="str"
                      help="Name of the StosGroup" required="True"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <RequireMatch Attribute="Name" RegEx="#BlockName"/>

            <PythonCall Function="stosgroup.ImportStos"
                        InputStosFullpath="#InputStosFullPath" BlockNode="#BlockNode"
                        GroupName="#StosGroup#ControlDownsample" ControlSectionNumber="#ControlSection"
                        ControlChannelName="#ControlChannel" ControlFilterName="#ControlFilter"
                        ControlDownsample="#ControlDownsample" MappedSectionNumber="#MappedSection"
                        MappedChannelName="#MappedChannel" MappedFilterName="#MappedFilter"
                        MappedDownsample="#MappedDownsample" Type="#Type"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="CleanupStos" Help="Clean common errors in section mappings">
        <Iterate VariableName="SectionMappingNode"
                 XPath="Block/StosGroup[@Name='StosBrute64']/SectionMappings">
            <PythonCall Function="block.ValidateSectionMappingPipeline"
                        section_mapping_node="#SectionMappingNode"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="RepairStosAngles" Help="Negate angles for .stos files created before Dec 1st 2023">
        <Arguments>
            <Argument flag="-Block" default="*" dest="BlockRegEx" type="str"
                      help="Block to examine the StosGroup within" required="False"/>
            <Argument flag="-StosGroup" dest="StosGroup" type="str"
                      help="Name of the StosGroup" default="*" required="False"/>
            <Argument flag="-Type" dest="Type" type="str"
                      help="Type of transform" default="Brute" required="False"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block" Validate="False">
            <RequireMatch Attribute="Name" RegEx="#BlockRegEx"/>
            <Iterate VariableName="SectionMappingNode"
                     XPath="StosGroup[@Name='#StosGroup']" Validate="False">
                <Iterate VariableName="transform_node"
                         XPath="SectionMappings/Transform[@Type='#Type']" Validate="False">
                    <PythonCall Function="migration.ReverseRigidTransformAngles"
                                transform_node="#transform_node"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="SyncValidationTimes"
              Help="Sync ValidationTime metadata to filesystem mtimes after a volume copy."
              Description="Walks filesystem-backed volume nodes and realigns ValidationTime to current mtimes so copied volumes do not look modified and trigger unnecessary pyramid rebuilds."
              Epilog="Example: nornir-build SyncValidationTimes /data/volume">
        <Arguments>
            <Argument flag="-dryrun" action="store_true" dest="dryrun"
                      help="Report nodes that would change without updating ValidationTime"
                      required="False"/>
        </Arguments>
        <PythonCall Module="nornir_buildmanager.operations.migration"
                    Function="SyncValidationTimes" dryrun="#dryrun"/>
    </Pipeline>

    <Pipeline Name="Cleanup" Help="Clean common errors from the volume meta-data">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to edit.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
        </Arguments>
        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <Iterate VariableName="TransformNode" XPath="Transform">
                    <PythonCall Function="general.CleanNodeIfInvalid" node="#TransformNode"/>
                </Iterate>
                <Iterate VariableName="TransformDataNode" XPath="TransformData">
                    <PythonCall Function="general.CleanNodeIfInvalid" node="#TransformDataNode"/>
                </Iterate>
                <Iterate VariableName="ImageNode" XPath="Image">
                    <PythonCall Function="general.CleanNodeIfInvalid" node="#ImageNode"/>
                </Iterate>
                <Iterate VariableName="DataNode" XPath="Data">
                    <PythonCall Function="general.CleanNodeIfInvalid" node="#DataNode"/>
                </Iterate>
                <Iterate VariableName="FilterNode" XPath="Filter">
                    <PythonCall Function="tile.VerifyAssembledImagePathIsCorrect"
                                filter_node="#FilterNode"/>
                    <Iterate VariableName="HistogramNode" XPath="Histogram">
                        <PythonCall Function="general.CleanNodeIfInvalid" node="#HistogramNode"/>
                    </Iterate>
                    <Iterate VariableName="ImageNode" XPath="ImageSet/Level/Image">
                        <PythonCall Function="general.CleanNodeIfInvalid" node="#ImageNode"/>
                    </Iterate>
                    <Iterate VariableName="LevelNode" XPath="ImageSet/Level">
                        <PythonCall Function="general.CleanNodeIfInvalid" node="#LevelNode"/>
                    </Iterate>
                    <Iterate VariableName="DataNode" XPath="Data">
                        <PythonCall Function="general.CleanNodeIfInvalid" node="#DataNode"/>
                    </Iterate>
                </Iterate>
            </Iterate>
        </Iterate>
        <Iterate VariableName="TransformNode"
                 XPath="Block/StosGroup/SectionMappings/Transform">
            <RequireSetMembership Attribute="ControlSectionNumber"
                                  List="#Sections"/>
            <PythonCall Function="block.ValidateSectionMappingTransformPipeline"
                        stos_transform_node="#TransformNode"/>
        </Iterate>
        <Iterate VariableName="TransformNode"
                 XPath="Block/StosGroup/SectionMappings/Transform">
            <RequireSetMembership Attribute="MappedSectionNumber"
                                  List="#Sections"/>
            <PythonCall Function="block.ValidateSectionMappingTransformPipeline"
                        stos_transform_node="#TransformNode"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="CreateBlobFilter"
              Help="Create a blob filter from an input filter.  Blobbing can make brute slice-to-slice alignment more reliable for downsampled mosaics with fine textures.">
        <Arguments>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section to execute blob on.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be assembled"
                      required="False"/>
            <Argument flag="-InputFilter" dest="InputFilter" default="Leveled"
                      help="Name of filter to create blob filter for" required="True"/>
            <Argument flag="-Levels" dest="BlobDownsampleLevels"
                      default="1" help="Downsample levels to be blobbed" required="False"/>
            <Argument flag="-Radius" dest="BlobRadius" default="9"
                      help="Radius to calculate variance within" required="False"/>
            <Argument flag="-Median" dest="BlobMedian" default="7"
                      help="Median filter radius for input" required="False"/>
            <Argument flag="-Max" dest="BlobMax" default="3"
                      help="Max variance before cropping" required="False"/>
            <Argument flag="-OutputFilter" dest="OutputFilterName"
                      default="Blob" help="Prefix added to created filters" required="False"/>
        </Arguments>

        <Iterate VariableName="SectionNode" XPath="Block/Section">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

                <Select VariableName="FilterNode" XPath="Filter[@Name='#InputFilter']"/>

                <PythonCall Function="channel.CreateBlobFilter" Levels="#BlobDownsampleLevels"
                            InputFilter="#FilterNode" OutputFilterName="#OutputFilterName">
                    <Parameters>
                        <Entry Name="r" Value="#BlobRadius"/>
                        <Entry Name="median" Value="#BlobMedian"/>
                        <Entry Name="max" Value="#BlobMax"/>
                    </Parameters>
                </PythonCall>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="AlignSections"
              Help="Performs slice to slice alignment from scratch to produce an initial 3D volume.  After alignment the best alignment results are selected to create a final stosmap." Description="Builds section-to-section mappings and computes brute alignment transforms across adjacent sections." Epilog="Example: nornir-build AlignSections /data/volume -Downsample 8 -Channels .* -Filters Raw8 -OutputStosMap PotentialRegistrationChain">
        <Arguments>
            <Argument flag="-Center" dest="Center" type="int"
                      help="Center section of the volume.  Defaults to center if unspecified.  The first run center is saved in meta-data and re-used on later runs."
                      required="False"/>
            <Argument flag="-NumAdjacentSections" default="1"
                      dest="NumAdjacentSections" type="int"
                      help="Number of adjacent sections to attempt registration with."
                      required="False"/>
            <Argument flag="-AngleRange" default="" dest="AngleSearchRange"
                      type="FloatRange" help="Range of angles to check in degrees not radians"
                      required="False"/>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str" help="Regular expression describing channels to be aligned"
                      required="False"/>
            <Argument flag="-Filters" default="(?![M|m]ask)" dest="FiltersRegEx"
                      type="str" help="Regular expression describing filters to be aligned"
                      required="False"/>
            <Argument flag="-UseMasks" action="store_true" dest="UseMasks"
                      help="Use masks for images if they are available.  Masked areas are replaced with random noise for alignment purposes."
                      required="False"/>
            <Argument flag="-Downsample" default="1" dest="StosBruteDownsample"
                      type="int"
                      help="Amount to downsample input images before attempting alignment.  Images are padded to the next power of two for the FFT.  Large images, roughly greater than 2048x2048, can be very slow."
                      required="True"/>
            <Argument flag="-OutputStosMap" default="PotentialRegistrationChain"
                      dest="OutputStosMap" type="str" help="Name assigned to generated stos map."
                      required="False"/>
            <Argument flag="-NoFlipCheck" action="store_true" dest="NoFlipCheck"
                      help="Do not check for flipped sections.  Use this option if it is impossible for images to be flipped.  Doubles execution speed."
                      required="False"/>
            <Argument flag="-Method" dest="Method" help="Method to use for intitial registration"
                      type="nornir_imageregistration.settings.SliceToSliceMethod"
                      required="False" default="LogPolar"/>
        </Arguments>

        <Select VariableName="block_node" XPath="Block"/>
        <PythonCall Function="block.CreateOrUpdateSectionToSectionMapping"
                    ChannelsRegEx="#ChannelsRegEx" FiltersRegEx="#FiltersRegEx"
                    OutputStosMapName="#OutputStosMap" block_node="#block_node">
            <Parameters>
                <Entry Name="NumAdjacentSections" Value="#NumAdjacentSections"/>
                <Entry Name="CenterSection" Value="#Center"/>
            </Parameters>
        </PythonCall>

        <Select Root="block_node" VariableName="StosMapNodeObj"
                XPath="StosMap[@Name='#OutputStosMap']"/>
        <Iterate Root="StosMapNodeObj" VariableName="mapping_node"
                 XPath="Mapping">
            <PythonCall Function="block.StosBrute" Root="BlockNode"
                        OutputGroup="StosBrute#StosBruteDownsample" ChannelsRegEx="#ChannelsRegEx"
                        FiltersRegEx="#FiltersRegEx" AngleSearchRange="#AngleSearchRange" Method="#Method">
                <Parameters>
                    <Entry Name="Downsample" Value="#StosBruteDownsample"/>
                    <Entry Name="refine"/>
                    <Entry Name="regularize"/>
                    <Entry Name="UseMasks" Value="#UseMasks"/>
                    <Entry Name="NoFlipCheck" Value="#NoFlipCheck"/>
                </Parameters>
            </PythonCall>
        </Iterate>
    </Pipeline>

    <Pipeline Name="AssembleStosOverlays"
              Help="Assembles registered images for .stos files in a group.  Typically this is done so quality of registration at that stage can be assessed.">
        <Arguments>
            <Argument flag="-StosGroup" default="StosBrute" dest="StosGroup"
                      type="str" help="Stos group to assemble" required="True"/>
            <Argument flag="-Downsample" dest="InputDownsample" type="int"
                      help="Downsample level to assemble" required="True"/>
            <Argument flag="-StosMap" default="FinalStosMap" dest="StosMap"
                      type="str" help="Name assigned to generated stos map." required="True"/>
        </Arguments>

        <Select VariableName="BlockNode" XPath="Block"/>
        <Select VariableName="GroupNodeObj" Root="BlockNode"
                XPath="StosGroup[@Name='#StosGroup#InputDownsample']"/>
        <Select Root="BlockNode" VariableName="StosMapNodeObj"
                XPath="StosMap[@Name='#StosMap']"/>
        <PythonCall Function="block.AssembleStosOverlays"
                    group_node="#GroupNodeObj" stos_map_node="#StosMapNodeObj"/>
    </Pipeline>

    <Pipeline Name="CalculateStosGroupWarpMetrics"
              Help="Creates images indicating the amount of warping present in each .stos file in a group.  Typically this is done so quality of registration at that stage can be assessed.">
        <Arguments>
            <Argument flag="-StosGroup" default="StosBrute" dest="StosGroup"
                      type="str" help="Stos group to assemble" required="True"/>
            <Argument flag="-Downsample" dest="InputDownsample" type="int"
                      help="Downsample level to assemble" required="True"/>
            <Argument flag="-StosMap" default="FinalStosMap" dest="StosMap"
                      type="str" help="Name assigned to generated stos map." required="True"/>
            <Argument flag="-MaxAngle" dest="MaxReportedAngle"
                      type="float"
                      help="Maximum angle value, in degrees, in output image heatmaps.  Defaults to max angle found for each section.  Specify a value to ensure consistent heatmap ranges for each transform.  15-30 degrees is usually a reasonable value."
                      required="False"/>
            <Argument flag="-RenderToTargetSection" action="store_false" default="True" dest="RenderToSourceSpace"
                      help="Indicates whether the image is rendered in the source or target section coordinates.  Rendering to source will result in a square image.  Rendering to target is better for transforms to volume space."/>
        </Arguments>

        <Select VariableName="BlockNode" XPath="Block"/>
        <Select VariableName="GroupNodeObj" Root="BlockNode"
                XPath="StosGroup[@Name='#StosGroup#InputDownsample']"/>
        <Select Root="BlockNode" VariableName="StosMapNodeObj"
                XPath="StosMap[@Name='#StosMap']"/>
        <PythonCall Function="block.CalculateStosGroupWarpMeasurementImages"
                    group_node="#GroupNodeObj" stos_map_node="#StosMapNodeObj" MaxReportedAngle="#MaxReportedAngle"
                    RenderToSourceSpace="#RenderToSourceSpace"/>
    </Pipeline>

    <Pipeline Name="ScoreStosGroupQuality"
              Help="Compute full-pair ZNCC for each .stos in a group, write stos_quality.json, and set Transform PairZNCC attributes.">
        <Arguments>
            <Argument flag="-StosGroup" default="StosBrute" dest="StosGroup"
                      type="str" help="Stos group to score" required="True"/>
            <Argument flag="-Downsample" dest="InputDownsample" type="int"
                      help="Downsample level of the stos group" required="True"/>
            <Argument flag="-StosMap" default="FinalStosMap" dest="StosMap"
                      type="str" help="Stos map describing which pairs to score" required="True"/>
            <Argument flag="-MaxSide" default="2048" dest="MaxSide" type="int"
                      help="Clamp longest image side (pixels) before scoring" required="False"/>
            <Argument flag="-Force" action="store_true" dest="Force"
                      help="Recompute pair ZNCC even when the cache entry is fresh" required="False"/>
        </Arguments>

        <Select VariableName="BlockNode" XPath="Block"/>
        <Select VariableName="GroupNodeObj" Root="BlockNode"
                XPath="StosGroup[@Name='#StosGroup#InputDownsample']"/>
        <Select Root="BlockNode" VariableName="StosMapNodeObj"
                XPath="StosMap[@Name='#StosMap']"/>
        <PythonCall Function="block.ScoreStosGroupQuality"
                    group_node="#GroupNodeObj" stos_map_node="#StosMapNodeObj"
                    MaxSide="#MaxSide" Force="#Force"/>
    </Pipeline>

    <Pipeline Name="SelectBestRegistrationChain"
              Help="Create a new registration chain by examining the stos overlays produced by AssembleStosOverlays.  If two registrations are possible for a section the overlay with the most overlap is chosen.">
        <Arguments>
            <Argument flag="-StosGroup" default="StosBrute" dest="StosGroup"
                      type="str" help="Stos group to assemble" required="True"/>
            <Argument flag="-Downsample" dest="InputDownsample" type="int"
                      help="Downsample level to assemble" required="True"/>
            <Argument flag="-InputStosMap" default="PotentialRegistrationChain"
                      dest="InputStosMap" type="str" help="Name assigned to generated stos map."
                      required="True"/>
            <Argument flag="-OutputStosMap" default="FinalStosMap" dest="OutputStosMap"
                      type="str" help="Name assigned to generated stos map." required="False"/>
        </Arguments>

        <Select VariableName="BlockNode" XPath="Block"/>
        <Select VariableName="GroupNodeObj" Root="BlockNode"
                XPath="StosGroup[@Name='#StosGroup#InputDownsample']"/>
        <Select Root="BlockNode" VariableName="StosMapNodeObj"
                XPath="StosMap[@Name='#InputStosMap']"/>
        <PythonCall Function="block.SelectBestRegistrationChain"
                    InputGroupNode="#GroupNodeObj" InputStosMapNode="#StosMapNodeObj"
                    OutputStosMapName="#OutputStosMap"/>
    </Pipeline>


    <Pipeline Name="RefineSectionAlignment-IrTools"
              Help="Refines slice-to-slice alignment at the given downsample level.  This pipeline is often used iteratively at higher and higher resolutions to improve registration quality.">
        <Arguments>
            <Argument flag="-SectionMap" default="FinalStosMap" dest="StosMap"
                      type="str" help="Map describing which sections register to each other"
                      required="False"/>
            <Argument flag="-InputGroup" default="StosBrute" dest="InputGroup"
                      type="str" help="Map describing which sections register to each other"
                      required="False"/>
            <Argument flag="-InputDownsample" dest="InputDownsample"
                      type="int" help="Downsample level to use for input" required="True"/>
            <Argument flag="-OutputGroup" default="StosGrid" dest="OutputGroup"
                      type="str" help="Name of output group" required="False"/>
            <Argument flag="-OutputDownsample" dest="OutputDownsample"
                      type="int" help="Downsample level to use for refinement" required="True"/>
            <Argument flag="-Filters" default="(?![M|m]ask)" dest="FiltersRegEx"
                      type="str" help="Regular expression describing filters to be aligned"
                      required="False"/>
            <Argument flag="-UseMasks" action="store_true" dest="UseMasks"
                      help="Use masks for images if they are available.  Masked areas are replaced with random noise for alignment purposes."
                      required="False"/>
            <Argument flag="-Threshold" default="0.1" dest="DisplacementThreshold"
                      type="float"
                      help="Displacement threshold for control point movement.  Refinement stops below this value"
                      required="False"/>
            <Argument flag="-Spacing" default="128" dest="GridSpacing"
                      type="int" help="Grid spacing in pixels" required="False"/>
            <Argument flag="-CellArea" default="256" dest="CellArea"
                      type="int" help="Size of region around each grid point to register, in pixels"
                      required="False"/>
            <Argument flag="-Iterations" default="10" dest="Iterations"
                      type="int" help="Maximum number of refinement passes to run"
                      required="False"/>
        </Arguments>

        <Select VariableName="BlockNode" XPath="Block"/>
        <Select Root="BlockNode" VariableName="StosMapNodeObj"
                XPath="StosMap[@Name='#StosMap']"/>

        <Select VariableName="InputGroupNode" Root="BlockNode"
                XPath="StosGroup[@Name='#InputGroup#InputDownsample']"/>
        <Iterate Root="StosMapNodeObj" VariableName="MappingNodeObj"
                 XPath="Mapping">
            <PythonCall Function="block.IrStosGridRefine" MappingNode="#MappingNodeObj"
                        OutputStosGroup="#OutputGroup#OutputDownsample" Downsample="#OutputDownsample"
                        ControlFilterPattern="#FiltersRegEx" MappedFilterPattern="#FiltersRegEx"
                        UseMasks="#UseMasks">
                <Parameters>
                    <Entry Name="grid_spacing" Value="#GridSpacing"/>
                    <Entry Name="neighborhood" Value="#CellArea"/>
                    <Entry Name="it" Value="#Iterations"/>
                    <Entry Name="fft" Value="2 0.25"/>
                    <Entry Name="displacement_threshold" Value="#DisplacementThreshold"/>
                </Parameters>
            </PythonCall>
        </Iterate>
    </Pipeline>

    <Pipeline Name="RefineSectionAlignment"
              Help="Refines slice-to-slice alignment at the given downsample level.  This pipeline is often used iteratively at higher and higher resolutions to improve registration quality.">
        <Arguments>
            <Argument flag="-SectionMap" default="FinalStosMap" dest="StosMap"
                      type="str" help="Map describing which sections register to each other"
                      required="False"/>
            <Argument flag="-InputGroup" default="StosBrute" dest="InputGroup"
                      type="str" help="Map describing which sections register to each other"
                      required="False"/>
            <Argument flag="-InputDownsample" dest="InputDownsample"
                      type="int" help="Downsample level to use for input" required="True"/>
            <Argument flag="-OutputGroup" default="StosGrid" dest="OutputGroup"
                      type="str" help="Name of output group" required="False"/>
            <Argument flag="-OutputDownsample" dest="OutputDownsample"
                      type="int" help="Downsample level to use for refinement" required="True"/>
            <Argument flag="-Filters" default="(?![M|m]ask)" dest="FiltersRegEx"
                      type="str" help="Regular expression describing filters to be aligned"
                      required="False"/>
            <Argument flag="-IgnoreMasks" action="store_true" dest="IgnoreMasks"
                      help="Ignore masks for images if they are available.  Masked areas are replaced with random noise for alignment purposes."
                      required="False"/>
            <Argument flag="-Spacing" default="192" dest="grid_spacing"
                      type="int" help="Grid cell spacing in pixels" required="False"/>
            <Argument flag="-CellArea" default="256" dest="cell_size"
                      type="int"
                      help="Area around each grid point to register, in pixels.  It is suggested to have overlap between the cells.  So cells of [128,128] would be spaced [64,64] apart."
                      required="False"/>
            <Argument flag="-Iterations" default="10" dest="num_iterations"
                      type="int" help="Maximum number of refinement passes to run"
                      required="False"/>
            <Argument flag="-AngleRange" dest="angles_to_search"
                      type="FloatRange"
                      help="Range of angles to check in degrees not radians.  Each cell is rotated by the angle and checked.  The highest scoring registration is used."
                      required="False"/>
            <Argument flag="-MaxTravelForFinalization" dest="max_travel_for_finalization"
                      type="float"
                      help="The maximum amount of travel a point can have from its predicted position for it is considered correctly placed."
                      required="False"/>
            <Argument flag="-MaxTravelForFinalizedImprovement" dest="max_travel_for_finalization_improvement"
                      type="float"
                      help="When finalized points are checked to see if they need to be nudged, they must move less than this distance to be considered.  If None, no limit is applied."
                      required="False"/>
            <Argument flag="-MinAlignOverlap" dest="min_alignment_overlap"
                      type="float"
                      help="The minimum amount of overlap a cell (point) must have with its expected position for a registration result to be considered valid."
                      required="False"/>
            <Argument flag="-MinUnmaskedCellArea" dest="min_unmasked_area"
                      type="float" help="Amount of cell area that must be unmasked to attempt registering a given cell"
                      required="False"/>
            <Argument flag="-SaveImages" action="store_true" dest="SaveImages"
                      help="Amount of cell area that must be unmasked to attempt registering a given cell"
                      required="False"/>
            <Argument flag="-SavePlots" action="store_true" dest="SavePlots"
                      help="Amount of cell area that must be unmasked to attempt registering a given cell"
                      required="False"/>
            <Argument flag="-MappedSections" dest="MappedSections" type="IntegerList"
                      help="Section numbers to map to control sections.  If omitted all sections are processed.  Values are separated with commas and ranges are indicated by hyphens. Ex: '1,3,5-7' == [1,3,5,6,7]"
                      required="False"/>
        </Arguments>

        <Select VariableName="BlockNode" XPath="Block"/>
        <Select Root="BlockNode" VariableName="StosMapNodeObj"
                XPath="StosMap[@Name='#StosMap']"/>

        <Select VariableName="InputGroupNode" Root="BlockNode"
                XPath="StosGroup[@Name='#InputGroup#InputDownsample']"/>
        <Iterate Root="StosMapNodeObj" VariableName="MappingNodeObj"
                 XPath="Mapping">
            <PythonCall Function="block.StosGridRefine" mapping_node="#MappingNodeObj"
                        OutputStosGroup="#OutputGroup#OutputDownsample" Downsample="#OutputDownsample"
                        ControlFilterPattern="#FiltersRegEx" MappedFilterPattern="#FiltersRegEx"
                        IgnoreMasks="#IgnoreMasks"
                        num_iterations="#num_iterations" cell_size="#cell_size"
                        grid_spacing="#grid_spacing" angles_to_search="#angles_to_search"
                        max_travel_for_finalization="#max_travel_for_finalization"
                        max_travel_for_finalization_improvement="#max_travel_for_finalization_improvement"
                        min_alignment_overlap="#min_alignment_overlap" min_unmasked_area="#min_unmasked_area"
                        SaveImages="#SaveImages" SavePlots="#SavePlots" MappedSections="#MappedSections"
            >
            </PythonCall>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ScaleVolumeTransforms"
              Help="Scales a stos group to use a different downsample level.  Useful for producing full-resolution output after generating transforms using downsampled images.">
        <Arguments>
            <Argument flag="-InputGroup" dest="ScaleGroupName" type="str"
                      required="True" help="Name of stos group to scale"/>
            <Argument flag="-InputDownsample" dest="ScaleInputDownsample"
                      type="int" help="Downsample level of input stosgroup" required="True"/>
            <Argument flag="-OutputDownsample" default="1"
                      dest="ScaleOutputDownsample" type="int"
                      help="Downsample level of output.  Defaults to 1." required="False"/>
            <Argument flag="-UseMasks" action="store_true" dest="UseMasks"
                      help="Use masks for images if they are available.  Masked areas are replaced with random noise for alignment purposes."
                      required="False"/>
            <Argument flag="-workers" type="int" required="False" dest="workers"
                      help="Parallel worker count for per-transform scaling. Defaults to CPU count; use 1 for serial."/>
        </Arguments>

        <Iterate VariableName="BlockNode" XPath="Block">
            <Select VariableName="StosGroupNode" Root="BlockNode"
                    XPath="StosGroup[@Name='#ScaleGroupName#ScaleInputDownsample']"/>
            <PythonCall Function="block.ScaleStosGroup"
                        InputStosGroupNode="#StosGroupNode" OutputDownsample="#ScaleOutputDownsample"
                        OutputGroupName="#ScaleGroupName#ScaleOutputDownsample" UseMasks="#UseMasks"
                        workers="#workers"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="LinearizeVolume">
        Help="Converts each stos transform in a group to a linear transform with translation, rotation, and scale
        components.\
        Mixes the output of the linear transform with the original transform according to the blend factor. This can\
        help reduce warping over long distances in the volume.">
        <Arguments>
            <Argument flag="-InputGroup" dest="InputGroupName" type="str"
                      required="True" help="Name of stos group to linearize"/>
            <Argument flag="-InputDownsample" dest="InputDownsample"
                      type="int" help="Downsample level of input stosgroup" required="True"/>
            <Argument flag="-OutputGroup" dest="OutputGroupName" type="str" required="True"
                      help="Name of the output stos group"/>
            <Argument flag="-min_blend" type="float" required="False" dest="min_blend"
                      default="0.05"
                      help="Floor weight toward rigid linear blend (0–1). Uniform when -travel_limit is omitted."/>
            <Argument flag="-linear_factor" type="float" required="False" dest="linear_blend_factor"
                      help="Deprecated alias for -min_blend."/>
            <Argument flag="-max_blend" type="float" required="False" dest="max_blend"
                      help="Cap per-point rigid blend weight (0–1). Defaults to min_blend without travel_limit, else 0.9."/>
            <Argument flag="-travel_limit" type="float" required="False" dest="travel_limit"
                      default="512"
                      help="Full-resolution distance scale for smooth per-point blend toward the rigid transform prediction."/>
            <Argument flag="-reblend_iterations" type="int" required="False" dest="reblend_iterations"
                      default="8"
                      help="Iterative linear-blend passes per transform composition. Use 1 for a single pass."/>
            <Argument flag="-reblend_tolerance" type="float" required="False" dest="reblend_tolerance"
                      default="0.5"
                      help="Stop re-blending when the maximum control-point movement falls below this distance (pixels at working resolution)."/>
            <Argument flag="-no_rotation" action="store_true" required="False" dest="ignore_rotation"
                      help="Added for SEM volumes where it is certain there is no rotation from section to section, defaults to False"/>
            <Argument flag="-workers" type="int" required="False" dest="workers"
                      help="Parallel worker count for per-transform linear blend. Defaults to CPU count; use 1 for serial."/>
        </Arguments>

        <Iterate VariableName="BlockNode" XPath="Block">
            <Select VariableName="StosGroupNode" Root="BlockNode"
                    XPath="StosGroup[@Name='#InputGroupName#InputDownsample']"/>
            <PythonCall Function="block.LinearBlendStosGroup"
                        InputStosGroupNode="#StosGroupNode"
                        OutputGroupName="#OutputGroupName#InputDownsample"
                        min_blend="#min_blend"
                        linear_blend_factor="#linear_blend_factor"
                        max_blend="#max_blend"
                        travel_limit="#travel_limit"
                        reblend_iterations="#reblend_iterations"
                        reblend_tolerance="#reblend_tolerance"
                        ignore_rotation="#ignore_rotation"
                        workers="#workers"/>
        </Iterate>
    </Pipeline>

    <Pipeline Name="SliceToVolume"
              Help="Created a group of slice-to-volume transforms from a group of slice-to-slice transforms">
        <Arguments>
            <Argument flag="-Downsample" dest="StovDownsample" default="16"
                      type="int" help="Downsample level to use for SliceToVolume"
                      required="False"/>
            <Argument flag="-InputGroup" default="StosGrid" dest="StovInputGroup"
                      type="str" help="Map describing which sections register to each other"
                      required="True"/>
            <Argument flag="-OutputGroup" default="SliceToVolume" dest="StovOutputGroupName"
                      type="str" help="Name of output group" required="False"/>
            <Argument flag="-InputMap" default="FinalStosMap" dest="InputMap"
                      type="str" help="Name of output group" required="False"/>
            <Argument flag="-OutputMap" dest="OutputMap" type="str"
                      help="Name of output registration map.  Defaults to -OutputGroup name if omitted."
                      required="False"/>
            <Argument flag="-NoEnrichment" dest="Enrich" action="store_false"
                      help="Do not add extra control points to slice-to-volume transforms to increase accuracy."
                      required="False"/>
            <Argument flag="-Tolerance" dest="Tolerance" type="float"
                      default="64"
                      help="Maximum distance, at full resolution, between the expected and actual position of transformed triangle centroids.  Above this value a control point is added at the centroid to increase transform accuracy."
                      required="False"/>
            <Argument flag="-NoLinearBlend" dest="NoLinearBlend" action="store_true"
                      help="Compose slice-to-volume transforms without per-section linear blend. Overrides -min_blend and -travel_limit pipeline defaults."
                      required="False"/>
            <Argument flag="-min_blend" type="float" required="False" dest="min_blend"
                      default="0.05"
                      help="Floor weight toward rigid linear blend during composition (0–1)."/>
            <Argument flag="-linear_factor" type="float" required="False" dest="linear_blend_factor"
                      help="Deprecated alias for -min_blend."/>
            <Argument flag="-max_blend" type="float" required="False" dest="max_blend"
                      help="Cap per-point rigid blend weight during composition."/>
            <Argument flag="-travel_limit" type="float" required="False" dest="travel_limit"
                      default="512"
                      help="Full-resolution distance scale for smooth per-point blend toward the rigid transform prediction during chain composition."/>
            <Argument flag="-reblend_iterations" type="int" required="False" dest="reblend_iterations"
                      default="8"
                      help="Iterative linear-blend passes per transform composition. Use 1 for a single pass."/>
            <Argument flag="-reblend_tolerance" type="float" required="False" dest="reblend_tolerance"
                      default="0.5"
                      help="Stop re-blending when the maximum control-point movement falls below this distance (pixels at working resolution)."/>
            <Argument flag="-no_rotation" action="store_true" required="False" dest="ignore_rotation"
                      help="Added for SEM volumes where it is certain there is no rotation from section to section, defaults to False"/>
            <Argument flag="-mesh_rigid_linear" dest="chain_consistent_linear" action="store_false"
                      default="True"
                      help="Use rigid fit of accumulated B→C mesh for linear blend instead of composed rigid slice-to-slice chain."/>


        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <Iterate VariableName="StosMapNode" XPath="StosMap[@Name='#InputMap']">
                <Iterate VariableName="StosGroupNode" Root="BlockNode"
                         XPath="StosGroup[@Name='#StovInputGroup#StovDownsample']">
                    <PythonCall Function="block.BuildSliceToVolumeTransforms"
                                OutputMap="#OutputMap" OutputGroupName="#StovOutputGroupName"
                                Downsample="#StovDownsample" Enrich="#Enrich" Tolerance="#Tolerance"
                                stos_map_node="#StosMapNode" stos_group_node="#StosGroupNode"
                                linear_blend_factor="#linear_blend_factor"
                                min_blend="#min_blend"
                                max_blend="#max_blend"
                                travel_limit="#travel_limit"
                                reblend_iterations="#reblend_iterations"
                                reblend_tolerance="#reblend_tolerance"
                                ignore_rotation="#ignore_rotation"
                                chain_consistent_linear="#chain_consistent_linear"
                                NoLinearBlend="#NoLinearBlend">
                    </PythonCall>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <!-- <Pipeline Name="SliceToVolumeZeroTest" Help="Created a group of slice-to-volume 
        transforms from a group of slice-to-slice transforms"> <Arguments> <Argument 
        flag="-InputDownsample" dest="StovDownsample" default="16" type="int" help="Downsample 
        level to use for SliceToVolume" required="False"/> <Argument flag="-InputGroup" 
        default="StosGrid" dest="StovInputGroup" type="str" help="Map describing 
        which sections register to each other" required="True"/> <Argument flag="-OutputGroup" 
        default="SliceToVolume" dest="StovOutputGroupName" type="str" help="Name 
        of output group" required="False"/> <Argument flag="-InputMap" default="FinalStosMap" 
        dest="InputMap" type="str" help="Name of output group" required="False"/> 
        </Arguments> <Iterate VariableName="BlockNode" XPath="Block"> <Iterate VariableName="StosMapNode" 
        XPath="StosMap[@Name='#InputMap']"> <Iterate VariableName="StosGroupNode" 
        Root="BlockNode" XPath="StosGroup[@Name='#StovOutputGroupName#StovDownsample']"> 
        <PythonCall Function="block.TranslateVolumeToZeroOrigin"> </PythonCall> </Iterate> 
        </Iterate> </Iterate> </Pipeline> -->

    -->

    <Pipeline Name="VolumeImage"
              Help="Produces an assembled image for transforms in a stos group.  If the transforms are slice-to-volume transforms the output should be registered into a volume.  Note that using this function requires assembled images to exist at full resolution.  If these do not exist see the MosaicToVolume pipeline.">
        <Arguments>
            <Argument flag="-InputGroup" default="SliceToVolume" dest="VolumeImageGroupName"
                      type="str" required="False"
                      help="Name of stos group to create scaled version of, default = SliceToVolume"/>
            <Argument flag="-InputDownsample" default="1"
                      dest="VolumeImageDownsample" type="int"
                      help="Downsample level of input stosgroup, default = 1"/>
        </Arguments>
        <Iterate VariableName="BlockNode" XPath="Block">
            <!-- <Iterate VariableName="StosMapNode" XPath="StosMap[@Name='#VolumeImageGroupName']"> 
                <Iterate VariableName="StosGroupNode" Root="BlockNode" XPath="StosGroup[@Name='#VolumeImageGroupName#VolumeImageDownsample']"> 
                <PythonCall Function="block.AssembleStosOverlays" group_node="#StosGroupNode"/>
                </Iterate> </Iterate> -->

            <Iterate VariableName="StosGroupNode" Root="BlockNode"
                     XPath="StosGroup[@Name='#VolumeImageGroupName#VolumeImageDownsample']">
                <Iterate VariableName="TransformNodeObj" Root="StosGroupNode"
                         XPath="SectionMappings/Transform">
                    <PythonCall Function="block.SectionToVolumeImage"
                                TransformNode="#TransformNodeObj"/>
                </Iterate>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="MosaicToVolume"
              Help="Used after slice to volume.  Creates new mosaic transforms that map tiles directly into the volume space.  This is done by passing the mosaic transform through a slice-to-volume transformation.  Assemble can then be used to produce volume registered images.">
        <Arguments>
            <Argument flag="-Channels" default="*" dest="ChannelsRegEx"
                      type="str" help="Regular expression describing channels to be processed"
                      required="False"/>
            <Argument flag="-InputDownsample" dest="StosDownsample"
                      default="1" type="int" help="Downsample level of input stosgroup"
                      required="False"/>
            <Argument flag="-InputMap" default="SliceToVolume" dest="InputMapName"
                      type="str" help="Name of output group" required="False"/>
            <Argument flag="-InputGroup" default="SliceToVolume" dest="InputStosGroup"
                      type="str" required="False"
                      help="Name of stos group to pass mosaic transforms through"/>
            <Argument flag="-InputTransform" default="Grid"
                      dest="InputTransformName" type="str" required="False"
                      help="Mosaic transform to pass through slice-to-slice transform"/>
            <Argument flag="-OutputTransform" default="ChannelToVolume"
                      dest="OutputTransform" type="str" required="False"
                      help="Name of output transform mapping a channel to a volume"/>
        </Arguments>

        <Iterate VariableName="BlockNode" XPath="Block">
            <Select VariableName="StosMapNode" XPath="StosMap[@Name='#InputMapName']"/>
            <Select VariableName="StosGroupNode" Root="BlockNode"
                    XPath="StosGroup[@Name='#InputStosGroup#StosDownsample']"/>

            <PythonCall Function="block.BuildMosaicToVolumeTransforms"
                        block_node="#BlockNode" ChannelsRegEx="#ChannelsRegEx" stos_map_node="#StosMapNode"
                        stos_group_node="#StosGroupNode" InputTransformName="#InputTransformName"
                        OutputTransformName="#OutputTransform"/>

            <!-- <Iterate VariableName="ChannelNode" XPath="Section/Channel"> <RequireMatch 
                Attribute="Name" RegEx="#ChannelsRegEx"/> <Select VariableName="TransformNode" 
                XPath="Transform[@Name='#InputTransform']"/> <PythonCall Function="block.BuildMosaicToVolumeTransforms" 
                OutputTransformName="#OutputTransform"/> </Iterate> -->
        </Iterate>
    </Pipeline>

    <Pipeline Name="ExportImages"
              Help="Copies images of sections to an output directory">
        <Arguments>
            <Argument flag="-Channels" dest="ChannelRegEx" default="*"
                      help="Regular expression describing channels to be assembled"
                      required="False"/>
            <Argument flag="-Filters" dest="FilterRegEx" default="(?![M|m]ask)"
                      help="Regular expression describing filters to be assembled"
                      required="False"/>
            <Argument flag="-Downsample" dest="Downsample" type="int"
                      default="1" help="Downsample output images by this factor" required="False"/>
            <Argument flag="-Output" dest="OutputPath"
                      help="Directory to move assembled images to" required="False"/>
        </Arguments>

        <Iterate VariableName="ChannelNode" XPath="Block/Section/Channel">
            <RequireMatch Attribute="Name" RegEx="#ChannelRegEx"/>

            <Iterate VariableName="FilterNodeObj" XPath="Filter">
                <RequireMatch Attribute="Name" RegEx="#FilterRegEx"/>

                <PythonCall Function="reporting.CopyImage" OutputDir="#OutputPath"
                            FilterNode="#FilterNodeObj" Downsample="#Downsample"/>
            </Iterate>
        </Iterate>
    </Pipeline>

    <Pipeline Name="ExportAnnotationCrops"
              Help="Export annotation crops for SAM2 / SA-1B training from OData or a saved OData dump."
              Description="Ingests Locations via -OData or -Geometries, then writes lossless PNG crops, 1-bit masks, COCO RLE JSON, and optional overlays. Does not modify VolumeData.xml."
              Epilog="Example: nornir-build ExportAnnotationCrops /data/volume -OData http://host/odata -Output /data/sam2 -Channels TEM -Filters Leveled">
        <Arguments>
            <Argument flag="-OData" dest="OData"
                      help="Connectome OData V4 service root. Exactly one of -OData or -Geometries is required."
                      required="False"/>
            <Argument flag="-Geometries" dest="Geometries"
                      help="Saved OData Locations dump (JSONL, {value:[]}, or concatenated page documents). Exactly one of -OData or -Geometries is required."
                      required="False"/>
            <Argument flag="-ODataFilter" dest="ODataFilter"
                      help="Override $filter only. Default selects closed polygons on Cell types including subtypes."
                      required="False"/>
            <Argument flag="-Output" dest="OutputPath"
                      help="Directory for PNG crops, JSON, masks, overlays, and _work cache"
                      required="True"/>
            <Argument flag="-Channels" dest="Channels" default="TEM"
                      help="Regular expression for the channel that owns the tileset"
                      required="False"/>
            <Argument flag="-Filters" dest="Filters" default="Leveled"
                      help="Regular expression for the filter that owns the tileset"
                      required="False"/>
            <Argument flag="-Downsample" dest="Downsample" type="int" default="1"
                      help="Tileset downsample to train at. With -Exporter tiled, masks that do not fit in one MaxTexture crop are split instead of coarsened."
                      required="False"/>
            <Argument flag="-Exporter" dest="Exporter" default="tiled"
                      help="Crop placement. tiled (default) keeps -Downsample and splits on the half-step lattice. legacy coarsens D until the mask fits one crop and never splits."
                      required="False"/>
            <Argument flag="-Pad" dest="Pad" type="float" default="1"
                      help="Mosaic pixels added to each bbox side. Default is 1."
                      required="False"/>
            <Argument flag="-MaxTexture" dest="MaxTexture" type="int"
                      help="Max pixels per crop axis. Default is 1024."
                      required="False"/>
            <Argument flag="-MaxTiles" dest="MaxTiles" type="int"
                      help="Override floor(MaxTexture / tile dim) for the snapped tile window"
                      required="False"/>
            <Argument flag="-MinProcessPixels" dest="MinProcessPixels" type="int" default="16"
                      help="Retained for compatibility. Does not change the training downsample."
                      required="False"/>
            <Argument flag="-Workers" dest="Workers" type="int"
                      help="Stitch PNG encode process-pool size. Default is CPU count. Tile reads use a thread pool sized from CPU count; column bands size themselves from RAM."
                      required="False"/>
            <Argument flag="-MaskWorkers" dest="MaskWorkers" type="int"
                      help="Mask rasterize process-pool size. Default is CPU count."
                      required="False"/>
            <Argument flag="-StageTiles" dest="StageTiles"
                      help="Scratch root for NAS tile copies. Default is /tmp/nornir-stage-tiles/{volume}/z{section}. Override with a local Linux path (not a Windows/CIFS mount). Cleared after each section."
                      required="False"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to process. Omitted means every Z that has geometries."
                      required="False"/>
            <Argument flag="-IncludeOffEdge" dest="IncludeOffEdge" action="store_true"
                      help="Keep OffEdge locations. They are skipped by default."/>
            <Argument flag="-Force" dest="Force" action="store_true"
                      help="Full rebuild: refetch OData MosaicShape and restitch every crop even when watermarks match."/>
            <Argument flag="-RefreshOData" dest="RefreshOData" action="store_true"
                      help="Refetch OData MosaicShape into Output/_work, then update masks and JSON (and restitch only where crop windows changed). Unchanged crops are skipped."/>
            <Argument flag="-FailMissingTileset" dest="FailMissingTileset" action="store_true"
                      help="Fail instead of skipping a section whose filter has no Tileset/level."/>
            <Argument flag="-NoOverlay" dest="NoOverlay" action="store_true"
                      help="Skip QA overlay PNGs (TEM + Viking HCL hue, TEM luma). SAM2 does not use them."/>
            <Argument flag="-Cleanup" dest="Cleanup" action="store_true"
                      help="Audit and repair corrupt AnnotationCrops products. Does not ingest OData or restitch."/>
        </Arguments>

        <PythonCall Function="segmentationtraining.IngestGeometries"
                    OutputPath="#OutputPath"
                    OData="#OData"
                    Geometries="#Geometries"
                    ODataFilter="#ODataFilter"
                    Sections="#Sections"
                    IncludeOffEdge="#IncludeOffEdge"
                    Force="#Force"
                    RefreshOData="#RefreshOData"
                    Cleanup="#Cleanup"/>

        <Iterate VariableName="section_node" XPath="Block/Section" SortAttribute="Number">
            <RequireSetMembership Attribute="Number" List="#Sections"/>

            <Iterate VariableName="ChannelNode" XPath="Channel">
                <RequireMatch Attribute="Name" RegEx="#Channels"/>

                <Iterate VariableName="FilterNode" XPath="Filter">
                    <RequireMatch Attribute="Name" RegEx="#Filters"/>

                    <PythonCall Function="segmentationtraining.ExportSectionCrops"
                                FilterNode="#FilterNode"
                                section_node="#section_node"
                                OutputPath="#OutputPath"
                                Pad="#Pad"
                                Downsample="#Downsample"
                                Exporter="#Exporter"
                                MaxTexture="#MaxTexture"
                                MaxTiles="#MaxTiles"
                                MinProcessPixels="#MinProcessPixels"
                                IncludeOffEdge="#IncludeOffEdge"
                                Channels="#Channels"
                                Filters="#Filters"
                                Workers="#Workers"
                                MaskWorkers="#MaskWorkers"
                                StageTiles="#StageTiles"
                                Force="#Force"
                                RefreshOData="#RefreshOData"
                                FailMissingTileset="#FailMissingTileset"
                                NoOverlay="#NoOverlay"
                                Cleanup="#Cleanup"/>
                </Iterate>
            </Iterate>
        </Iterate>

        <PythonCall Function="segmentationtraining.CleanupAnnotationCrops"
                    OutputPath="#OutputPath"
                    Sections="#Sections"
                    Cleanup="#Cleanup"/>

        <PythonCall Module="nornir_buildmanager.operations.segmentationtraining.sam2"
                    Function="WriteGallery"
                    OutputPath="#OutputPath"/>
    </Pipeline>

    <Pipeline Name="RepairAnnotationOverlays"
              Help="Rebuild QA overlay PNGs from existing AnnotationCrops TEM images and masks."
              Description="Composites TEM crop + 1-bit masks into overlays/ without restitching tiles or ingesting OData. Always rewrites overlay PNGs. Temporary helper after overlay-style changes."
              Epilog="Example: nornir-build RepairAnnotationOverlays /data/volume -Output /data/volume/AnnotationCrops">
        <Arguments>
            <Argument flag="-Output" dest="OutputPath"
                      help="Directory that already contains images/, masks/, and optional _work watermarks"
                      required="True"/>
            <Argument flag="-Sections" dest="Sections" type="IntegerList"
                      help="Section numbers to repair. Omitted means every Z with a watermark, else every crop image."
                      required="False"/>
        </Arguments>

        <PythonCall Function="segmentationtraining.RepairAnnotationOverlays"
                    OutputPath="#OutputPath"
                    Sections="#Sections"/>
    </Pipeline>

    <Pipeline Name="ScoreAnnotationCrops"
              Help="Score AnnotationCrops masks with SAM2 on the build machine."
              Description="Writes sam2* columns on annotation_crops.sqlite using a ground-truth bbox prompt. Requires the optional SAM2 stack (not the gallery image, not the default headless install). No-op with a log line when SAM2 is not installed. Does not spawn from the gallery HTTP process."
              Epilog="Example: nornir-build ScoreAnnotationCrops /data/volume -Output /data/volume/AnnotationCrops -Checkpoint /models/sam2.pt">
        <Arguments>
            <Argument flag="-Output" dest="OutputPath"
                      help="AnnotationCrops directory that already has annotation_crops.sqlite"
                      required="True"/>
            <Argument flag="-Checkpoint" dest="Checkpoint"
                      help="SAM2 checkpoint path on the build machine"
                      required="False"/>
        </Arguments>

        <PythonCall Function="segmentationtraining.ScoreAnnotationCrops"
                    OutputPath="#OutputPath"
                    Checkpoint="#Checkpoint"/>
    </Pipeline>

    <Pipeline Name="ExportMetadata" Help="Export all meta-data to a single file">
        <Arguments>
            <Argument flag="-Output" dest="OutputPath"
                      help="Full path of output xml file, defaults to VolumeData.SingleFileBackup.xml in the Volume directory."
                      required="False"/>
        </Arguments>

        <PythonCall Function="general.SaveVolumeDataToSingleFile"
                    save_filename="#OutputPath"/>
    </Pipeline>

</Pipelines>