changeset 0:e434d9b9cd13 draft default tip

planemo upload for repository https://github.com/galaxyproject/tools-iuc/tree/main/tools/biapy/ commit 66b393a7118c81d86d0fd80780d2bd551c18f3f0
author iuc
date Thu, 09 Oct 2025 07:42:36 +0000
parents
children
files biapy.xml create_yaml.py macros.xml test-data/example.yaml test-data/im_0000.png test-data/mask_0000.png
diffstat 6 files changed, 1028 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/biapy.xml	Thu Oct 09 07:42:36 2025 +0000
@@ -0,0 +1,643 @@
+<tool id="biapy" name="Build a workflow with BiaPy" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="@PROFILE@" license="MIT">
+    <description>Accessible deep learning on bioimages</description>
+    <macros>
+        <import>macros.xml</import>
+    </macros>
+    <edam_topics>
+        <edam_topic>topic_3474</edam_topic>  <!-- Machine learning -->
+        <!-- <edam_topic>topic_3753</edam_topic>  Deep learning (not in EDAM) -->
+    </edam_topics>
+    <edam_operations>
+        <edam_operation>operation_2945</edam_operation>  <!-- Image segmentation -->
+        <edam_operation>operation_3925</edam_operation>  <!-- Object detection -->
+        <edam_operation>operation_3443</edam_operation>  <!-- Image denoising -->
+        <!-- <edam_operation>Single image super-resolution (not in EDAM)</edam_operation> -->
+        <edam_operation>operation_2946</edam_operation>  <!-- Image restoration -->
+        <!-- <edam_operation>Image-to-image translation (not in EDAM)</edam_operation>   -->
+        <!-- <edam_operation>operation_3442</edam_operation> Image classification -->
+        <!-- <edam_operation>Self-supervision learning (not in EDAM)</edam_operation>  -->
+        <edam_operation>operation_2944</edam_operation>  <!-- Image analysis -->
+    </edam_operations>
+    <expand macro="requirements" />
+    <required_files>
+        <include path="create_yaml.py" />
+    </required_files>
+    <expand macro="creators" />
+    <command detect_errors="exit_code">
+        <![CDATA[
+        set -xeuo pipefail &&
+        export OPENCV_IO_ENABLE_OPENEXR=0 &&
+
+        ## Define some useful variables
+        #set $train_raw_dir = './dataset/train/raw'
+        #set $train_gt_dir = './dataset/train/gt'
+        #set $test_raw_dir = './dataset/test/raw'
+        #set $test_gt_dir = './dataset/test/gt'
+        #set $generated_cfg = 'generated_config.yaml'
+        #set $checkpoint_dir = './output/my_experiment/checkpoints'
+        #set $checkpoint_file = $checkpoint_dir + '/checkpoint.safetensors'
+        #set $common_yaml_args = " --out_config_path '%s' --biapy_version '@TOOL_VERSION@'" % $generated_cfg
+
+        ## Decide phase and GT availability without touching missing names
+        #set $selected_phase = 'train_test'
+        #set $test_gt_avail = 'test_gt_no'
+
+        #if $mode_selection['selected_mode'] == 'create_new_cfg'
+            #set $selected_phase = $mode_selection['phase_decision']['phases']
+            #if $selected_phase in ['train_test', 'test']
+                #set $test_gt_avail = (
+                    $mode_selection['phase_decision'].get('test_sec') and
+                    $mode_selection['phase_decision']['test_sec'].get('gt_test')
+                ) and 'test_gt_yes' or 'test_gt_no'
+            #end if
+        #end if
+
+        ## Define output directory
+        mkdir -p output &&
+
+        ## Define checkpoint directory in case it is needed
+        mkdir -p '$checkpoint_dir' && 
+
+        ########## Reuse provided yaml file and update paths ##########
+        #if $mode_selection.selected_mode == 'custom_cfg':
+            #if $mode_selection.get('test_sec') and $mode_selection['test_sec'].get('gt_test'):
+                #set $test_gt_avail = 'test_gt_yes'
+            #end if
+            #set $mpath = $mode_selection.get('biapy_model_path')
+            #if $mpath and str($mpath) not in ['None', '']
+                ln -fs '$mpath' ${checkpoint_file} &&
+            #end if
+            python '$__tool_directory__/create_yaml.py'
+                --input_config_path '$mode_selection.config_path'
+                ${common_yaml_args}
+                ## Optionally override data paths with the staged dirs if user provided inputs
+                #if $selected_phase in ['train_test', 'train'] and $mode_selection.get('train_sec') and $mode_selection['train_sec'].get('raw_train')
+                    --raw_train '$train_raw_dir'
+                    #if $mode_selection['train_sec'].get('gt_train')
+                        --gt_train '$train_gt_dir'
+                    #end if
+                #end if
+                #if $selected_phase in ['train_test', 'test'] and $mode_selection.get('test_sec') and $mode_selection['test_sec'].get('raw_test')
+                    --test_raw_path '$test_raw_dir'
+                    #if $test_gt_avail == 'test_gt_yes' and $mode_selection['test_sec'].get('gt_test')
+                        --test_gt_path '$test_gt_dir'
+                    #end if
+                #end if
+                #if $mpath and str($mpath) not in ['None', '']
+                    --model '$checkpoint_file'
+                    --model_source 'biapy'
+                #end if
+        #else
+            ########## Create new yaml file ##########
+            #set $pm = $mode_selection["pretrained_model"]
+            python '$__tool_directory__/create_yaml.py'
+                --new_config
+                ${common_yaml_args}
+                --workflow '$mode_selection["workflow"]'
+                --dims '$mode_selection["dimensionality"]["is_3d"]'
+                --obj_slices '$mode_selection["dimensionality"].get("obj_slices")'
+                --obj_size '$mode_selection["obj_size"]'
+                --img_channel '$mode_selection["img_channel"]'
+                #if $pm["model_source"] == 'biapy'
+                    --model_source 'biapy'
+                #elif $pm["model_source"] == 'biapy_pretrained'
+                    --model '$checkpoint_file'
+                    --model_source 'biapy'
+                #elif $pm.get("model_source") == 'bmz_torchvision' and $pm.get("bmz_torchvision_model")
+                    #set $bt = $pm["bmz_torchvision_model"].get("bmz_or_torchvision", "")
+                    #if $bt == 'bmz'
+                        --model_source 'bmz'
+                        --model '$pm["bmz_torchvision_model"].get("bmz_model_name", "")'
+                    #else
+                        --model_source 'torchvision'
+                        --model '$pm["bmz_torchvision_model"].get("torchvision_model_name", "")'
+                    #end if
+                #end if
+                #if $selected_phase == 'train_test'
+                    --raw_train '$train_raw_dir'
+                    --gt_train '$train_gt_dir'
+                    --test_raw_path '$test_raw_dir'
+                    #if $test_gt_avail == 'test_gt_yes'
+                        --test_gt_path '$test_gt_dir'
+                    #end if
+                #elif $selected_phase == 'train'
+                    --raw_train '$train_raw_dir'
+                    --gt_train '$train_gt_dir'
+                #elif $selected_phase == 'test'
+                    --test_raw_path '$test_raw_dir'
+                    #if $test_gt_avail == 'test_gt_yes'
+                        --test_gt_path '$test_gt_dir'
+                    #end if
+                #end if
+
+            #if $pm["model_source"] == 'biapy_pretrained'
+                && ln -fs '$pm["biapy_model_path"]' ${checkpoint_file}
+            #end if
+        #end if
+        
+        &&
+
+        ## Copy the training data
+        #if $selected_phase in ['train_test', 'train']:
+            mkdir -p '$train_raw_dir' &&
+            #for $i, $image in enumerate($raw_train)
+                #set $ext = $image.ext
+                ln -s '$image' ${train_raw_dir}/training-${i}.${ext} &&
+            #end for
+            mkdir -p '$train_gt_dir' &&
+            #for $i, $image in enumerate($gt_train)
+                #set $ext = $image.ext
+                ln -s '$image' ${train_gt_dir}/training-gt-${i}.${ext} &&
+            #end for
+        #end if
+
+        ## Copy the test data
+        #if $selected_phase in ['train_test', 'test']:
+            mkdir -p '$test_raw_dir' &&
+            #for $i, $image in enumerate($raw_test)
+                #set $ext = $image.ext
+                ln -s '$image' ${test_raw_dir}/test-${i}.${ext} &&
+            #end for
+            #if $test_gt_avail == 'test_gt_yes':
+                mkdir -p '$test_gt_dir' &&
+                #for $i, $image in enumerate($gt_test)
+                    #set $ext = $image.ext
+                    ln -s '$image' ${test_gt_dir}/test-gt-${i}.${ext} &&
+                #end for
+            #end if
+        #end if
+
+        ########## Run BiaPy ##########
+        biapy 
+            --config '$generated_cfg'
+            --result_dir './output'
+            --name 'my_experiment'
+            --run_id 1
+            --gpu \${GALAXY_BIAPY_GPU_STRING:-""}
+
+        #set $outs = $selected_outputs or []
+
+        ## Copy the selected output to the correct place
+        #if $selected_phase in ['train_test', 'test']:
+
+            #if 'raw' in $outs
+                ######## 
+                ## RAW #
+                ########
+                && mkdir -p raw && {
+                    ## Instance segmentation
+                    if [ -d "output/my_experiment/results/my_experiment_1/per_image_instances" ]; then
+                        mv output/my_experiment/results/my_experiment_1/per_image_instances/* raw/;
+
+                    ## Instance segmentation
+                    elif [ -d "output/my_experiment/results/my_experiment_1/full_image_instances" ]; then
+                        mv output/my_experiment/results/my_experiment_1/full_image_instances/* raw/;
+
+                    ## Semantic segmentation
+                    elif [ -d "output/my_experiment/results/my_experiment_1/per_image_binarized" ]; then
+                        mv output/my_experiment/results/my_experiment_1/per_image_binarized/* raw/;
+                    
+                    ## Semantic segmentation
+                    elif [ -d "output/my_experiment/results/my_experiment_1/full_image_binarized" ]; then
+                        mv output/my_experiment/results/my_experiment_1/full_image_binarized/* raw/;
+
+                    ## I2I
+                    elif [ -d "output/my_experiment/results/my_experiment_1/full_image" ]; then
+                        mv output/my_experiment/results/my_experiment_1/full_image/* raw/;
+
+                    ## Detection
+                    elif [ -d "output/my_experiment/results/my_experiment_1/per_image_local_max_check" ]; then
+                        mv output/my_experiment/results/my_experiment_1/per_image_local_max_check/* raw/;
+
+                    ## Detection, Denoising, I2I, SSL, SR
+                    elif [ -d "output/my_experiment/results/my_experiment_1/per_image" ]; then
+                        mv output/my_experiment/results/my_experiment_1/per_image/* raw/;
+
+                    ## Classification
+                    elif [ -f "output/my_experiment/results/my_experiment_1/predictions.csv" ]; then
+                        mv output/my_experiment/results/my_experiment_1/predictions.csv raw/;
+                    fi; 
+                }
+            #end if
+
+            #if 'post_proc' in $outs
+                ############## 
+                ## POST-PROC #
+                ##############
+                && mkdir -p post_proc && {
+                    ## Instance segmentation
+                    if [ -d "output/my_experiment/results/my_experiment_1/per_image_post_processing" ]; then
+                        mv output/my_experiment/results/my_experiment_1/per_image_post_processing/* post_proc/;
+
+                    ## Instance segmentation
+                    elif [ -d "output/my_experiment/results/my_experiment_1/full_image_post_processing" ]; then
+                        mv output/my_experiment/results/my_experiment_1/full_image_post_processing/* post_proc/;
+
+                    ## Detection
+                    elif [ -d "output/my_experiment/results/my_experiment_1/per_image_local_max_check_post_proc" ]; then
+                        mv output/my_experiment/results/my_experiment_1/per_image_local_max_check_post_proc/* post_proc/;
+                    fi;
+                }
+            #end if
+
+            #if 'metrics' in $outs and $test_gt_avail == "test_gt_yes":
+                && mkdir -p metrics && 
+                mv output/my_experiment/results/my_experiment_1/test_results_metrics.csv metrics/ 2>/dev/null || true
+            #end if 
+        #end if
+        #if $selected_phase in ['train_test', 'train']:
+            #if 'tcharts' in $outs
+                && mkdir -p train_charts
+            #end if
+            #if 'tlogs' in $outs
+                && mkdir -p train_logs
+            #end if
+        #end if
+        #if 'checkpoint' in $outs
+            && mkdir -p checkpoints
+        #end if
+        ]]>
+    </command>
+
+    <inputs>
+        <conditional name="mode_selection">
+            <param name="selected_mode" type="select" label="Do you have a configuration file?">
+                <option value="custom_cfg" selected="true">
+                    Yes, I already have one and I want to run BiaPy directly.
+                </option>
+                <option value="create_new_cfg">
+                    No, I want to create one from scratch.
+                </option>
+            </param>
+            <when value="custom_cfg">
+                <param name="config_path" type="data" format="yaml" optional="false" label="Select a configuration file" help="Input configuration file"/>
+                <param name="biapy_model_path" type="data" format="safetensors" optional="true" label="Select the model checkpoint (if needed)" help="Path to a pre-trained model checkpoint (.safetensors) generated by BiaPy. Use this only if 'MODEL.LOAD_CHECKPOINT' is set to 'True' in your configuration."/>
+                <section name="train_sec" title="If train is enabled select the training images"> 
+                    <!-- Q9 -->
+                    <expand macro="train_raw_param_opt"/>
+                    <!-- Q10 -->
+                    <expand macro="train_gt_param_opt"/>
+                </section> 
+                <section name="test_sec" title="If test is enabled select the test images"> 
+                    <!-- Q11 -->
+                    <expand macro="test_raw_param_opt"/>
+                    <!-- Q13 -->
+                    <expand macro="test_gt_param_optional"/>
+                </section> 
+            </when>
+            <when value="create_new_cfg">
+                <conditional name="dimensionality">
+                    <!-- Q1 -->
+                    <param name="is_3d" type="select" label="Are your images in 3D?" help="Select the type of images you will use: 'No' = 2D images (e.g. (512, 1024, 2)); 'Yes' = 3D images (e.g. (400, 400, 50, 1)); 'No, but output as 3D stack' = process 2D images and combine them into a 3D stack after inference, useful if 2D slices form a larger 3D volume.">
+                        <option value="2d" selected="true">No</option>
+                        <option value="3d">Yes</option>
+                        <option value="2d_stack">No, but I would like to have a 3D stack output</option>
+                    </param>
+                    <when value="3d">
+                        <!-- Q7 -->
+                        <param name="obj_slices" type="select" label="How many slices can an object be represented in?" help="This parameter defines the approximate size of the objects of interest along the Z axis. For example, in nucleus segmentation it refers to how many slices a nucleus spans in the stack; knowing this helps set an appropriate value.">
+                            <option value="1-5" selected="true">1-5 slices</option>
+                            <option value="5-10">5-10 slices</option>
+                            <option value="10-20">10-20 slices</option>
+                            <option value="20-60">20-60 slices</option>
+                            <option value="60+">More than 60 slices</option>
+                        </param>
+                    </when>
+                    <when value="2d"/>
+                    <when value="2d_stack"/>
+                </conditional>
+                <!-- Q6 -->
+                <param name="obj_size" type="select" label="What is the average object width/height in pixels?" help="This parameter defines the approximate size of the objects of interest in your images; for example, in nucleus segmentation it refers to the typical size of nuclei, and only a rough estimation is needed.">
+                    <option value="0-25" selected="true">0-25 px</option>
+                    <option value="25-100">25-100 px</option>
+                    <option value="100-200">100-200 px</option>
+                    <option value="200-500">200-500 px</option>
+                    <option value="500+">More than 500 px</option>
+                </param>
+                <param name="img_channel" type="integer" value="1" min="1" max="10" label="Input the number of channels of the images" help="This parameter specifies the number of channels in your images; for example, use 3 for RGB images or 1 for grayscale, so the model can correctly interpret the input data."/>
+
+                <!-- Q2 -->
+                <param name="workflow" type="select" label="Do you want to:" help="Select a workflow to run; see https://biapy.readthedocs.io/en/latest/get_started/select_workflow.html for further explanation.">
+                    <option value="semantic" selected="true">Generate masks of different (or just one) objects/regions within the image</option>
+                    <option value="instance">Generate masks for each object in the image</option>
+                    <option value="detection">Identify and count roughly circular objects in the images, without needing an exact outline around each one</option>
+                    <option value="denoising">Clean noisy images</option>
+                    <option value="sr">Upsample images into higher resolution</option>
+                    <option value="cls">Assign a label to each image</option>
+                    <option value="sr2">Restore a degraded image</option>
+                    <option value="i2i">Generate new images based on an input one</option>
+                </param>
+
+                <conditional name="pretrained_model">
+                    <!-- Q3 -->
+                    <param name="model_source" type="select" label="Do you want to use a pre-trained model?" help="This parameter defines how the deep learning model will be built: (1) build from scratch based on the workflow and image size, (2) load a model previously trained in BiaPy (checkpoint .safetensors in the results/checkpoints folder), or (3) load a pre-trained model from external sources such as the BioImage Model Zoo or Torchvision; training requires labeled data, but pre-trained models can save time and improve results if they match your task.">
+                        <option value="biapy" selected="true">No, I want to build a model from scratch</option>
+                        <option value="biapy_pretrained">Yes, I have a model previously trained in BiaPy</option>
+                        <option value="bmz_torchvision">Yes, I want to check if there is a pre-trained model I can use</option>
+                    </param>
+                    <when value="biapy_pretrained">
+                        <!-- Q4 -->
+                        <param name="biapy_model_path" type="data" format="data" optional="false" label="Select the model trained with BiaPy before" help="Select a pre-trained BiaPy model checkpoint (.safetensors) to use for inference or to resume training. Checkpoints are typically generated by previous BiaPy training runs and appear in your Galaxy history as output datasets."/>
+                    </when>
+                    <when value="bmz_torchvision">
+                        <!-- Q5 -->
+                        <conditional name="bmz_torchvision_model">
+                            <param name="bmz_or_torchvision" type="select" label="Which is the source of the model?" help="Enter the source of the model, whether if it is available through the BioImage Model Zoo or TorchVision">
+                                <option value="bmz" selected="true">BioImage Model Zoo</option>
+                                <option value="torchvision">TorchVision</option>
+                            </param>
+                            <when value="bmz">
+                                <param name="bmz_model_name" type="text" optional="false" value="sensible-cat" label="BioImage Model Zoo model name" help="Enter the name of a pre-trained model from the BioImage Model Zoo (https://bioimage.io/#/models); filter by the BiaPy icon and ensure the model matches your dimensionality (2D/3D) and task (e.g. semantic segmentation).">
+                                    <validator type="regex" message="Use an adjective-noun pattern like 'sensible-cat' (letters and dashes only).">^[A-Za-z]+(?:-[A-Za-z]+)+$</validator>
+                                </param>
+                            </when>
+                            <when value="torchvision">
+                                <param name="torchvision_model_name" type="text" optional="false" label="TorchVision model name" help="Enter the name of a pre-trained model from TorchVision (see https://docs.pytorch.org/vision/0.21/models.html#general-information-on-pre-trained-weights), e.g. 'alexnet' for classification.">
+                                    <validator type="regex" message="Only letters, digits, underscores and dots; must start with a letter.">^[a-zA-Z][a-zA-Z0-9_\.]*$</validator>
+                                </param>
+                            </when>
+                        </conditional>
+                    </when>  
+                    <when value="biapy"/>
+                </conditional>
+                    
+                <conditional name="phase_decision">
+                    <!-- Q8 -->
+                    <param name="phases" type="select" label="What do you want to do?" help="Select which workflow phases to run: training (fit the model to labeled data) and/or testing (inference/prediction on new images using the trained model).">
+                        <option value="train_test" selected="true">Train and test a model</option>
+                        <option value="train">Train a model</option>
+                        <option value="test">Test a model</option>
+                    </param>
+                    <when value="train_test">
+                        <section name="train_sec" title="Train data" expanded="True"> 
+                            <!-- Q9 -->
+                            <expand macro="train_raw_param"/>
+                            <!-- Q10 -->
+                            <expand macro="train_gt_param"/>
+                        </section> 
+                        <section name="test_sec" title="Test data" expanded="True"> 
+                            <!-- Q11 -->
+                            <expand macro="test_raw_param"/>
+                            <!-- Optional test GT -->
+                            <expand macro="test_gt_param_optional"/>
+                        </section> 
+                    </when>
+
+                    <when value="train">
+                        <section name="train_sec" title="Train data" expanded="True"> 
+                            <!-- Q9 --> 
+                            <expand macro="train_raw_param"/>
+                            <!-- Q10 -->
+                            <expand macro="train_gt_param"/>
+                        </section> 
+                    </when>
+
+                    <when value="test">
+                        <section name="test_sec" title="Test data" expanded="True"> 
+                            <!-- Q11 -->
+                            <expand macro="test_raw_param"/>
+                            <!-- Optional test GT -->
+                            <expand macro="test_gt_param_optional"/>
+                        </section> 
+                    </when>
+                </conditional>
+            </when>
+        </conditional>
+        <param name="selected_outputs" type="select" display="checkboxes" multiple="true" label="Select the outputs" help="Select which outputs to generate from running BiaPy (e.g. predictions, metrics, logs, or intermediate results).">
+            <option value="raw" selected="true">Test predictions (if exist)</option>
+            <option value="post_proc">Post-processed test predictions (if exist)</option>
+            <option value="metrics">Evaluation metrics (if exist, on test data)</option>
+            <option value="tcharts">Training charts (if exist)</option>
+            <option value="tlogs">Training logs (if exist)</option>
+            <option value="checkpoint">Model checkpoint</option>
+        </param>
+
+    </inputs>
+
+    <outputs>
+        <collection name="predictions_raw" type="list" label="${tool.name} on ${on_string}: Test predictions">
+            <discover_datasets directory="raw" pattern="(?P&lt;designation&gt;.+)\.tif" format="tif" recurse="false"/>
+            <discover_datasets directory="raw" pattern="(?P&lt;designation&gt;.+)\.tiff" format="tiff" recurse="false"/>
+            <discover_datasets directory="raw" pattern="(?P&lt;designation&gt;.+)\.csv" format="csv" recurse="false"/>
+            <discover_datasets directory="raw" pattern="(?P&lt;designation&gt;.+)\.h5"  format="h5"  recurse="false"/>
+            <filter><![CDATA[
+                'raw' in selected_outputs and (
+                    (mode_selection['selected_mode'] == 'create_new_cfg' and
+                    mode_selection['phase_decision']['phases'] in ['test','train_test'])
+                    or
+                    (mode_selection['selected_mode'] == 'custom_cfg')
+                )
+            ]]></filter>
+        </collection>
+
+        <collection name="predictions_post_proc" type="list" label="${tool.name} on ${on_string}: Post-processed test predictions">
+            <discover_datasets directory="post_proc" pattern="(?P&lt;designation&gt;.+)\.tif"  format="tif" recurse="false" />
+            <discover_datasets directory="post_proc" pattern="(?P&lt;designation&gt;.+)\.tiff" format="tiff" recurse="false"/>
+            <discover_datasets directory="post_proc" pattern="(?P&lt;designation&gt;.+)\.csv" format="csv" recurse="false"/>
+            <discover_datasets directory="post_proc" pattern="(?P&lt;designation&gt;.+)\.h5"   format="h5"   recurse="false" />
+            <filter><![CDATA[
+                'post_proc' in selected_outputs and (
+                    (mode_selection['selected_mode'] == 'create_new_cfg' and
+                    mode_selection['phase_decision']['phases'] in ['test','train_test'])
+                    or
+                    (mode_selection['selected_mode'] == 'custom_cfg')
+                )
+            ]]>
+            </filter>
+        </collection>
+
+        <collection name="test_metrics" type="list" label="${tool.name} on ${on_string}: Test metrics">
+            <discover_datasets directory="metrics" pattern="(?P&lt;designation&gt;.+)\.csv" format="csv" recurse="false" />
+            <filter><![CDATA[
+                'metrics' in selected_outputs and (
+                    (mode_selection['selected_mode'] == 'create_new_cfg' and
+                    mode_selection['phase_decision']['phases'] in ['test','train_test'] and
+                    mode_selection['phase_decision'].get('test_sec') and
+                    mode_selection['phase_decision']['test_sec'].get('gt_test'))
+                    or
+                    (mode_selection['selected_mode'] == 'custom_cfg')
+                )
+            ]]>
+            </filter>
+        </collection>
+        
+        <collection name="train_charts" type="list" label="${tool.name} on ${on_string}: Training charts">
+            <discover_datasets directory="output/my_experiment/results/my_experiment_1/charts" pattern="(?P&lt;designation&gt;.+)\.png" format="png" recurse="false" />
+            <filter><![CDATA[
+                'tcharts' in selected_outputs and (
+                    (mode_selection['selected_mode'] == 'create_new_cfg' and
+                    mode_selection['phase_decision']['phases'] in ['train','train_test'])
+                    or
+                    (mode_selection['selected_mode'] == 'custom_cfg')
+                )
+            ]]>
+            </filter> 
+        </collection>
+
+        <collection name="train_logs" type="list" label="${tool.name} on ${on_string}: Training logs">
+            <discover_datasets directory="output/my_experiment/train_logs" pattern="(?P&lt;designation&gt;.+)\.txt" format="txt" recurse="false" />
+            <filter><![CDATA[
+                'tlogs' in selected_outputs and (
+                    (mode_selection['selected_mode'] == 'create_new_cfg' and
+                    mode_selection['phase_decision']['phases'] in ['train','train_test'])
+                    or
+                    (mode_selection['selected_mode'] == 'custom_cfg')
+                )
+            ]]>
+            </filter>
+        </collection>
+
+        <collection name="model_checkpoint" type="list" label="${tool.name} on ${on_string}: Model checkpoint">
+            <discover_datasets pattern="(?P&lt;designation&gt;.+)\.safetensors" format="data" directory="output/my_experiment/checkpoints" recurse="false" />
+            <filter><![CDATA[
+                'checkpoint' in selected_outputs
+            ]]>
+            </filter>
+        </collection>
+    </outputs>
+    <tests>
+        <!-- test1: test with custom cfg -->
+        <test expect_num_outputs="2">
+            <!-- Choose the conditional branch -->
+            <param name="mode_selection|selected_mode" value="custom_cfg"/>
+
+            <param name="mode_selection|config_path" value="example.yaml"/>
+            <param name="mode_selection|test_sec|raw_test" value="im_0000.png"/>
+            <param name="mode_selection|test_sec|gt_test" value="mask_0000.png"/>
+            <param name="selected_outputs" value="raw,metrics"/>
+            <output_collection name="predictions_raw" type="list" count="1" />
+            <output_collection name="test_metrics"     type="list" count="1"/>
+            <assert_command>
+                <has_text text="--config 'generated_config.yaml'"/>
+                <has_text text="--result_dir './output'"/>
+                <has_text text="--name 'my_experiment'"/>
+                <has_text text="--run_id 1"/>
+            </assert_command>
+        </test>
+
+        <!-- test2: create_new_cfg using a model from the zoo -->
+        <test expect_num_outputs="2">
+            <!-- Top-level branch -->
+            <param name="mode_selection|selected_mode" value="create_new_cfg" />
+
+            <!-- Dimensionality (Q1) -->
+            <param name="mode_selection|dimensionality|is_3d" value="2d" />
+
+            <!-- Object size (Q6) & channels -->
+            <param name="mode_selection|obj_size" value="0-25" />
+            <param name="mode_selection|img_channel" value="1" />
+
+            <!-- Workflow (Q2) -->
+            <param name="mode_selection|workflow" value="semantic" />
+
+            <!-- Pretrained model (Q3, Q5) -->
+            <param name="mode_selection|pretrained_model|model_source" value="bmz_torchvision" />
+            <param name="mode_selection|pretrained_model|bmz_torchvision_model|bmz_or_torchvision" value="bmz" />
+            <param name="mode_selection|pretrained_model|bmz_torchvision_model|bmz_model_name" value="sensible-cat" />
+
+            <!-- Phase decision (Q8) -->
+            <param name="mode_selection|phase_decision|phases" value="test" />
+
+            <!-- Test data (Q11/Q12/Q13) -->
+            <param name="mode_selection|phase_decision|test_sec|raw_test" value="im_0000.png" />
+            <param name="mode_selection|phase_decision|test_sec|gt_test" value="mask_0000.png" />
+
+            <!-- Outputs to check -->
+            <param name="selected_outputs" value="raw,metrics" />
+            <output_collection name="predictions_raw" type="list" count="1" />
+            <output_collection name="test_metrics"     type="list" count="1"/>
+            <assert_command>
+                <has_text text="--config 'generated_config.yaml'"/>
+                <has_text text="--result_dir './output'"/>
+                <has_text text="--name 'my_experiment'"/>
+                <has_text text="--run_id 1"/>
+            </assert_command>
+        </test>
+
+        <!-- test3: create_new_cfg to use a denoising workflow -->
+        <test expect_num_outputs="1">
+            <!-- Top-level branch -->
+            <param name="mode_selection|selected_mode" value="create_new_cfg" />
+
+            <!-- Dimensionality (Q1) -->
+            <param name="mode_selection|dimensionality|is_3d" value="2d" />
+
+            <!-- Object size (Q6) & channels -->
+            <param name="mode_selection|obj_size" value="25-100" />
+            <param name="mode_selection|img_channel" value="1" />
+
+            <!-- Workflow (Q2) -->
+            <param name="mode_selection|workflow" value="denoising" />
+
+            <!-- Model from scratch (Q3) -->
+            <param name="mode_selection|pretrained_model|model_source" value="biapy" />
+
+            <!-- Phase decision (Q8) -->
+            <param name="mode_selection|phase_decision|phases" value="test" />
+
+            <!-- Test data (Q11) -->
+            <param name="mode_selection|phase_decision|test_sec|raw_test" value="im_0000.png" />
+
+            <!-- Outputs to check -->
+            <param name="selected_outputs" value="raw" />
+            <output_collection name="predictions_raw" type="list" count="1" />
+            <assert_command>
+                <has_text text="--config 'generated_config.yaml'"/>
+                <has_text text="--result_dir './output'"/>
+                <has_text text="--name 'my_experiment'"/>
+                <has_text text="--run_id 1"/>
+            </assert_command>
+        </test>
+
+    </tests>
+<help><![CDATA[
+**What it does**
+
+This tool runs a BiaPy workflow for image analysis using deep learning models. BiaPy is a bioimage analysis pipeline designed to simplify training, prediction, and evaluation across a variety of tasks such as image segmentation, classification, denoising, and more.
+
+---
+
+**Usage**
+
+There are two main usage modes for this tool:
+
+1. **Using a custom configuration file (YAML)**  
+   If you already have a BiaPy configuration file, you can upload it directly. The tool will use this configuration without further modification to run the specified BiaPy workflow.
+
+2. **Constructing a configuration interactively**  
+   If you do not have a YAML configuration file, the tool can help you build one by asking a set of guided questions. This includes settings like:
+   - Task type (e.g., segmentation, classification)
+   - Model architecture
+   - Input/output patch sizes
+   - Data paths and formats
+   - Training parameters (epochs, batch size, etc.)
+
+Once these options are specified, the tool generates a valid BiaPy YAML config and proceeds to execute the workflow.
+
+---
+
+**Output**
+
+The output depends on the chosen workflow and may include:
+- Trained model weights
+- Prediction results
+- Evaluation metrics (if ground truth is available)
+- Log files and training history
+
+---
+
+**Tips**
+
+- For best results, ensure that your input data format matches what BiaPy expects. Refer to the [BiaPy documentation](https://biapy.readthedocs.io/en/latest/) for details.
+- Use the "interactive mode" if you're new to BiaPy and want guidance on configuration.
+- Advanced users with pre-tuned configs may prefer uploading a YAML directly for faster execution.
+
+---
+
+**References**
+- BiaPy landing page: https://biapyx.github.io/
+- BiaPy documentation: https://biapy.readthedocs.io/
+- Galaxy Tool Development: https://galaxyproject.org/tools/
+]]></help>
+    <expand macro="citations"/>
+</tool>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/create_yaml.py	Thu Oct 09 07:42:36 2025 +0000
@@ -0,0 +1,262 @@
+import argparse
+
+import requests
+import yaml
+
+
+def download_yaml_template(workflow, dims, biapy_version=""):
+    """
+    Download a YAML template for a specific workflow and dimensions.
+
+    Parameters:
+        workflow (str): The workflow type.
+        dims (str): The dimensions (e.g., 2d, 3d).
+        biapy_version (str): The BiaPy version to use.
+
+    Returns:
+        dict: The YAML template as a dictionary.
+    """
+    template_dir_map = {
+        "SEMANTIC_SEG": "semantic_segmentation",
+        "INSTANCE_SEG": "instance_segmentation",
+        "DETECTION": "detection",
+        "DENOISING": "denoising",
+        "SUPER_RESOLUTION": "super-resolution",
+        "CLASSIFICATION": "classification",
+        "SELF_SUPERVISED": "self-supervised",
+        "IMAGE_TO_IMAGE": "image-to-image",
+    }
+    template_name = (
+        template_dir_map[workflow]
+        + "/"
+        + dims.lower() + "_" + template_dir_map[workflow] + ".yaml"
+    )
+
+    url = (
+        f"https://raw.githubusercontent.com/BiaPyX/BiaPy/"
+        f"refs/tags/v{biapy_version}/templates/{template_name}"
+    )
+    print(f"Downloading YAML template from {url}")
+    response = requests.get(url)
+    if response.status_code != 200:
+        raise RuntimeError(
+            f"Failed to download YAML template: {response.status_code}"
+        )
+    return yaml.safe_load(response.text)
+
+
+def tuple_to_list(obj):
+    """Convert tuples to lists recursively."""
+    if isinstance(obj, tuple):
+        return list(obj)
+    if isinstance(obj, dict):
+        return {k: tuple_to_list(v) for k, v in obj.items()}
+    if isinstance(obj, list):
+        return [tuple_to_list(v) for v in obj]
+    return obj
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Generate a YAML configuration from given arguments."
+    )
+    parser.add_argument(
+        '--input_config_path', default='', type=str,
+        help="Input configuration file to reuse"
+    )
+    parser.add_argument(
+        '--new_config', action='store_true',
+        help="Whether to create a new config or reuse an existing one."
+    )
+    parser.add_argument(
+        '--out_config_path', required=True, type=str,
+        help="Path to save the generated YAML configuration."
+    )
+    parser.add_argument(
+        '--workflow', default='semantic', type=str,
+        choices=['semantic', 'instance', 'detection', 'denoising',
+                 'sr', 'cls', 'sr2', 'i2i'],
+    )
+    parser.add_argument(
+        '--dims', default='2d', type=str,
+        choices=['2d_stack', '2d', '3d'],
+        help="Number of dimensions for the problem"
+    )
+    parser.add_argument(
+        '--obj_slices', default='', type=str,
+        choices=['', '1-5', '5-10', '10-20', '20-60', '60+'],
+        help="Number of slices for the objects in the images"
+    )
+    parser.add_argument(
+        '--obj_size', default='0-25', type=str,
+        choices=['0-25', '25-100', '100-200', '200-500', '500+'],
+        help="Size of the objects in the images"
+    )
+    parser.add_argument(
+        '--img_channel', default=1, type=int,
+        help="Number of channels in the input images"
+    )
+    parser.add_argument(
+        '--model_source', default='biapy',
+        choices=['biapy', 'bmz', 'torchvision'],
+        help="Source of the model."
+    )
+    parser.add_argument(
+        '--model', default='', type=str,
+        help=("Path to the model file if using a pre-trained model "
+              "from BiaPy or name of the model within BioImage "
+              "Model Zoo or TorchVision.")
+    )
+    parser.add_argument(
+        '--raw_train', default='', type=str,
+        help="Path to the training raw data."
+    )
+    parser.add_argument(
+        '--gt_train', default='', type=str,
+        help="Path to the training ground truth data."
+    )
+    parser.add_argument(
+        '--test_raw_path', default='', type=str,
+        help="Path to the testing raw data."
+    )
+    parser.add_argument(
+        '--test_gt_path', default='', type=str,
+        help="Path to the testing ground truth data."
+    )
+    parser.add_argument(
+        '--biapy_version', default='', type=str,
+        help="BiaPy version to use."
+    )
+
+    args = parser.parse_args()
+
+    if args.new_config:
+        workflow_map = {
+            "semantic": "SEMANTIC_SEG",
+            "instance": "INSTANCE_SEG",
+            "detection": "DETECTION",
+            "denoising": "DENOISING",
+            "sr": "SUPER_RESOLUTION",
+            "cls": "CLASSIFICATION",
+            "sr2": "SELF_SUPERVISED",
+            "i2i": "IMAGE_TO_IMAGE",
+        }
+        workflow_type = workflow_map[args.workflow]
+
+        if args.dims == "2d_stack":
+            ndim = "2D"
+            as_stack = True
+        elif args.dims == "2d":
+            ndim = "2D"
+            as_stack = True
+        elif args.dims == "3d":
+            ndim = "3D"
+            as_stack = False
+
+        config = download_yaml_template(
+            workflow_type, ndim, biapy_version=args.biapy_version
+        )
+
+        config["PROBLEM"]["TYPE"] = workflow_type
+        config["PROBLEM"]["NDIM"] = ndim
+        config["TEST"]["ANALIZE_2D_IMGS_AS_3D_STACK"] = as_stack
+
+        if args.model_source == "biapy":
+            config["MODEL"]["SOURCE"] = "biapy"
+            if args.model:
+                config["MODEL"]["LOAD_CHECKPOINT"] = True
+                config["MODEL"]["LOAD_MODEL_FROM_CHECKPOINT"] = True
+                config.setdefault("PATHS", {})
+                config["PATHS"]["CHECKPOINT_FILE"] = args.model
+            else:
+                config["MODEL"]["LOAD_CHECKPOINT"] = False
+                config["MODEL"]["LOAD_MODEL_FROM_CHECKPOINT"] = False
+        elif args.model_source == "bmz":
+            config["MODEL"]["SOURCE"] = "bmz"
+            config["MODEL"]["LOAD_CHECKPOINT"] = False
+            config["MODEL"]["LOAD_MODEL_FROM_CHECKPOINT"] = False
+            config.setdefault("MODEL", {}).setdefault("BMZ", {})
+            config["MODEL"]["BMZ"]["SOURCE_MODEL_ID"] = args.model
+        elif args.model_source == "torchvision":
+            config["MODEL"]["SOURCE"] = "torchvision"
+            config["MODEL"]["LOAD_CHECKPOINT"] = False
+            config["MODEL"]["LOAD_MODEL_FROM_CHECKPOINT"] = False
+            config["MODEL"]["TORCHVISION_MODEL_NAME"] = args.model
+
+        obj_size_map = {
+            "0-25": (256, 256),
+            "25-100": (256, 256),
+            "100-200": (512, 512),
+            "200-500": (512, 512),
+            "500+": (1024, 1024),
+        }
+        obj_size = obj_size_map[args.obj_size]
+
+        obj_slices_map = {
+            "": -1,
+            "1-5": 5,
+            "5-10": 10,
+            "10-20": 20,
+            "20-60": 40,
+            "60+": 80,
+        }
+        obj_slices = obj_slices_map[args.obj_slices]
+        if config["PROBLEM"]["NDIM"] == "2D":
+            config["DATA"]["PATCH_SIZE"] = obj_size + (args.img_channel,)
+        else:
+            assert obj_slices != -1, (
+                "For 3D problems, obj_slices must be specified."
+            )
+            config["DATA"]["PATCH_SIZE"] = (
+                (obj_slices,) + obj_size + (args.img_channel,)
+            )
+        config["DATA"]["PATCH_SIZE"] = str(config["DATA"]["PATCH_SIZE"])
+    else:
+        assert args.input_config_path, (
+            "Input configuration path must be specified when not "
+            "creating a new config."
+        )
+        with open(args.input_config_path, 'r', encoding='utf-8') as f:
+            config = yaml.safe_load(f)
+
+        if args.model:
+            config["MODEL"]["SOURCE"] = "biapy"
+            config["MODEL"]["LOAD_CHECKPOINT"] = True
+            config["MODEL"]["LOAD_MODEL_FROM_CHECKPOINT"] = True
+            config.setdefault("PATHS", {})
+            config["PATHS"]["CHECKPOINT_FILE"] = args.model
+        else:
+            config["MODEL"]["LOAD_CHECKPOINT"] = False
+            config["MODEL"]["LOAD_MODEL_FROM_CHECKPOINT"] = False
+
+    if args.raw_train:
+        config["TRAIN"]["ENABLE"] = True
+        config["DATA"]["TRAIN"]["PATH"] = args.raw_train
+        config["DATA"]["TRAIN"]["GT_PATH"] = args.gt_train
+    else:
+        config["TRAIN"]["ENABLE"] = False
+
+    if args.test_raw_path:
+        config["TEST"]["ENABLE"] = True
+        config["DATA"]["TEST"]["PATH"] = args.test_raw_path
+        if args.test_gt_path:
+            config["DATA"]["TEST"]["LOAD_GT"] = True
+            config["DATA"]["TEST"]["GT_PATH"] = args.test_gt_path
+        else:
+            config["DATA"]["TEST"]["LOAD_GT"] = False
+    else:
+        config["TEST"]["ENABLE"] = False
+
+    # Always use safetensors in Galaxy
+    config["MODEL"]["OUT_CHECKPOINT_FORMAT"] = "safetensors"
+
+    config = tuple_to_list(config)
+
+    with open(args.out_config_path, 'w', encoding='utf-8') as f:
+        yaml.dump(config, f, default_flow_style=False)
+
+    print(f"YAML configuration written to {args.out_config_path}")
+
+
+if __name__ == "__main__":
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/macros.xml	Thu Oct 09 07:42:36 2025 +0000
@@ -0,0 +1,73 @@
+<macros>
+    <token name="@TOOL_VERSION@">3.6.5</token>
+    <token name="@VERSION_SUFFIX@">0</token>
+    <token name="@PROFILE@">25.0</token>
+
+    <xml name="requirements">
+        <requirements>
+            <requirement type="package" version="3.10.18">python</requirement>
+            <requirement type="package" version="@TOOL_VERSION@">biapy</requirement>
+            <requirement type="package" version="4.10.0">opencv</requirement>
+            <requirement type="package" version="3.3.3">openexr</requirement>
+            <requirement type="package" version="0.5.3">safetensors</requirement>
+        </requirements>
+    </xml>
+    <xml name="creators">
+        <creator>
+            <person givenName="Daniel" familyName="Franco" url="https://danifranco.github.io/" identifier="https://orcid.org/0000-0002-1109-110X" />
+        </creator>
+    </xml>
+    <xml name="citations">
+        <citations>
+            <citation type="doi">10.1038/s41592-025-02699-y</citation>
+            <yield/>
+        </citations>
+    </xml>
+
+    <!-- Q9 -->
+    <xml name="train_raw_param">
+        <param name="raw_train" type="data" format="tiff,tif,png,jpg,h5" multiple="true" label="Specify the training raw images" help="Select the training images; all must have the same number of channels, and each channel should contain the same type of information (see https://biapy.readthedocs.io/en/latest/get_started/how_it_works.html for details)."/>
+    </xml>
+
+    <xml name="train_raw_param_opt">
+        <param name="raw_train" type="data" optional="true" format="tiff,tif,png,jpg,h5" multiple="true" label="Specify the training raw images" help="Select the training images; all must have the same number of channels, and each channel should contain the same type of information (see https://biapy.readthedocs.io/en/latest/get_started/how_it_works.html for details)."/>
+    </xml>
+
+    <!-- Q10 -->
+    <xml name="train_gt_param">
+        <param name="gt_train" type="data" format="tiff,tif,png,jpg,h5,csv,txt" multiple="true" label="Specify the training ground truth (target) images" help="Select the ground truth (target) data for training; the expected format depends on the workflow (e.g. masks for semantic/instance segmentation, centroid files for detection, high-res images for super-resolution, paired images for image-to-image); see https://biapy.readthedocs.io/en/latest/get_started/select_workflow.html for details."/>
+    </xml>
+    <xml name="train_gt_param_opt">
+        <param name="gt_train" type="data"  optional="true" format="tiff,tif,png,jpg,h5,csv,txt" multiple="true" label="Specify the training ground truth (target) images" help="Select the ground truth (target) data for training; the expected format depends on the workflow (e.g. masks for semantic/instance segmentation, centroid files for detection, high-res images for super-resolution, paired images for image-to-image); see https://biapy.readthedocs.io/en/latest/get_started/select_workflow.html for details."/>
+    </xml>
+
+    <!-- Q11 -->
+    <xml name="test_raw_param">
+        <param name="raw_test" type="data" format="tiff,tif,png,jpg,h5" multiple="true" label="Specify the test raw images" help="Select the test images; all must have the same number of channels, and each channel should contain the same type of information to avoid confusing the model."/>
+    </xml>
+    <xml name="test_raw_param_opt">
+        <param name="raw_test" type="data" format="tiff,tif,png,jpg,h5" optional="true" multiple="true" label="Specify the test raw images" help="Select the test images; all must have the same number of channels, and each channel should contain the same type of information to avoid confusing the model."/>
+    </xml>
+
+    <!-- Q13: REQUIRED test GT -->
+    <xml name="test_gt_param_required">
+        <param name="gt_test"
+            type="data"
+            format="tiff,tif,png,jpg,h5,csv,txt"
+            multiple="true"
+            label="Specify the test ground truth/target images"
+            help="Select the ground truth (target) data for testing; the expected format depends on the workflow (e.g. masks for semantic/instance segmentation, centroid files for detection, high-res images for super-resolution, paired images for image-to-image); see https://biapy.readthedocs.io/en/latest/get_started/select_workflow.html for details."
+            optional="false" />
+    </xml>
+
+    <!-- Q13: OPTIONAL test GT -->
+    <xml name="test_gt_param_optional">
+        <param name="gt_test"
+            type="data"
+            format="tiff,tif,png,jpg,h5,csv,txt"
+            multiple="true"
+            label="Specify the test ground truth/target images (optional)"
+            help="Select the ground truth (target) data for testing; the expected format depends on the workflow (e.g. masks for semantic/instance segmentation, centroid files for detection, high-res images for super-resolution, paired images for image-to-image). If provided, BiaPy will compute workflow-specific evaluation metrics on the test set. See https://biapy.readthedocs.io/en/latest/get_started/select_workflow.html for details."
+            optional="true" />
+    </xml>
+</macros>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/example.yaml	Thu Oct 09 07:42:36 2025 +0000
@@ -0,0 +1,50 @@
+# BiaPy version: 3.6.2
+
+SYSTEM:
+    NUM_CPUS: -1
+
+PROBLEM:
+    TYPE: SEMANTIC_SEG
+    NDIM: 2D
+  
+DATA: 
+    PATCH_SIZE: (256, 256, 1)
+    TRAIN:                                                                                                              
+        PATH: /path/to/data
+        GT_PATH: /path/to/data
+        IN_MEMORY: True
+    VAL:
+        SPLIT_TRAIN: 0.1
+    TEST:                                                                                                               
+        PATH: /path/to/data
+        GT_PATH: /path/to/data
+        IN_MEMORY: True
+        LOAD_GT: True
+        PADDING: (32,32)
+
+AUGMENTOR:
+    ENABLE: True
+    AUG_SAMPLES: False
+    RANDOM_ROT: True
+    VFLIP: True
+    HFLIP: True
+
+MODEL:
+    ARCHITECTURE: unet
+    FEATURE_MAPS: [16, 32, 64, 128, 256]
+    LOAD_CHECKPOINT: False
+
+TRAIN:
+    ENABLE: False
+    OPTIMIZER: ADAMW
+    LR: 1.E-3
+    BATCH_SIZE: 6
+    EPOCHS: 20
+    PATIENCE: 20
+    LR_SCHEDULER:
+        NAME: 'onecycle'    # use one-cycle learning rate scheduler
+  
+TEST:
+    ENABLE: True
+    AUGMENTATION: False
+    FULL_IMG: False
Binary file test-data/im_0000.png has changed
Binary file test-data/mask_0000.png has changed