#!/usr/bin/env bash

# from_h5mu_or_h5ad_to_tiledb processing-workflows-quantile-filtering
# 
# This wrapper script is auto-generated by viash 0.9.7 and is thus a derivative
# work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data
# Intuitive.
# 
# The component may contain files which fall under a different license. The
# authors of this component should specify the license in the header of such
# files, or include a separate license file detailing the licenses of all included
# files.
# 
# Component authors:
#  * Dries Schaumont (author, maintainer)

set -e

if [ -z "$VIASH_TEMP" ]; then
  VIASH_TEMP=${VIASH_TEMP:-$VIASH_TMPDIR}
  VIASH_TEMP=${VIASH_TEMP:-$VIASH_TEMPDIR}
  VIASH_TEMP=${VIASH_TEMP:-$VIASH_TMP}
  VIASH_TEMP=${VIASH_TEMP:-$TMPDIR}
  VIASH_TEMP=${VIASH_TEMP:-$TMP}
  VIASH_TEMP=${VIASH_TEMP:-$TEMPDIR}
  VIASH_TEMP=${VIASH_TEMP:-$TEMP}
  VIASH_TEMP=${VIASH_TEMP:-/tmp}
fi

# define helper functions
# ViashQuote: put quotes around non flag values
# $1     : unquoted string
# return : possibly quoted string
# examples:
#   ViashQuote --foo      # returns --foo
#   ViashQuote bar        # returns 'bar'
#   Viashquote --foo=bar  # returns --foo='bar'
function ViashQuote {
  if [[ "$1" =~ ^-+[a-zA-Z0-9_\-]+=.+$ ]]; then
    echo "$1" | sed "s#=\(.*\)#='\1'#"
  elif [[ "$1" =~ ^-+[a-zA-Z0-9_\-]+$ ]]; then
    echo "$1"
  else
    echo "'$1'"
  fi
}
# ViashRemoveFlags: Remove leading flag
# $1     : string with a possible leading flag
# return : string without possible leading flag
# examples:
#   ViashRemoveFlags --foo=bar  # returns bar
function ViashRemoveFlags {
  echo "$1" | sed 's/^--*[a-zA-Z0-9_\-]*=//'
}
# ViashSourceDir: return the path of a bash file, following symlinks
# usage   : ViashSourceDir ${BASH_SOURCE[0]}
# $1      : Should always be set to ${BASH_SOURCE[0]}
# returns : The absolute path of the bash file
function ViashSourceDir {
  local source="$1"
  while [ -h "$source" ]; do
    local dir="$( cd -P "$( dirname "$source" )" >/dev/null 2>&1 && pwd )"
    source="$(readlink "$source")"
    [[ $source != /* ]] && source="$dir/$source"
  done
  cd -P "$( dirname "$source" )" >/dev/null 2>&1 && pwd
}
# ViashFindTargetDir: return the path of the '.build.yaml' file, following symlinks
# usage   : ViashFindTargetDir 'ScriptPath'
# $1      : The location from where to start the upward search
# returns : The absolute path of the '.build.yaml' file
function ViashFindTargetDir {
  local source="$1"
  while [[ "$source" != "" && ! -e "$source/.build.yaml" ]]; do
    source=${source%/*}
  done
  echo $source
}
# see https://en.wikipedia.org/wiki/Syslog#Severity_level
VIASH_LOGCODE_EMERGENCY=0
VIASH_LOGCODE_ALERT=1
VIASH_LOGCODE_CRITICAL=2
VIASH_LOGCODE_ERROR=3
VIASH_LOGCODE_WARNING=4
VIASH_LOGCODE_NOTICE=5
VIASH_LOGCODE_INFO=6
VIASH_LOGCODE_DEBUG=7
VIASH_VERBOSITY=$VIASH_LOGCODE_NOTICE

# ViashLog: Log events depending on the verbosity level
# usage: ViashLog 1 alert Oh no something went wrong!
# $1: required verbosity level
# $2: display tag
# $3+: messages to display
# stdout: Your input, prepended by '[$2] '.
function ViashLog {
  local required_level="$1"
  local display_tag="$2"
  shift 2
  if [ $VIASH_VERBOSITY -ge $required_level ]; then
    >&2 echo "[$display_tag]" "$@"
  fi
}

# ViashEmergency: log events when the system is unstable
# usage: ViashEmergency Oh no something went wrong.
# stdout: Your input, prepended by '[emergency] '.
function ViashEmergency {
  ViashLog $VIASH_LOGCODE_EMERGENCY emergency "$@"
}

# ViashAlert: log events when actions must be taken immediately (e.g. corrupted system database)
# usage: ViashAlert Oh no something went wrong.
# stdout: Your input, prepended by '[alert] '.
function ViashAlert {
  ViashLog $VIASH_LOGCODE_ALERT alert "$@"
}

# ViashCritical: log events when a critical condition occurs
# usage: ViashCritical Oh no something went wrong.
# stdout: Your input, prepended by '[critical] '.
function ViashCritical {
  ViashLog $VIASH_LOGCODE_CRITICAL critical "$@"
}

# ViashError: log events when an error condition occurs
# usage: ViashError Oh no something went wrong.
# stdout: Your input, prepended by '[error] '.
function ViashError {
  ViashLog $VIASH_LOGCODE_ERROR error "$@"
}

# ViashWarning: log potentially abnormal events
# usage: ViashWarning Something may have gone wrong.
# stdout: Your input, prepended by '[warning] '.
function ViashWarning {
  ViashLog $VIASH_LOGCODE_WARNING warning "$@"
}

# ViashNotice: log significant but normal events
# usage: ViashNotice This just happened.
# stdout: Your input, prepended by '[notice] '.
function ViashNotice {
  ViashLog $VIASH_LOGCODE_NOTICE notice "$@"
}

# ViashInfo: log normal events
# usage: ViashInfo This just happened.
# stdout: Your input, prepended by '[info] '.
function ViashInfo {
  ViashLog $VIASH_LOGCODE_INFO info "$@"
}

# ViashDebug: log all events, for debugging purposes
# usage: ViashDebug This just happened.
# stdout: Your input, prepended by '[debug] '.
function ViashDebug {
  ViashLog $VIASH_LOGCODE_DEBUG debug "$@"
}

# find source folder of this component
VIASH_META_RESOURCES_DIR=`ViashSourceDir ${BASH_SOURCE[0]}`

# find the root of the built components & dependencies
VIASH_TARGET_DIR=`ViashFindTargetDir $VIASH_META_RESOURCES_DIR`

# define meta fields
VIASH_META_NAME="from_h5mu_or_h5ad_to_tiledb"
VIASH_META_FUNCTIONALITY_NAME="from_h5mu_or_h5ad_to_tiledb"
VIASH_META_EXECUTABLE="$VIASH_META_RESOURCES_DIR/$VIASH_META_NAME"
VIASH_META_CONFIG="$VIASH_META_RESOURCES_DIR/.config.vsh.yaml"
VIASH_META_TEMP_DIR="$VIASH_TEMP"



# initialise variables
VIASH_MODE='run'
VIASH_ENGINE_ID='docker'

######## Helper functions for setting up Docker images for viash ########
# expects: ViashDockerBuild

# ViashDockerInstallationCheck: check whether Docker is installed correctly
#
# examples:
#   ViashDockerInstallationCheck
function ViashDockerInstallationCheck {
  ViashDebug "Checking whether Docker is installed"
  if [ ! command -v docker &> /dev/null ]; then
    ViashCritical "Docker doesn't seem to be installed. See 'https://docs.docker.com/get-docker/' for instructions."
    exit 1
  fi

  ViashDebug "Checking whether the Docker daemon is running"
  local save=$-; set +e
  local docker_version=$(docker version --format '{{.Client.APIVersion}}' 2> /dev/null)
  local out=$?
  [[ $save =~ e ]] && set -e
  if [ $out -ne 0 ]; then
    ViashCritical "Docker daemon does not seem to be running. Try one of the following:"
    ViashCritical "- Try running 'dockerd' in the command line"
    ViashCritical "- See https://docs.docker.com/config/daemon/"
    exit 1
  fi
}

# ViashDockerRemoteTagCheck: check whether a Docker image is available 
# on a remote. Assumes `docker login` has been performed, if relevant.
#
# $1                  : image identifier with format `[registry/]image[:tag]`
# exit code $?        : whether or not the image was found
# examples:
#   ViashDockerRemoteTagCheck python:latest
#   echo $?                                     # returns '0'
#   ViashDockerRemoteTagCheck sdaizudceahifu
#   echo $?                                     # returns '1'
function ViashDockerRemoteTagCheck {
  docker manifest inspect $1 > /dev/null 2> /dev/null
}

# ViashDockerLocalTagCheck: check whether a Docker image is available locally
#
# $1                  : image identifier with format `[registry/]image[:tag]`
# exit code $?        : whether or not the image was found
# examples:
#   docker pull python:latest
#   ViashDockerLocalTagCheck python:latest
#   echo $?                                     # returns '0'
#   ViashDockerLocalTagCheck sdaizudceahifu
#   echo $?                                     # returns '1'
function ViashDockerLocalTagCheck {
  [ -n "$(docker images -q $1)" ]
}

# ViashDockerPull: pull a Docker image
#
# $1                  : image identifier with format `[registry/]image[:tag]`
# exit code $?        : whether or not the image was found
# examples:
#   ViashDockerPull python:latest
#   echo $?                                     # returns '0'
#   ViashDockerPull sdaizudceahifu
#   echo $?                                     # returns '1'
function ViashDockerPull {
  ViashNotice "Checking if Docker image is available at '$1'"
  if [ $VIASH_VERBOSITY -ge $VIASH_LOGCODE_INFO ]; then
    docker pull $1 && return 0 || return 1
  else
    local save=$-; set +e
    docker pull $1 2> /dev/null > /dev/null
    local out=$?
    [[ $save =~ e ]] && set -e
    if [ $out -ne 0 ]; then
      ViashWarning "Could not pull from '$1'. Docker image doesn't exist or is not accessible."
    fi
    return $out
  fi
}

# ViashDockerPush: push a Docker image
#
# $1                  : image identifier with format `[registry/]image[:tag]`
# exit code $?        : whether or not the image was found
# examples:
#   ViashDockerPush python:latest
#   echo $?                                     # returns '0'
#   ViashDockerPush sdaizudceahifu
#   echo $?                                     # returns '1'
function ViashDockerPush {
  ViashNotice "Pushing image to '$1'"
  local save=$-; set +e
  local out
  if [ $VIASH_VERBOSITY -ge $VIASH_LOGCODE_INFO ]; then
    docker push $1
    out=$?
  else
    docker push $1 2> /dev/null > /dev/null
    out=$?
  fi
  [[ $save =~ e ]] && set -e
  if [ $out -eq 0 ]; then
    ViashNotice "Container '$1' push succeeded."
  else
    ViashError "Container '$1' push errored. You might not be logged in or have the necessary permissions."
  fi
  return $out
}

# ViashDockerPullElseBuild: pull a Docker image, else build it
#
# $1                  : image identifier with format `[registry/]image[:tag]`
# ViashDockerBuild    : a Bash function which builds a docker image, takes image identifier as argument.
# examples:
#   ViashDockerPullElseBuild mynewcomponent
function ViashDockerPullElseBuild {
  local save=$-; set +e
  ViashDockerPull $1
  local out=$?
  [[ $save =~ e ]] && set -e
  if [ $out -ne 0 ]; then
    ViashDockerBuild $@
  fi
}

# ViashDockerSetup: create a Docker image, according to specified docker setup strategy
#
# $1          : image identifier with format `[registry/]image[:tag]`
# $2          : docker setup strategy, see DockerSetupStrategy.scala
# examples:
#   ViashDockerSetup mynewcomponent alwaysbuild
function ViashDockerSetup {
  local image_id="$1"
  local setup_strategy="$2"
  if [ "$setup_strategy" == "alwaysbuild" -o "$setup_strategy" == "build" -o "$setup_strategy" == "b" ]; then
    ViashDockerBuild $image_id --no-cache $(ViashDockerBuildArgs "$engine_id")
  elif [ "$setup_strategy" == "alwayspull" -o "$setup_strategy" == "pull" -o "$setup_strategy" == "p" ]; then
    ViashDockerPull $image_id
  elif [ "$setup_strategy" == "alwayspullelsebuild" -o "$setup_strategy" == "pullelsebuild" ]; then
    ViashDockerPullElseBuild $image_id --no-cache $(ViashDockerBuildArgs "$engine_id")
  elif [ "$setup_strategy" == "alwayspullelsecachedbuild" -o "$setup_strategy" == "pullelsecachedbuild" ]; then
    ViashDockerPullElseBuild $image_id $(ViashDockerBuildArgs "$engine_id")
  elif [ "$setup_strategy" == "alwayscachedbuild" -o "$setup_strategy" == "cachedbuild" -o "$setup_strategy" == "cb" ]; then
    ViashDockerBuild $image_id $(ViashDockerBuildArgs "$engine_id")
  elif [[ "$setup_strategy" =~ ^ifneedbe ]]; then
    local save=$-; set +e
    ViashDockerLocalTagCheck $image_id
    local outCheck=$?
    [[ $save =~ e ]] && set -e
    if [ $outCheck -eq 0 ]; then
      ViashInfo "Image $image_id already exists"
    elif [ "$setup_strategy" == "ifneedbebuild" ]; then
      ViashDockerBuild $image_id --no-cache $(ViashDockerBuildArgs "$engine_id")
    elif [ "$setup_strategy" == "ifneedbecachedbuild" ]; then
      ViashDockerBuild $image_id $(ViashDockerBuildArgs "$engine_id")
    elif [ "$setup_strategy" == "ifneedbepull" ]; then
      ViashDockerPull $image_id
    elif [ "$setup_strategy" == "ifneedbepullelsebuild" ]; then
      ViashDockerPullElseBuild $image_id --no-cache $(ViashDockerBuildArgs "$engine_id")
    elif [ "$setup_strategy" == "ifneedbepullelsecachedbuild" ]; then
      ViashDockerPullElseBuild $image_id $(ViashDockerBuildArgs "$engine_id")
    else
      ViashError "Unrecognised Docker strategy: $setup_strategy"
      exit 1
    fi
  elif [ "$setup_strategy" == "push" -o "$setup_strategy" == "forcepush" -o "$setup_strategy" == "alwayspush" ]; then
    ViashDockerPush "$image_id"
  elif [ "$setup_strategy" == "pushifnotpresent" -o "$setup_strategy" == "gentlepush" -o "$setup_strategy" == "maybepush" ]; then
    local save=$-; set +e
    ViashDockerRemoteTagCheck $image_id
    local outCheck=$?
    [[ $save =~ e ]] && set -e
    if [ $outCheck -eq 0 ]; then
      ViashNotice "Container '$image_id' exists, doing nothing."
    else
      ViashNotice "Container '$image_id' does not yet exist."
      ViashDockerPush "$image_id"
    fi
  elif [ "$setup_strategy" == "donothing" -o "$setup_strategy" == "meh" ]; then
    ViashNotice "Skipping setup."
  else
    ViashError "Unrecognised Docker strategy: $setup_strategy"
    exit 1
  fi
}

# ViashDockerCheckCommands: Check whether a docker container has the required commands
#
# $1                  : image identifier with format `[registry/]image[:tag]`
# $@                  : commands to verify being present
# examples:
#   ViashDockerCheckCommands bash:4.0 bash ps foo
function ViashDockerCheckCommands {
  local image_id="$1"
  shift 1
  local commands="$@"
  local save=$-; set +e
  local missing # mark 'missing' as local in advance, otherwise the exit code of the command will be missing and always be '0'
  missing=$(docker run --rm --entrypoint=sh "$image_id" -c "for command in $commands; do command -v \$command >/dev/null 2>&1; if [ \$? -ne 0 ]; then echo \$command; exit 1; fi; done")
  local outCheck=$?
  [[ $save =~ e ]] && set -e
  if [ $outCheck -ne 0 ]; then
  	ViashError "Docker container '$image_id' does not contain command '$missing'."
  	exit 1
  fi
}

# ViashDockerBuild: build a docker image
# $1                               : image identifier with format `[registry/]image[:tag]`
# $...                             : additional arguments to pass to docker build
# $VIASH_META_TEMP_DIR             : temporary directory to store dockerfile & optional resources in
# $VIASH_META_NAME                 : name of the component
# $VIASH_META_RESOURCES_DIR        : directory containing the resources
# $VIASH_VERBOSITY                 : verbosity level
# exit code $?                     : whether or not the image was built successfully
function ViashDockerBuild {
  local image_id="$1"
  shift 1

  # create temporary directory to store dockerfile & optional resources in
  local tmpdir=$(mktemp -d "$VIASH_META_TEMP_DIR/dockerbuild-$VIASH_META_NAME-XXXXXX")
  local dockerfile="$tmpdir/Dockerfile"
  function clean_up {
    rm -rf "$tmpdir"
  }
  trap clean_up EXIT

  # store dockerfile and resources
  ViashDockerfile "$VIASH_ENGINE_ID" > "$dockerfile"

  # generate the build command
  local docker_build_cmd="docker build -t '$image_id' $@ '$VIASH_META_RESOURCES_DIR' -f '$dockerfile'"

  # build the container
  ViashNotice "Building container '$image_id' with Dockerfile"
  ViashInfo "$docker_build_cmd"
  local save=$-; set +e
  if [ $VIASH_VERBOSITY -ge $VIASH_LOGCODE_INFO ]; then
    eval $docker_build_cmd
  else
    eval $docker_build_cmd &> "$tmpdir/docker_build.log"
  fi

  # check exit code
  local out=$?
  [[ $save =~ e ]] && set -e
  if [ $out -ne 0 ]; then
    ViashError "Error occurred while building container '$image_id'"
    if [ $VIASH_VERBOSITY -lt $VIASH_LOGCODE_INFO ]; then
      ViashError "Transcript: --------------------------------"
      cat "$tmpdir/docker_build.log"
      ViashError "End of transcript --------------------------"
    fi
    exit 1
  fi
}

######## End of helper functions for setting up Docker images for viash ########

# ViashDockerFile: print the dockerfile to stdout
# $1    : engine identifier
# return : dockerfile required to run this component
# examples:
#   ViashDockerFile
function ViashDockerfile {
  local engine_id="$1"

  if [[ "$engine_id" == "docker" ]]; then
    cat << 'VIASHDOCKER'
FROM python:3.12-slim
ENTRYPOINT []
RUN apt-get update && \
  DEBIAN_FRONTEND=noninteractive apt-get install -y procps && \
  rm -rf /var/lib/apt/lists/*

RUN pip install --upgrade pip && \
  pip install --upgrade --no-cache-dir "anndata~=0.12.16" "awkward" "scipy~=1.17.1" "mudata~=0.3.8" "tiledbsoma" && \
  python -c 'exec("try:\n  import zarr; from importlib.metadata import version\nexcept ModuleNotFoundError:\n  exit(0)\nelse:  assert int(version(\"zarr\").partition(\".\")[0]) > 2")'

LABEL org.opencontainers.image.authors="Dries Schaumont"
LABEL org.opencontainers.image.description="Companion container for running component convert from_h5mu_or_h5ad_to_tiledb"
LABEL org.opencontainers.image.created="2026-06-09T16:27:35Z"
LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline"
LABEL org.opencontainers.image.revision="38f5f83d11f837ab6c211a98f36979405dd99e58"
LABEL org.opencontainers.image.version="processing-workflows-quantile-filtering"

VIASHDOCKER
  fi
}

# ViashDockerBuildArgs: return the arguments to pass to docker build
# $1    : engine identifier
# return : arguments to pass to docker build
function ViashDockerBuildArgs {
  local engine_id="$1"

  if [[ "$engine_id" == "docker" ]]; then
    echo ""
  fi
}

# ViashAbsolutePath: generate absolute path from relative path
# borrowed from https://stackoverflow.com/a/21951256
# $1     : relative filename
# return : absolute path
# examples:
#   ViashAbsolutePath some_file.txt   # returns /path/to/some_file.txt
#   ViashAbsolutePath /foo/bar/..     # returns /foo
function ViashAbsolutePath {
  local thePath
  local parr
  local outp
  local len
  if [[ ! "$1" =~ ^/ ]]; then
    thePath="$PWD/$1"
  else
    thePath="$1"
  fi
  echo "$thePath" | (
    IFS=/
    read -a parr
    declare -a outp
    for i in "${parr[@]}"; do
      case "$i" in
      ''|.) continue ;;
      ..)
        len=${#outp[@]}
        if ((len==0)); then
          continue
        else
          unset outp[$((len-1))]
        fi
        ;;
      *)
        len=${#outp[@]}
        outp[$len]="$i"
      ;;
      esac
    done
    echo /"${outp[*]}"
  )
}
# ViashDockerAutodetectMount: auto configuring docker mounts from parameters
# $1                             : The parameter value
# returns                        : New parameter
# $VIASH_DIRECTORY_MOUNTS        : Added another parameter to be passed to docker
# $VIASH_DOCKER_AUTOMOUNT_PREFIX : The prefix to be used for the automounts
# examples:
#   ViashDockerAutodetectMount /path/to/bar      # returns '/viash_automount/path/to/bar'
#   ViashDockerAutodetectMountArg /path/to/bar   # returns '--volume="/path/to:/viash_automount/path/to"'
function ViashDockerAutodetectMount {
  local abs_path=$(ViashAbsolutePath "$1")
  local mount_source
  local base_name
  if [ -d "$abs_path" ]; then
    mount_source="$abs_path"
    base_name=""
  else
    mount_source=`dirname "$abs_path"`
    base_name=`basename "$abs_path"`
  fi
  local mount_target="$VIASH_DOCKER_AUTOMOUNT_PREFIX$mount_source"
  if [ -z "$base_name" ]; then
    echo "$mount_target"
  else
    echo "$mount_target/$base_name"
  fi
}
function ViashDockerAutodetectMountArg {
  local abs_path=$(ViashAbsolutePath "$1")
  local mount_source
  local base_name
  if [ -d "$abs_path" ]; then
    mount_source="$abs_path"
    base_name=""
  else
    mount_source=`dirname "$abs_path"`
    base_name=`basename "$abs_path"`
  fi
  local mount_target="$VIASH_DOCKER_AUTOMOUNT_PREFIX$mount_source"
  ViashDebug "ViashDockerAutodetectMountArg $1 -> $mount_source -> $mount_target"
  echo "--volume=\"$mount_source:$mount_target\""
}
function ViashDockerStripAutomount {
  local abs_path=$(ViashAbsolutePath "$1")
  echo "${abs_path#$VIASH_DOCKER_AUTOMOUNT_PREFIX}"
}
# initialise variables
VIASH_DIRECTORY_MOUNTS=()

# configure default docker automount prefix if it is unset
if [ -z "${VIASH_DOCKER_AUTOMOUNT_PREFIX+x}" ]; then
  VIASH_DOCKER_AUTOMOUNT_PREFIX="/viash_automount"
fi

# initialise docker variables
VIASH_DOCKER_RUN_ARGS=(-i --rm)


# ViashHelp: Display helpful explanation about this executable
function ViashHelp {
  echo "from_h5mu_or_h5ad_to_tiledb processing-workflows-quantile-filtering"
  echo ""
  echo "Convert a MuData or AnnData object to tiledb. Currently, transcriptome and"
  echo "protein modalities are supported."
  echo ""
  echo "NOTE: The functionality provided by this component is experimental and may be"
  echo "subject to change."
  echo ""
  echo "Input:"
  echo "    --input"
  echo "        type: file, required parameter, file must exist"
  echo "        example: input.h5mu"
  echo "        Input AnnData or MuData file. When an AnnData file is provided, it is"
  echo "        automatically assumed to"
  echo "        contain transcriptome counts."
  echo ""
  echo "RNA modality:"
  echo "    --rna_modality"
  echo "        type: string"
  echo "        default: rna"
  echo "        The name used for the RNA modality. Used when input file is a MuData"
  echo "        object."
  echo ""
  echo "    --rna_raw_layer_input"
  echo "        type: string, required parameter"
  echo "        example: X"
  echo "        Location of the layer containing the raw transcriptome counts. Layers"
  echo "        are looked for in .layers,"
  echo "        except when using the value 'X'; in which case .X is used."
  echo ""
  echo "    --rna_normalized_layer_input"
  echo "        type: string, required parameter"
  echo "        example: log_normalized"
  echo "        Location of the layer containing the normalized counts. Layers are"
  echo "        looked for in .layers,"
  echo "        except when using the value 'X'; in which case .X is used."
  echo ""
  echo "    --rna_var_gene_names_input"
  echo "        type: string"
  echo "        example: gene_symbol"
  echo "        Column in .var that provides the gene names. If not specified, the index"
  echo "        from the input is used."
  echo ""
  echo "Protein modality:"
  echo "    --prot_modality"
  echo "        type: string"
  echo "        example: prot"
  echo "        The name used for the protein modality. Used when input file is a MuData"
  echo "        object."
  echo "        When not specified, the protein modality will not be processed."
  echo ""
  echo "    --prot_raw_layer_input"
  echo "        type: string"
  echo "        example: X"
  echo "        Location of the layer containing the raw protein counts. Layers are"
  echo "        looked for in .layers,"
  echo "        except when using the value 'X'; in which case .X is used."
  echo ""
  echo "    --prot_normalized_layer_input"
  echo "        type: string"
  echo "        example: clr"
  echo "        Location of the layer containing the normalized counts. Layers are"
  echo "        looked for in .layers,"
  echo "        except when using the value 'X'; in which case .X is used."
  echo ""
  echo "Output slots:"
  echo "    --rna_modality_output"
  echo "        type: string"
  echo "        default: rna"
  echo "        TileDB Measurement name where the RNA modality will be stored."
  echo ""
  echo "    --prot_modality_output"
  echo "        type: string"
  echo "        default: prot"
  echo "        Name of the TileDB Measurement where the protein modality will be"
  echo "        stored."
  echo ""
  echo "    --obs_index_name_output"
  echo "        type: string"
  echo "        default: cell_id"
  echo "        Name of the index that is used to describe the cells (observations)."
  echo ""
  echo "    --rna_var_index_name_output"
  echo "        type: string"
  echo "        default: rna_index"
  echo "        Output name of the index that is used to describe the genes."
  echo ""
  echo "    --rna_raw_layer_output"
  echo "        type: string"
  echo "        default: X"
  echo "        Output location for the raw transcriptomics counts."
  echo ""
  echo "    --rna_normalized_layer_output"
  echo "        type: string"
  echo "        default: log_normalized"
  echo "        Output location for the normalized RNA counts."
  echo ""
  echo "    --rna_var_gene_names_output"
  echo "        type: string"
  echo "        default: gene_symbol"
  echo "        Name of the .var column that specifies the gene games."
  echo ""
  echo "    --prot_var_index_name_output"
  echo "        type: string"
  echo "        default: prot_index"
  echo "        Output name of the index that is used to describe the proteins."
  echo ""
  echo "    --prot_raw_layer_output"
  echo "        type: string"
  echo "        default: X"
  echo "        Output location for the raw protein counts."
  echo ""
  echo "    --prot_normalized_layer_output"
  echo "        type: string"
  echo "        default: log_normalized"
  echo "        Output location for the normalized protein counts."
  echo ""
  echo "Output arguments:"
  echo "    --tiledb_dir"
  echo "        type: file, output, file must exist"
  echo "        example: path/to/tiledb_dir"
  echo "        Directory where the TileDB output will be written to."
  echo ""
  echo "Viash built in Computational Requirements:"
  echo "    ---cpus=INT"
  echo "        Number of CPUs to use"
  echo "    ---memory=STRING"
  echo "        Amount of memory to use. Examples: 4GB, 3MiB."
  echo ""
  echo "Viash built in Docker:"
  echo "    ---setup=STRATEGY"
  echo "        Setup the docker container. Options are: alwaysbuild, alwayscachedbuild, ifneedbebuild, ifneedbecachedbuild, alwayspull, alwayspullelsebuild, alwayspullelsecachedbuild, ifneedbepull, ifneedbepullelsebuild, ifneedbepullelsecachedbuild, push, pushifnotpresent, donothing."
  echo "        Default: ifneedbepullelsecachedbuild"
  echo "    ---dockerfile"
  echo "        Print the dockerfile to stdout."
  echo "    ---docker_run_args=ARG"
  echo "        Provide runtime arguments to Docker. See the documentation on \`docker run\` for more information."
  echo "    ---docker_image_id"
  echo "        Print the docker image id to stdout."
  echo "    ---debug"
  echo "        Enter the docker container for debugging purposes."
  echo ""
  echo "Viash built in Engines:"
  echo "    ---engine=ENGINE_ID"
  echo "        Specify the engine to use. Options are: docker, native."
  echo "        Default: docker"
}

# initialise array
VIASH_POSITIONAL_ARGS=''

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help)
            ViashHelp
            exit
            ;;
        ---v|---verbose)
            let "VIASH_VERBOSITY=VIASH_VERBOSITY+1"
            shift 1
            ;;
        ---verbosity)
            VIASH_VERBOSITY="$2"
            shift 2
            ;;
        ---verbosity=*)
            VIASH_VERBOSITY="$(ViashRemoveFlags "$1")"
            shift 1
            ;;
        --version)
            echo "from_h5mu_or_h5ad_to_tiledb processing-workflows-quantile-filtering"
            exit
            ;;
        --input)
            [ -n "$VIASH_PAR_INPUT" ] && ViashError Bad arguments for option \'--input\': \'$VIASH_PAR_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_INPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --input. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --input=*)
            [ -n "$VIASH_PAR_INPUT" ] && ViashError Bad arguments for option \'--input=*\': \'$VIASH_PAR_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_INPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --rna_modality)
            [ -n "$VIASH_PAR_RNA_MODALITY" ] && ViashError Bad arguments for option \'--rna_modality\': \'$VIASH_PAR_RNA_MODALITY\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_MODALITY="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --rna_modality. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --rna_modality=*)
            [ -n "$VIASH_PAR_RNA_MODALITY" ] && ViashError Bad arguments for option \'--rna_modality=*\': \'$VIASH_PAR_RNA_MODALITY\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_MODALITY=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --rna_raw_layer_input)
            [ -n "$VIASH_PAR_RNA_RAW_LAYER_INPUT" ] && ViashError Bad arguments for option \'--rna_raw_layer_input\': \'$VIASH_PAR_RNA_RAW_LAYER_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_RAW_LAYER_INPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --rna_raw_layer_input. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --rna_raw_layer_input=*)
            [ -n "$VIASH_PAR_RNA_RAW_LAYER_INPUT" ] && ViashError Bad arguments for option \'--rna_raw_layer_input=*\': \'$VIASH_PAR_RNA_RAW_LAYER_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_RAW_LAYER_INPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --rna_normalized_layer_input)
            [ -n "$VIASH_PAR_RNA_NORMALIZED_LAYER_INPUT" ] && ViashError Bad arguments for option \'--rna_normalized_layer_input\': \'$VIASH_PAR_RNA_NORMALIZED_LAYER_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_NORMALIZED_LAYER_INPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --rna_normalized_layer_input. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --rna_normalized_layer_input=*)
            [ -n "$VIASH_PAR_RNA_NORMALIZED_LAYER_INPUT" ] && ViashError Bad arguments for option \'--rna_normalized_layer_input=*\': \'$VIASH_PAR_RNA_NORMALIZED_LAYER_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_NORMALIZED_LAYER_INPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --rna_var_gene_names_input)
            [ -n "$VIASH_PAR_RNA_VAR_GENE_NAMES_INPUT" ] && ViashError Bad arguments for option \'--rna_var_gene_names_input\': \'$VIASH_PAR_RNA_VAR_GENE_NAMES_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_VAR_GENE_NAMES_INPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --rna_var_gene_names_input. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --rna_var_gene_names_input=*)
            [ -n "$VIASH_PAR_RNA_VAR_GENE_NAMES_INPUT" ] && ViashError Bad arguments for option \'--rna_var_gene_names_input=*\': \'$VIASH_PAR_RNA_VAR_GENE_NAMES_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_VAR_GENE_NAMES_INPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --prot_modality)
            [ -n "$VIASH_PAR_PROT_MODALITY" ] && ViashError Bad arguments for option \'--prot_modality\': \'$VIASH_PAR_PROT_MODALITY\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_MODALITY="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --prot_modality. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --prot_modality=*)
            [ -n "$VIASH_PAR_PROT_MODALITY" ] && ViashError Bad arguments for option \'--prot_modality=*\': \'$VIASH_PAR_PROT_MODALITY\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_MODALITY=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --prot_raw_layer_input)
            [ -n "$VIASH_PAR_PROT_RAW_LAYER_INPUT" ] && ViashError Bad arguments for option \'--prot_raw_layer_input\': \'$VIASH_PAR_PROT_RAW_LAYER_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_RAW_LAYER_INPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --prot_raw_layer_input. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --prot_raw_layer_input=*)
            [ -n "$VIASH_PAR_PROT_RAW_LAYER_INPUT" ] && ViashError Bad arguments for option \'--prot_raw_layer_input=*\': \'$VIASH_PAR_PROT_RAW_LAYER_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_RAW_LAYER_INPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --prot_normalized_layer_input)
            [ -n "$VIASH_PAR_PROT_NORMALIZED_LAYER_INPUT" ] && ViashError Bad arguments for option \'--prot_normalized_layer_input\': \'$VIASH_PAR_PROT_NORMALIZED_LAYER_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_NORMALIZED_LAYER_INPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --prot_normalized_layer_input. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --prot_normalized_layer_input=*)
            [ -n "$VIASH_PAR_PROT_NORMALIZED_LAYER_INPUT" ] && ViashError Bad arguments for option \'--prot_normalized_layer_input=*\': \'$VIASH_PAR_PROT_NORMALIZED_LAYER_INPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_NORMALIZED_LAYER_INPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --rna_modality_output)
            [ -n "$VIASH_PAR_RNA_MODALITY_OUTPUT" ] && ViashError Bad arguments for option \'--rna_modality_output\': \'$VIASH_PAR_RNA_MODALITY_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_MODALITY_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --rna_modality_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --rna_modality_output=*)
            [ -n "$VIASH_PAR_RNA_MODALITY_OUTPUT" ] && ViashError Bad arguments for option \'--rna_modality_output=*\': \'$VIASH_PAR_RNA_MODALITY_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_MODALITY_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --prot_modality_output)
            [ -n "$VIASH_PAR_PROT_MODALITY_OUTPUT" ] && ViashError Bad arguments for option \'--prot_modality_output\': \'$VIASH_PAR_PROT_MODALITY_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_MODALITY_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --prot_modality_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --prot_modality_output=*)
            [ -n "$VIASH_PAR_PROT_MODALITY_OUTPUT" ] && ViashError Bad arguments for option \'--prot_modality_output=*\': \'$VIASH_PAR_PROT_MODALITY_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_MODALITY_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --obs_index_name_output)
            [ -n "$VIASH_PAR_OBS_INDEX_NAME_OUTPUT" ] && ViashError Bad arguments for option \'--obs_index_name_output\': \'$VIASH_PAR_OBS_INDEX_NAME_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_OBS_INDEX_NAME_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --obs_index_name_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --obs_index_name_output=*)
            [ -n "$VIASH_PAR_OBS_INDEX_NAME_OUTPUT" ] && ViashError Bad arguments for option \'--obs_index_name_output=*\': \'$VIASH_PAR_OBS_INDEX_NAME_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_OBS_INDEX_NAME_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --rna_var_index_name_output)
            [ -n "$VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT" ] && ViashError Bad arguments for option \'--rna_var_index_name_output\': \'$VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --rna_var_index_name_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --rna_var_index_name_output=*)
            [ -n "$VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT" ] && ViashError Bad arguments for option \'--rna_var_index_name_output=*\': \'$VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --rna_raw_layer_output)
            [ -n "$VIASH_PAR_RNA_RAW_LAYER_OUTPUT" ] && ViashError Bad arguments for option \'--rna_raw_layer_output\': \'$VIASH_PAR_RNA_RAW_LAYER_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_RAW_LAYER_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --rna_raw_layer_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --rna_raw_layer_output=*)
            [ -n "$VIASH_PAR_RNA_RAW_LAYER_OUTPUT" ] && ViashError Bad arguments for option \'--rna_raw_layer_output=*\': \'$VIASH_PAR_RNA_RAW_LAYER_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_RAW_LAYER_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --rna_normalized_layer_output)
            [ -n "$VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT" ] && ViashError Bad arguments for option \'--rna_normalized_layer_output\': \'$VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --rna_normalized_layer_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --rna_normalized_layer_output=*)
            [ -n "$VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT" ] && ViashError Bad arguments for option \'--rna_normalized_layer_output=*\': \'$VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --rna_var_gene_names_output)
            [ -n "$VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT" ] && ViashError Bad arguments for option \'--rna_var_gene_names_output\': \'$VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --rna_var_gene_names_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --rna_var_gene_names_output=*)
            [ -n "$VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT" ] && ViashError Bad arguments for option \'--rna_var_gene_names_output=*\': \'$VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --prot_var_index_name_output)
            [ -n "$VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT" ] && ViashError Bad arguments for option \'--prot_var_index_name_output\': \'$VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --prot_var_index_name_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --prot_var_index_name_output=*)
            [ -n "$VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT" ] && ViashError Bad arguments for option \'--prot_var_index_name_output=*\': \'$VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --prot_raw_layer_output)
            [ -n "$VIASH_PAR_PROT_RAW_LAYER_OUTPUT" ] && ViashError Bad arguments for option \'--prot_raw_layer_output\': \'$VIASH_PAR_PROT_RAW_LAYER_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_RAW_LAYER_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --prot_raw_layer_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --prot_raw_layer_output=*)
            [ -n "$VIASH_PAR_PROT_RAW_LAYER_OUTPUT" ] && ViashError Bad arguments for option \'--prot_raw_layer_output=*\': \'$VIASH_PAR_PROT_RAW_LAYER_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_RAW_LAYER_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --prot_normalized_layer_output)
            [ -n "$VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT" ] && ViashError Bad arguments for option \'--prot_normalized_layer_output\': \'$VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --prot_normalized_layer_output. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --prot_normalized_layer_output=*)
            [ -n "$VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT" ] && ViashError Bad arguments for option \'--prot_normalized_layer_output=*\': \'$VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        --tiledb_dir)
            [ -n "$VIASH_PAR_TILEDB_DIR" ] && ViashError Bad arguments for option \'--tiledb_dir\': \'$VIASH_PAR_TILEDB_DIR\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_TILEDB_DIR="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to --tiledb_dir. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        --tiledb_dir=*)
            [ -n "$VIASH_PAR_TILEDB_DIR" ] && ViashError Bad arguments for option \'--tiledb_dir=*\': \'$VIASH_PAR_TILEDB_DIR\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_PAR_TILEDB_DIR=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        ---engine)
            VIASH_ENGINE_ID="$2"
            shift 2
            ;;
        ---engine=*)
            VIASH_ENGINE_ID="$(ViashRemoveFlags "$1")"
            shift 1
            ;;
        ---setup)
            VIASH_MODE='setup'
            VIASH_SETUP_STRATEGY="$2"
            shift 2
            ;;
        ---setup=*)
            VIASH_MODE='setup'
            VIASH_SETUP_STRATEGY="$(ViashRemoveFlags "$1")"
            shift 1
            ;;
        ---dockerfile)
            VIASH_MODE='dockerfile'
            shift 1
            ;;
        ---docker_run_args)
            VIASH_DOCKER_RUN_ARGS+=("$2")
            shift 2
            ;;
        ---docker_run_args=*)
            VIASH_DOCKER_RUN_ARGS+=("$(ViashRemoveFlags "$1")")
            shift 1
            ;;
        ---docker_image_id)
            VIASH_MODE='docker_image_id'
            shift 1
            ;;
        ---debug)
            VIASH_MODE='debug'
            shift 1
            ;;
        ---cpus)
            [ -n "$VIASH_META_CPUS" ] && ViashError Bad arguments for option \'---cpus\': \'$VIASH_META_CPUS\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_META_CPUS="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to ---cpus. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        ---cpus=*)
            [ -n "$VIASH_META_CPUS" ] && ViashError Bad arguments for option \'---cpus=*\': \'$VIASH_META_CPUS\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_META_CPUS=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        ---memory)
            [ -n "$VIASH_META_MEMORY" ] && ViashError Bad arguments for option \'---memory\': \'$VIASH_META_MEMORY\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_META_MEMORY="$2"
            [ $# -lt 2 ] && ViashError Not enough arguments passed to ---memory. Use "--help" to get more information on the parameters. && exit 1
            shift 2
            ;;
        ---memory=*)
            [ -n "$VIASH_META_MEMORY" ] && ViashError Bad arguments for option \'---memory=*\': \'$VIASH_META_MEMORY\' \& \'$2\' - you should provide exactly one argument for this option. && exit 1
            VIASH_META_MEMORY=$(ViashRemoveFlags "$1")
            shift 1
            ;;
        *)  # positional arg or unknown option
            # since the positional args will be eval'd, can we always quote, instead of using ViashQuote
            VIASH_POSITIONAL_ARGS="$VIASH_POSITIONAL_ARGS '$1'"
            [[ $1 == -* ]] && ViashWarning $1 looks like a parameter but is not a defined parameter and will instead be treated as a positional argument. Use "--help" to get more information on the parameters.
            shift # past argument
            ;;
    esac
done

# parse positional parameters
eval set -- $VIASH_POSITIONAL_ARGS


if   [ "$VIASH_ENGINE_ID" == "native" ]  ; then
  VIASH_ENGINE_TYPE='native'
elif   [ "$VIASH_ENGINE_ID" == "docker" ]  ; then
  VIASH_ENGINE_TYPE='docker'
else
  ViashError "Engine '$VIASH_ENGINE_ID' is not recognized. Options are: docker, native."
  exit 1
fi

if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
  # check if docker is installed properly
  ViashDockerInstallationCheck

  # determine docker image id
  if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then
    VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline/convert/from_h5mu_or_h5ad_to_tiledb:processing-workflows-quantile-filtering'
  fi

  # print dockerfile
  if [ "$VIASH_MODE" == "dockerfile" ]; then
    ViashDockerfile "$VIASH_ENGINE_ID"
    exit 0

  elif [ "$VIASH_MODE" == "docker_image_id" ]; then
    echo "$VIASH_DOCKER_IMAGE_ID"
    exit 0
  
  # enter docker container
  elif [[ "$VIASH_MODE" == "debug" ]]; then
    VIASH_CMD="docker run --entrypoint=bash ${VIASH_DOCKER_RUN_ARGS[@]} -v '$(pwd)':/pwd --workdir /pwd -t $VIASH_DOCKER_IMAGE_ID"
    ViashNotice "+ $VIASH_CMD"
    eval $VIASH_CMD
    exit 

  # build docker image
  elif [ "$VIASH_MODE" == "setup" ]; then
    ViashDockerSetup "$VIASH_DOCKER_IMAGE_ID" "$VIASH_SETUP_STRATEGY"
    ViashDockerCheckCommands "$VIASH_DOCKER_IMAGE_ID" 'bash'
    exit 0
  fi

  # check if docker image exists
  ViashDockerSetup "$VIASH_DOCKER_IMAGE_ID" ifneedbepullelsecachedbuild
  ViashDockerCheckCommands "$VIASH_DOCKER_IMAGE_ID" 'bash'
fi

# setting computational defaults

# helper function for parsing memory strings
function ViashMemoryAsBytes {
  local memory=`echo "$1" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]'`
  local memory_regex='^([0-9]+)([kmgtp]i?b?|b)$'
  if [[ $memory =~ $memory_regex ]]; then
    local number=${memory/[^0-9]*/}
    local symbol=${memory/*[0-9]/}
    
    case $symbol in
      b)      memory_b=$number ;;
      kb|k)   memory_b=$(( $number * 1000 )) ;;
      mb|m)   memory_b=$(( $number * 1000 * 1000 )) ;;
      gb|g)   memory_b=$(( $number * 1000 * 1000 * 1000 )) ;;
      tb|t)   memory_b=$(( $number * 1000 * 1000 * 1000 * 1000 )) ;;
      pb|p)   memory_b=$(( $number * 1000 * 1000 * 1000 * 1000 * 1000 )) ;;
      kib|ki)   memory_b=$(( $number * 1024 )) ;;
      mib|mi)   memory_b=$(( $number * 1024 * 1024 )) ;;
      gib|gi)   memory_b=$(( $number * 1024 * 1024 * 1024 )) ;;
      tib|ti)   memory_b=$(( $number * 1024 * 1024 * 1024 * 1024 )) ;;
      pib|pi)   memory_b=$(( $number * 1024 * 1024 * 1024 * 1024 * 1024 )) ;;
    esac
    echo "$memory_b"
  fi
}
# compute memory in different units
if [ ! -z ${VIASH_META_MEMORY+x} ]; then
  VIASH_META_MEMORY_B=`ViashMemoryAsBytes $VIASH_META_MEMORY`
  # do not define other variables if memory_b is an empty string
  if [ ! -z "$VIASH_META_MEMORY_B" ]; then
    VIASH_META_MEMORY_KB=$(( ($VIASH_META_MEMORY_B+999) / 1000 ))
    VIASH_META_MEMORY_MB=$(( ($VIASH_META_MEMORY_KB+999) / 1000 ))
    VIASH_META_MEMORY_GB=$(( ($VIASH_META_MEMORY_MB+999) / 1000 ))
    VIASH_META_MEMORY_TB=$(( ($VIASH_META_MEMORY_GB+999) / 1000 ))
    VIASH_META_MEMORY_PB=$(( ($VIASH_META_MEMORY_TB+999) / 1000 ))
    VIASH_META_MEMORY_KIB=$(( ($VIASH_META_MEMORY_B+1023) / 1024 ))
    VIASH_META_MEMORY_MIB=$(( ($VIASH_META_MEMORY_KIB+1023) / 1024 ))
    VIASH_META_MEMORY_GIB=$(( ($VIASH_META_MEMORY_MIB+1023) / 1024 ))
    VIASH_META_MEMORY_TIB=$(( ($VIASH_META_MEMORY_GIB+1023) / 1024 ))
    VIASH_META_MEMORY_PIB=$(( ($VIASH_META_MEMORY_TIB+1023) / 1024 ))
  else
    # unset memory if string is empty
    unset $VIASH_META_MEMORY_B
  fi
fi
# unset nproc if string is empty
if [ -z "$VIASH_META_CPUS" ]; then
  unset $VIASH_META_CPUS
fi


# check whether required parameters exist
if [ -z ${VIASH_PAR_INPUT+x} ]; then
  ViashError '--input' is a required argument. Use "--help" to get more information on the parameters.
  exit 1
fi
if [ -z ${VIASH_PAR_RNA_RAW_LAYER_INPUT+x} ]; then
  ViashError '--rna_raw_layer_input' is a required argument. Use "--help" to get more information on the parameters.
  exit 1
fi
if [ -z ${VIASH_PAR_RNA_NORMALIZED_LAYER_INPUT+x} ]; then
  ViashError '--rna_normalized_layer_input' is a required argument. Use "--help" to get more information on the parameters.
  exit 1
fi
if [ -z ${VIASH_META_NAME+x} ]; then
  ViashError 'name' is a required argument. Use "--help" to get more information on the parameters.
  exit 1
fi
if [ -z ${VIASH_META_FUNCTIONALITY_NAME+x} ]; then
  ViashError 'functionality_name' is a required argument. Use "--help" to get more information on the parameters.
  exit 1
fi
if [ -z ${VIASH_META_RESOURCES_DIR+x} ]; then
  ViashError 'resources_dir' is a required argument. Use "--help" to get more information on the parameters.
  exit 1
fi
if [ -z ${VIASH_META_EXECUTABLE+x} ]; then
  ViashError 'executable' is a required argument. Use "--help" to get more information on the parameters.
  exit 1
fi
if [ -z ${VIASH_META_CONFIG+x} ]; then
  ViashError 'config' is a required argument. Use "--help" to get more information on the parameters.
  exit 1
fi
if [ -z ${VIASH_META_TEMP_DIR+x} ]; then
  ViashError 'temp_dir' is a required argument. Use "--help" to get more information on the parameters.
  exit 1
fi

# filling in defaults
if [ -z ${VIASH_PAR_RNA_MODALITY+x} ]; then
  VIASH_PAR_RNA_MODALITY="rna"
fi
if [ -z ${VIASH_PAR_RNA_MODALITY_OUTPUT+x} ]; then
  VIASH_PAR_RNA_MODALITY_OUTPUT="rna"
fi
if [ -z ${VIASH_PAR_PROT_MODALITY_OUTPUT+x} ]; then
  VIASH_PAR_PROT_MODALITY_OUTPUT="prot"
fi
if [ -z ${VIASH_PAR_OBS_INDEX_NAME_OUTPUT+x} ]; then
  VIASH_PAR_OBS_INDEX_NAME_OUTPUT="cell_id"
fi
if [ -z ${VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT+x} ]; then
  VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT="rna_index"
fi
if [ -z ${VIASH_PAR_RNA_RAW_LAYER_OUTPUT+x} ]; then
  VIASH_PAR_RNA_RAW_LAYER_OUTPUT="X"
fi
if [ -z ${VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT+x} ]; then
  VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT="log_normalized"
fi
if [ -z ${VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT+x} ]; then
  VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT="gene_symbol"
fi
if [ -z ${VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT+x} ]; then
  VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT="prot_index"
fi
if [ -z ${VIASH_PAR_PROT_RAW_LAYER_OUTPUT+x} ]; then
  VIASH_PAR_PROT_RAW_LAYER_OUTPUT="X"
fi
if [ -z ${VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT+x} ]; then
  VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT="log_normalized"
fi

# check whether required files exist
if [ ! -z "$VIASH_PAR_INPUT" ] && [ ! -e "$VIASH_PAR_INPUT" ]; then
  ViashError "Input file '$VIASH_PAR_INPUT' does not exist."
  exit 1
fi

# check whether parameters values are of the right type
if [[ -n "$VIASH_META_CPUS" ]]; then
  if ! [[ "$VIASH_META_CPUS" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'cpus' has to be an integer. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_B" ]]; then
  if ! [[ "$VIASH_META_MEMORY_B" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_b' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_KB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_KB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_kb' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_MB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_MB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_mb' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_GB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_GB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_gb' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_TB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_TB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_tb' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_PB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_PB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_pb' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_KIB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_KIB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_kib' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_MIB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_MIB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_mib' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_GIB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_GIB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_gib' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_TIB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_TIB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_tib' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi
if [[ -n "$VIASH_META_MEMORY_PIB" ]]; then
  if ! [[ "$VIASH_META_MEMORY_PIB" =~ ^[-+]?[0-9]+$ ]]; then
    ViashError 'memory_pib' has to be a long. Use "--help" to get more information on the parameters.
    exit 1
  fi
fi

# create parent directories of output files, if so desired
if [ ! -z "$VIASH_PAR_TILEDB_DIR" ] && [ ! -d "$(dirname "$VIASH_PAR_TILEDB_DIR")" ]; then
  mkdir -p "$(dirname "$VIASH_PAR_TILEDB_DIR")"
fi

if  [ "$VIASH_ENGINE_ID" == "native" ]  ; then
  if [ "$VIASH_MODE" == "run" ]; then
    VIASH_CMD="bash"
  else
    ViashError "Engine '$VIASH_ENGINE_ID' does not support mode '$VIASH_MODE'."
    exit 1
  fi
fi

if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
  # detect volumes from file arguments
  VIASH_CHOWN_VARS=()
if [ ! -z "$VIASH_PAR_INPUT" ]; then
  VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_PAR_INPUT")" )
  VIASH_PAR_INPUT=$(ViashDockerAutodetectMount "$VIASH_PAR_INPUT")
fi
if [ ! -z "$VIASH_PAR_TILEDB_DIR" ]; then
  VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_PAR_TILEDB_DIR")" )
  VIASH_PAR_TILEDB_DIR=$(ViashDockerAutodetectMount "$VIASH_PAR_TILEDB_DIR")
  VIASH_CHOWN_VARS+=( "$VIASH_PAR_TILEDB_DIR" )
fi
if [ ! -z "$VIASH_META_RESOURCES_DIR" ]; then
  VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_META_RESOURCES_DIR")" )
  VIASH_META_RESOURCES_DIR=$(ViashDockerAutodetectMount "$VIASH_META_RESOURCES_DIR")
fi
if [ ! -z "$VIASH_META_EXECUTABLE" ]; then
  VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_META_EXECUTABLE")" )
  VIASH_META_EXECUTABLE=$(ViashDockerAutodetectMount "$VIASH_META_EXECUTABLE")
fi
if [ ! -z "$VIASH_META_CONFIG" ]; then
  VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_META_CONFIG")" )
  VIASH_META_CONFIG=$(ViashDockerAutodetectMount "$VIASH_META_CONFIG")
fi
if [ ! -z "$VIASH_META_TEMP_DIR" ]; then
  VIASH_DIRECTORY_MOUNTS+=( "$(ViashDockerAutodetectMountArg "$VIASH_META_TEMP_DIR")" )
  VIASH_META_TEMP_DIR=$(ViashDockerAutodetectMount "$VIASH_META_TEMP_DIR")
fi
  
  # get unique mounts
  VIASH_UNIQUE_MOUNTS=($(for val in "${VIASH_DIRECTORY_MOUNTS[@]}"; do echo "$val"; done | sort -u))
fi

if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
  # change file ownership
  function ViashPerformChown {
    if (( ${#VIASH_CHOWN_VARS[@]} )); then
      set +e
      VIASH_CMD="docker run --entrypoint=bash --rm ${VIASH_UNIQUE_MOUNTS[@]} $VIASH_DOCKER_IMAGE_ID -c 'chown $(id -u):$(id -g) --silent --recursive ${VIASH_CHOWN_VARS[@]}'"
      ViashDebug "+ $VIASH_CMD"
      eval $VIASH_CMD
      set -e
    fi
  }
  trap ViashPerformChown EXIT
fi

if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
  # helper function for filling in extra docker args
  if [ ! -z "$VIASH_META_MEMORY_B" ]; then
    VIASH_DOCKER_RUN_ARGS+=("--memory=${VIASH_META_MEMORY_B}")
  fi
  if [ ! -z "$VIASH_META_CPUS" ]; then
    VIASH_DOCKER_RUN_ARGS+=("--cpus=${VIASH_META_CPUS}")
  fi
fi

if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
  VIASH_CMD="docker run --entrypoint=bash ${VIASH_DOCKER_RUN_ARGS[@]} ${VIASH_UNIQUE_MOUNTS[@]} $VIASH_DOCKER_IMAGE_ID"
fi


# set dependency paths


ViashDebug "Running command: $(echo $VIASH_CMD)"
cat << VIASHEOF | eval $VIASH_CMD
set -e
tempscript=\$(mktemp "$VIASH_META_TEMP_DIR/viash-run-from_h5mu_or_h5ad_to_tiledb-XXXXXX").py
function clean_up {
  rm "\$tempscript"
}
function interrupt {
  echo -e "\nCTRL-C Pressed..."
  exit 1
}
trap clean_up EXIT
trap interrupt INT SIGINT
cat > "\$tempscript" << 'VIASHMAIN'
import sys
import anndata
import numpy as np
import tiledbsoma

# This import seems redundant, but it is required!
import tiledbsoma.io
import pandas as pd
from functools import singledispatch
from collections.abc import Mapping
import h5py
from shutil import copy2, rmtree
from collections.abc import Callable
from functools import partial
from pathlib import Path
from tempfile import NamedTemporaryFile
import pyarrow as pa
from collections import defaultdict
import json

anndata.settings.allow_write_nullable_strings = True


## VIASH START
# The following code has been auto-generated by Viash.
par = {
  'input': $( if [ ! -z ${VIASH_PAR_INPUT+x} ]; then echo "r'${VIASH_PAR_INPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'rna_modality': $( if [ ! -z ${VIASH_PAR_RNA_MODALITY+x} ]; then echo "r'${VIASH_PAR_RNA_MODALITY//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'rna_raw_layer_input': $( if [ ! -z ${VIASH_PAR_RNA_RAW_LAYER_INPUT+x} ]; then echo "r'${VIASH_PAR_RNA_RAW_LAYER_INPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'rna_normalized_layer_input': $( if [ ! -z ${VIASH_PAR_RNA_NORMALIZED_LAYER_INPUT+x} ]; then echo "r'${VIASH_PAR_RNA_NORMALIZED_LAYER_INPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'rna_var_gene_names_input': $( if [ ! -z ${VIASH_PAR_RNA_VAR_GENE_NAMES_INPUT+x} ]; then echo "r'${VIASH_PAR_RNA_VAR_GENE_NAMES_INPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'prot_modality': $( if [ ! -z ${VIASH_PAR_PROT_MODALITY+x} ]; then echo "r'${VIASH_PAR_PROT_MODALITY//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'prot_raw_layer_input': $( if [ ! -z ${VIASH_PAR_PROT_RAW_LAYER_INPUT+x} ]; then echo "r'${VIASH_PAR_PROT_RAW_LAYER_INPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'prot_normalized_layer_input': $( if [ ! -z ${VIASH_PAR_PROT_NORMALIZED_LAYER_INPUT+x} ]; then echo "r'${VIASH_PAR_PROT_NORMALIZED_LAYER_INPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'rna_modality_output': $( if [ ! -z ${VIASH_PAR_RNA_MODALITY_OUTPUT+x} ]; then echo "r'${VIASH_PAR_RNA_MODALITY_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'prot_modality_output': $( if [ ! -z ${VIASH_PAR_PROT_MODALITY_OUTPUT+x} ]; then echo "r'${VIASH_PAR_PROT_MODALITY_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'obs_index_name_output': $( if [ ! -z ${VIASH_PAR_OBS_INDEX_NAME_OUTPUT+x} ]; then echo "r'${VIASH_PAR_OBS_INDEX_NAME_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'rna_var_index_name_output': $( if [ ! -z ${VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT+x} ]; then echo "r'${VIASH_PAR_RNA_VAR_INDEX_NAME_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'rna_raw_layer_output': $( if [ ! -z ${VIASH_PAR_RNA_RAW_LAYER_OUTPUT+x} ]; then echo "r'${VIASH_PAR_RNA_RAW_LAYER_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'rna_normalized_layer_output': $( if [ ! -z ${VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT+x} ]; then echo "r'${VIASH_PAR_RNA_NORMALIZED_LAYER_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'rna_var_gene_names_output': $( if [ ! -z ${VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT+x} ]; then echo "r'${VIASH_PAR_RNA_VAR_GENE_NAMES_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'prot_var_index_name_output': $( if [ ! -z ${VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT+x} ]; then echo "r'${VIASH_PAR_PROT_VAR_INDEX_NAME_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'prot_raw_layer_output': $( if [ ! -z ${VIASH_PAR_PROT_RAW_LAYER_OUTPUT+x} ]; then echo "r'${VIASH_PAR_PROT_RAW_LAYER_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'prot_normalized_layer_output': $( if [ ! -z ${VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT+x} ]; then echo "r'${VIASH_PAR_PROT_NORMALIZED_LAYER_OUTPUT//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'tiledb_dir': $( if [ ! -z ${VIASH_PAR_TILEDB_DIR+x} ]; then echo "r'${VIASH_PAR_TILEDB_DIR//\'/\'\"\'\"r\'}'"; else echo None; fi )
}
meta = {
  'name': $( if [ ! -z ${VIASH_META_NAME+x} ]; then echo "r'${VIASH_META_NAME//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'functionality_name': $( if [ ! -z ${VIASH_META_FUNCTIONALITY_NAME+x} ]; then echo "r'${VIASH_META_FUNCTIONALITY_NAME//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'resources_dir': $( if [ ! -z ${VIASH_META_RESOURCES_DIR+x} ]; then echo "r'${VIASH_META_RESOURCES_DIR//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'executable': $( if [ ! -z ${VIASH_META_EXECUTABLE+x} ]; then echo "r'${VIASH_META_EXECUTABLE//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'config': $( if [ ! -z ${VIASH_META_CONFIG+x} ]; then echo "r'${VIASH_META_CONFIG//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'temp_dir': $( if [ ! -z ${VIASH_META_TEMP_DIR+x} ]; then echo "r'${VIASH_META_TEMP_DIR//\'/\'\"\'\"r\'}'"; else echo None; fi ),
  'cpus': $( if [ ! -z ${VIASH_META_CPUS+x} ]; then echo "int(r'${VIASH_META_CPUS//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_b': $( if [ ! -z ${VIASH_META_MEMORY_B+x} ]; then echo "int(r'${VIASH_META_MEMORY_B//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_kb': $( if [ ! -z ${VIASH_META_MEMORY_KB+x} ]; then echo "int(r'${VIASH_META_MEMORY_KB//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_mb': $( if [ ! -z ${VIASH_META_MEMORY_MB+x} ]; then echo "int(r'${VIASH_META_MEMORY_MB//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_gb': $( if [ ! -z ${VIASH_META_MEMORY_GB+x} ]; then echo "int(r'${VIASH_META_MEMORY_GB//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_tb': $( if [ ! -z ${VIASH_META_MEMORY_TB+x} ]; then echo "int(r'${VIASH_META_MEMORY_TB//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_pb': $( if [ ! -z ${VIASH_META_MEMORY_PB+x} ]; then echo "int(r'${VIASH_META_MEMORY_PB//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_kib': $( if [ ! -z ${VIASH_META_MEMORY_KIB+x} ]; then echo "int(r'${VIASH_META_MEMORY_KIB//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_mib': $( if [ ! -z ${VIASH_META_MEMORY_MIB+x} ]; then echo "int(r'${VIASH_META_MEMORY_MIB//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_gib': $( if [ ! -z ${VIASH_META_MEMORY_GIB+x} ]; then echo "int(r'${VIASH_META_MEMORY_GIB//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_tib': $( if [ ! -z ${VIASH_META_MEMORY_TIB+x} ]; then echo "int(r'${VIASH_META_MEMORY_TIB//\'/\'\"\'\"r\'}')"; else echo None; fi ),
  'memory_pib': $( if [ ! -z ${VIASH_META_MEMORY_PIB+x} ]; then echo "int(r'${VIASH_META_MEMORY_PIB//\'/\'\"\'\"r\'}')"; else echo None; fi )
}
dep = {
  
}

## VIASH END
sys.path.append(meta["resources_dir"])
from setup_logger import setup_logger

logger = setup_logger()

LAYER_ARGUMENTS = (
    "rna_raw_layer_input",
    "rna_raw_layer_output",
    "rna_normalized_layer_input",
    "rna_normalized_layer_output",
    "prot_raw_layer_input",
    "prot_raw_layer_output",
    "prot_normalized_layer_input",
    "prot_normalized_layer_output",
)


multidim_column_indices = {}


def _convert_to_array(mod_name, _, group):
    """
    tiledb SOMA does not support pandas dataframes in varm and obsm
    Convert those to structured arrays and store column index as metadata
    """
    entries = group.keys()
    for entry in entries:
        encoding = group[entry].attrs["encoding-type"]
        if encoding in ("array", "csr_matrix", "csc_matrix"):
            return
        if encoding in {
            "dataframe",
            "nullable-integer",
            "nullable-boolean",
            "nullable-string-array",
        }:
            df = anndata.io.read_elem(group[entry])
            data = df.to_numpy()
            anndata.io.write_elem(group, entry, data)
            index_to_write = df.columns
            # No need to store write the index to tileDB in the case its just the integer index
            if not isinstance(index_to_write, pd.RangeIndex):
                multidim_column_indices.setdefault(mod_name, {}).setdefault(
                    group.name.strip("/"), {}
                ).update({entry: index_to_write.to_list()})
            return
        logger.info(
            "Deleting %s because it is of encoding %s, which is not supported.",
            f"{group.name}/{entry}",
            encoding,
        )
        del group[entry]


def _read_obs(anndata_file):
    with h5py.File(anndata_file, "r") as open_rna:
        return _to_pandas_nullable(anndata.io.read_elem(open_rna["obs"]))


def _write_obs(anndata_file, obs_df):
    with h5py.File(anndata_file, "r+") as open_h5:
        anndata.io.write_elem(open_h5, "obs", obs_df)


def _log_arguments(function_obj, arg_dict):
    """
    Format a dictionairy of arguments into a string that is put into the script logs.
    """
    args_str = [f"\\t{param}: {param_val}\\n" for param, param_val in arg_dict.items()]
    logger.info(
        "Calling %s with arguments:\\n%s",
        function_obj.__name__,
        "".join(args_str).rstrip(),
    )


def _to_pandas_nullable(table_or_df: pa.Table | pd.DataFrame) -> pd.DataFrame:
    """
    Convert pyarrow Table or pandas dataframe to pandas dataframe that uses nullable data types.

    This function takes the following into account:
        1. AnnData supports writing BooleanDtype, IntegerDtype and StringDtype (which uses pd.NA)
        2. AnnData does *not* support FloatingDtype
        3. AnnData does support numpy-stype floating and integer dtypes (which use np.nan).
        4. TileDB SOMA's \`update_obs\` does not allow casting floats to integers (potential information loss).
        5. When pandas's \`convert_dtypes\` convert_integer argument is set to True,
           floats will be converted to integer if they can be 'faithfully' casted.

    Giving the previous constraints, it is *not* possible to only convert integers using \`convert_types\` because
    it might cause some float columns to be converted to integers (which is incompatible with tiledbsoma).
    Therefore, this function will leave integers and floats as-is.

    Based on the conversion provided by this function, a suitable na representation (pd.NA or np.NA) for a column can
    be chosen using \`pd.api.types.is_extension_array_dtype(column_dtype)\`
    """
    try:
        df = table_or_df.to_pandas(
            types_mapper=defaultdict(None, {pa.large_string(): pd.StringDtype()}).get
        )
    except AttributeError:
        df = table_or_df.convert_dtypes(
            infer_objects=False,
            convert_string=True,
            convert_integer=False,
            convert_boolean=False,
            convert_floating=False,
        )
    return df.convert_dtypes(
        infer_objects=False,
        convert_string=False,
        convert_integer=False,
        convert_boolean=True,
        convert_floating=False,
    )


def _get_temp_h5ad():
    return Path(NamedTemporaryFile(suffix=".h5ad", dir=meta["temp_dir"]).name)


def _h5mu_to_h5ad(h5mu_path, modality_name):
    """
    Create an anndata file from a modality in a mudata file by
    copying over the relevant h5 objects.
    """
    result = _get_temp_h5ad()
    with h5py.File(h5mu_path, "r") as open_anndata:
        with h5py.File(result, "w") as file_to_write:
            for h5_key in open_anndata["mod"][modality_name].keys():
                open_anndata.copy(
                    source=f"/mod/{modality_name}/{h5_key}", dest=file_to_write
                )
    return result


def _handle_layer_location(layer_name):
    """
    Handle the special case where 'X' is specified as layer
    this layer is not present in \`<mod_name>/layers/X\` but in \`<mod_name>/X\`.
    """
    prefix = "layers/" if layer_name != "X" else ""
    return f"{prefix}{layer_name}"


def _check_input_args(par, input, is_mudata):
    """
    Check the input arguments:
        * Arguments for protein modalities are not allowed when the input is not a MuData file.
        * Arguments for input layers may not point to the same layer
        * Output locations for layers may not be the same
        * Input modalities must not be the same
        * Input modalities must exist
    """
    if not is_mudata:
        for par_name in (
            "prot_modality",
            "prot_raw_layer_input",
            "prot_normalized_layer_input",
        ):
            if par[par_name]:
                raise ValueError(
                    f"'{par_name}' can not be used when the input is not a MuData file."
                )

    if par["prot_modality"]:
        for prot_arg in ("prot_raw_layer_input", "prot_raw_layer_output"):
            if not par[prot_arg]:
                ValueError(
                    f"When providing 'prot_modality', '{prot_arg}' must also be set."
                )

    NOT_SAME_VALUE = (
        ("rna_raw_layer_input", "rna_normalized_layer_input"),
        ("prot_raw_layer_input", "prot_normalized_layer_input"),
        ("rna_raw_layer_output", "rna_normalized_layer_output"),
        ("prot_raw_layer_output", "prot_normalized_layer_output"),
        ("rna_modality", "prot_modality"),
    )
    for layer1_arg, layer2_arg in NOT_SAME_VALUE:
        arg1_val, arg2_val = par[layer1_arg], par[layer2_arg]
        if (arg1_val == arg2_val) and arg1_val is not None:
            raise ValueError(
                f"The value for argument '{layer1_arg}' ({arg1_val}) must not be the same as for argument '{layer2_arg}' ({arg2_val})"
            )

    with h5py.File(input, "r") as open_h5:
        if "mod" in open_h5:
            available_modalities = tuple(open_h5["mod"].keys())
            for mod in (par["rna_modality"], par["prot_modality"]):
                if mod is not None and f"/mod/{mod}" not in open_h5:
                    raise ValueError(
                        f"Modality '{mod}' was not found in object, available modalities: {available_modalities}"
                    )


def _copy_layer(source: str, dest: str, input_h5ad: Path, mod_obj: h5py.Group):
    """
    Copy a h5py object from one location (source) to a new location (dest).
    The source location must be located in the input AnnData file, the destination
    will be written to a h5py Group object.
    """
    logger.info("Copying layer '%s' from '%s' to '%s'", source, input_h5ad, dest)
    if source == f"{mod_obj.name}/{dest}":
        logger.info("Layer is already at the correct location. Nothing to do.")
        return
    if not input_h5ad.is_file():
        raise FileNotFoundError(f"Could not link to {input_h5ad}: file does not exist.")
    with h5py.File(input_h5ad) as open_input:
        if source not in open_input:
            raise ValueError(f"Layer {source} was not found in {input_h5ad}.")
    if dest in mod_obj:
        logger.info(f"{dest} is already present. Removing before creating link.")
        del mod_obj[dest]
    # ExternalLink could have been used instead of 'copy', but it does not seem to work with tileDB
    with h5py.File(str(input_h5ad), "r") as open_input:
        open_input.copy(
            source=open_input[source], dest=mod_obj, name=dest, expand_external=True
        )
    logger.info("Copying complete.")


def _copy_column(src, dest, _, df_h5_obj):
    """
    Duplicate a column in a dataframe and give a new name.
    The input dataframe must be h5py object that is encoded by AnnData to store
    a pandas dataframe (e.g. .obs and .var).
    """
    logger.info("Copying column '%s' to '%s' for '%s'.", src, dest, df_h5_obj.name)
    columns = df_h5_obj.attrs["column-order"]
    if src == dest:
        logger.info("Source and destination for the column are the same, not copying.")
        return None
    if dest in columns:
        raise ValueError(f"Column {dest} already exists in {df_h5_obj.name}.")
    df_h5_obj.attrs["column-order"] = np.append(columns, dest)
    if src is None:
        # Use the index as source.
        src = df_h5_obj.attrs["_index"]
        # Both the index and columns are written as keys to df_h5_obj
        # An index name may be allowed to also be column name _only_ when
        # the column and the index have the same contents (this is an anndata requirement).
        # Here, this is always the case.
        if src in df_h5_obj.attrs["column-order"]:
            return None
    df_h5_obj.copy(src, dest, expand_refs=True, expand_soft=True)


def _set_index_name(name, _, df_h5_obj):
    """
    Set the index for a dataframe to an existing column.
    The input dataframe must be h5py object that is encoded by AnnData to store
    a pandas dataframe (e.g. .obs and .var).
    """
    logger.info("Setting index name of '%s' to '%s'.", df_h5_obj.name, name)
    original_index_name = df_h5_obj.attrs["_index"]
    # An index and a column may share a key in df_h5_obj
    # In this case we must not remove the original key from the object
    operator = (
        df_h5_obj.move
        if original_index_name not in df_h5_obj.attrs["column-order"]
        else df_h5_obj.copy
    )
    operator(original_index_name, name)
    df_h5_obj.attrs["_index"] = name
    logger.info("Done setting index name")


def _delete_all_keys(_, group_obj, exception=None):
    """
    Delete all keys from a h5py.Group object; which optional exceptions.
    """
    if exception is None:
        exception = ()
    logger.info(
        "Deleting all keys from '%s'%s.",
        group_obj.name,
        f" except {', '.join(exception)}" if exception else "",
    )
    keys_to_delete = set(group_obj.keys()) - set(exception)
    if not keys_to_delete:
        logger.info("No keys to delete.")
        return
    for key in keys_to_delete:
        del group_obj[key]
    logger.info("Done deleting keys %s.", ", ".join(keys_to_delete))


def _set_index_to_string_dtype(_, df_obj):
    """
    Change the index dataype for a dataframe to string when it is encoded as categorical.
    The dataframe must be provided as a h5py Group object that has been encoded by AnnData.
    """
    logger.info(
        "Checking if index of object '%s' is encoded as a categorical.", df_obj.name
    )
    index_col_name = df_obj.attrs["_index"]
    index_col = df_obj[index_col_name]
    logger.info("Column '%s' is being used as the index.", index_col_name)
    index_col_dtype = index_col.attrs["encoding-type"]
    if index_col_dtype == "categorical":
        logger.info(
            "Column '%s' is encoded as categorical, converting to string.",
            index_col_name,
        )
        is_ordered = bool(index_col.attrs.get("ordered", False))
        codes, categories = index_col["codes"], index_col["categories"]
        column = pd.Categorical.from_codes(codes, categories, is_ordered)
        column_str = column.astype(str).astype(object)
        del df_obj[index_col_name]
        df_obj.create_dataset(index_col_name, data=column_str)
        index_col.attrs["encoding-type"] = "string-array"
        logger.info("Conversion to string dtype complete.")
        return
    logger.info(
        "Column '%s' not encoded as categorical. Leaving as is.", index_col_name
    )


def _get_rna_conversion_specification(par):
    rna_spec = [
        partial(_copy_layer, par["rna_raw_layer_input"], par["rna_raw_layer_output"]),
        partial(
            _copy_layer,
            par["rna_normalized_layer_input"],
            par["rna_normalized_layer_output"],
        ),
        {
            "varm": partial(_convert_to_array, "rna"),
            "obsm": partial(_convert_to_array, "rna"),
            "uns": _delete_all_keys,
            "obsp": _delete_all_keys,
            "var": [
                partial(
                    _copy_column,
                    par["rna_var_gene_names_input"],
                    par["rna_var_gene_names_output"],
                ),
                partial(_set_index_name, par["rna_var_index_name_output"]),
                _set_index_to_string_dtype,
            ],
            "obs": partial(_set_index_name, par["obs_index_name_output"]),
            "layers": partial(
                _delete_all_keys,
                exception=(
                    par["rna_raw_layer_output"].removeprefix("layers/"),
                    par["rna_normalized_layer_output"].removeprefix("layers/"),
                ),
            ),
        },
    ]
    return rna_spec


def _get_prot_conversion_specification(par):
    prot_spec = [
        partial(_copy_layer, par["prot_raw_layer_input"], par["prot_raw_layer_output"]),
        partial(
            _copy_layer,
            par["prot_normalized_layer_input"],
            par["prot_normalized_layer_output"],
        ),
        {
            "varm": partial(_convert_to_array, "prot"),
            "obsm": partial(_convert_to_array, "prot"),
            "var": [
                partial(_set_index_name, par["prot_var_index_name_output"]),
                _set_index_to_string_dtype,
            ],
            "uns": _delete_all_keys,
            "obsp": _delete_all_keys,
            # "varm": _delete_all_keys,
            "layers": partial(
                _delete_all_keys,
                exception=(
                    par["prot_raw_layer_output"].removeprefix("layers/"),
                    par["prot_normalized_layer_output"].removeprefix("layers/"),
                ),
            ),
            "obs": partial(_set_index_name, par["obs_index_name_output"]),
        },
    ]

    return prot_spec


def _copy_anndata_and_convert_inplace(anndata_path, conversion_specification):
    converted_anndata = _get_temp_h5ad()
    copy2(anndata_path, converted_anndata)
    with h5py.File(converted_anndata, "r+") as open_prot_h5:
        apply_conversion(conversion_specification, anndata_path, open_prot_h5)
    return converted_anndata


@singledispatch
def apply_conversion(conversion_specification, input_h5ad, open_h5):
    raise NotImplementedError(
        f"Error: function 'apply_conversion' is not implemented for type '{type(conversion_specification)}'"
    )


@apply_conversion.register
def _(conversion_specification: list, input_h5ad, open_h5):
    for item in conversion_specification:
        apply_conversion(item, input_h5ad, open_h5)


@apply_conversion.register
def _(conversion_specifications: Mapping, input_h5ad, open_h5):
    for h5_key, conversion_spec in conversion_specifications.items():
        if hasattr(open_h5, h5_key):
            h5_obj = getattr(open_h5, h5_key)
        else:
            h5_obj = open_h5[h5_key]
        apply_conversion(conversion_spec, input_h5ad, h5_obj)


@apply_conversion.register
def _(conversion_specifications: Callable, input_h5ad, open_h5):
    conversion_specifications(input_h5ad, open_h5)


def _write_varm_obsm_indices(output_dir, mod_name, multidim_column_indices):
    # TileDB SOMA does not support column indices for .varm and obsm matrices
    # As a workaround, we will store these in the metadata slot
    for multidim_key in ("varm", "obsm"):
        collection_name = f"{str(output_dir)}/ms/{mod_name}/{multidim_key}"
        if tiledbsoma.Collection.exists(collection_name):
            with tiledbsoma.Collection.open(
                collection_name, "w"
            ) as multidim_collection:
                for item in multidim_collection:
                    index_to_write = (
                        multidim_column_indices.get(mod_name, {})
                        .get(multidim_key, {})
                        .get(item, None)
                    )
                    if index_to_write:
                        multidim_collection[item].metadata["column_index"] = json.dumps(
                            index_to_write
                        )


def _remove_directory(dir_path):
    """
    Check if a directory exists and remove it does.
    """
    if dir_path.exists():
        try:
            # Delete the directory and all its contents
            for item in dir_path.iterdir():
                if item.is_file() or item.is_symlink():
                    item.unlink()  # Removes files or symbolic links
                elif item.is_dir():
                    rmtree(item)  # Removes directories recursively
            logger.info("Directory '%s' has been deleted successfully.", dir_path)
        except FileNotFoundError:
            logger.info("Directory '%s' not found.", dir_path)
        except PermissionError as e:
            raise ValueError(
                f"Permission denied: Unable to delete '{dir_path}'."
            ) from e


def _align_obs_columns(experiment_df, anndata_df):
    def _create_na_columns(df_to_write, columns, dtype_df):
        """
        Create new columns in a dataframe with NA's as content while
        making sure that the datatype of the newly created columns match
        with those from another dataframe.
        """
        for col in columns:
            dtype = dtype_df[col].dtype
            na = pd.NA if pd.api.types.is_extension_array_dtype(dtype) else np.nan
            df_to_write.loc[:, col] = pd.Series(
                na, index=df_to_write.index, dtype=dtype
            )
        return df_to_write

    logger.info("Making sure obs columns are aligned.")
    contents_columns = set(experiment_df.columns)
    logger.info("Already ingested .obs columns: %s", ", ".join(contents_columns))
    logger.info("Retreiving obs columns to be added.")

    obs_to_add_columns = set(anndata_df.columns)

    logger.info("Checking dtype of common columns")
    common_columns = obs_to_add_columns.intersection(contents_columns)
    common_dtypes = {}
    for column_name in common_columns:
        # Combine the two columns, but only keep the calculated dtype.
        # We'll update the data types in the two original frames and let tiledb do the joining.
        new_series = experiment_df[column_name].combine_first(anndata_df[column_name])
        result_dtype = new_series.dtype
        # Nullable floats are not supported by anndata...
        if pd.api.types.is_float_dtype(
            result_dtype
        ) and pd.api.types.is_extension_array_dtype(result_dtype):
            result_dtype = result_dtype.numpy_dtype
        common_dtypes[column_name] = result_dtype

    logger.info(
        "Updating the dtypes to comply with the following schema: %s", common_dtypes
    )
    experiment_df, anndata_df = (
        experiment_df.astype(common_dtypes),
        anndata_df.astype(common_dtypes),
    )

    logger.info("Will add the following columns: %s", ", ".join(obs_to_add_columns))
    missing_columns_in_old = obs_to_add_columns - contents_columns
    logger.info(
        "Adding the following columns to the schema: %s.",
        ", ".join(missing_columns_in_old),
    )
    experiment_df = _create_na_columns(
        experiment_df, missing_columns_in_old, anndata_df
    )

    logger.info("Adjusting .obs succeeded!")
    missing_columns_in_new = (
        contents_columns
        - obs_to_add_columns
        - {"soma_joinid", par["obs_index_name_output"]}
    )
    logger.info(
        "Adding the following columns to modality to be ingested: %s",
        ", ".join(missing_columns_in_new),
    )
    anndata_df = _create_na_columns(anndata_df, missing_columns_in_new, experiment_df)
    return experiment_df, anndata_df


def main(par):
    logger.info(f"Component {meta['name']} started.")
    par["input"], par["tiledb_dir"] = Path(par["input"]), Path(par["tiledb_dir"])

    # Handle case where input is a mudat file
    with open(par["input"], "rb") as open_input_file:
        # It is possible to create a MuData compatible h5 file without using
        # MuData, and those could not have "MuData" as the first bytes.
        # But that is really an edge case and this check should hold.
        is_mudata = open_input_file.read(6) == b"MuData"

    _check_input_args(par, par["input"], is_mudata)

    # Reference layers other than "X" refer as "layer/<layer_name>"
    for arg_name in LAYER_ARGUMENTS:
        par[arg_name] = _handle_layer_location(par[arg_name])

    rna_input = par["input"]
    # Create a temporary file that holds the converted RNA h5ad
    if is_mudata:
        if not par["rna_modality"]:
            raise ValueError(
                "'rna_modality' argument must be set if the input is a MuData file."
            )
        # If the input is a MuData file, take the RNA modality and use that as input instead
        rna_input = _h5mu_to_h5ad(par["input"], par["rna_modality"])

    # Put a copy of the RNA input in the output file and convert it in-place
    rna_converted = _copy_anndata_and_convert_inplace(
        rna_input, _get_rna_conversion_specification(par)
    )

    if par["prot_modality"]:
        prot_input = _h5mu_to_h5ad(par["input"], par["prot_modality"])
        prot_converted = _copy_anndata_and_convert_inplace(
            prot_input, _get_prot_conversion_specification(par)
        )

        logger.info(
            "Making sure that obs columns are shared between the two modalities."
        )
        contents, obs_to_add = _read_obs(rna_converted), _read_obs(prot_converted)
        contents, obs_to_add = _align_obs_columns(contents, obs_to_add)
        _write_obs(prot_converted, obs_to_add)
        _write_obs(rna_converted, contents)

    tiledbsoma.logging.info()
    _remove_directory(par["tiledb_dir"])

    logger.info("Ingesting RNA modality")
    rna_from_h5ad_args = {
        "experiment_uri": str(par["tiledb_dir"]),
        "input_path": rna_converted,
        "measurement_name": par["rna_modality_output"],
        "uns_keys": [],
        "X_layer_name": "raw",
    }
    func_to_call = tiledbsoma.io.from_h5ad
    _log_arguments(func_to_call, rna_from_h5ad_args)
    func_to_call(**rna_from_h5ad_args)

    _write_varm_obsm_indices(par["tiledb_dir"], "rna", multidim_column_indices)

    logger.info("Done ingesting RNA modality")

    if par["prot_modality"]:
        logger.info("Ingesting protein modality")

        logger.info("Initalizing prot modality schema.")
        # Note the 'schema_only'
        prot_from_h5ad_schema_args = {
            "experiment_uri": str(par["tiledb_dir"]),
            "input_path": prot_converted,
            "measurement_name": par["prot_modality_output"],
            "uns_keys": [],
            "ingest_mode": "schema_only",
            "X_layer_name": "raw",
        }
        func_to_call = tiledbsoma.io.from_h5ad
        _log_arguments(func_to_call, prot_from_h5ad_schema_args)
        func_to_call(**prot_from_h5ad_schema_args)

        logger.info("Registering prot modality anndata")
        # Register the second anndata object in the protein measurement
        register_prot_args = {
            "experiment_uri": str(par["tiledb_dir"]),
            "h5ad_file_names": [prot_converted],
            "measurement_name": par["prot_modality_output"],
            "obs_field_name": par["obs_index_name_output"],
            "var_field_name": par["prot_var_index_name_output"],
            "append_obsm_varm": True,
            "allow_duplicate_obs_ids": True,  # Adding a new Measurement to existing observations
        }
        func_to_call = tiledbsoma.io.register_h5ads
        _log_arguments(func_to_call, register_prot_args)
        rd = func_to_call(**register_prot_args)

        rd.prepare_experiment(str(par["tiledb_dir"]))

        # Ingest the second anndata object into the protein measurement
        prot_write_args = {
            "experiment_uri": str(par["tiledb_dir"]),
            "input_path": prot_converted,
            "measurement_name": par["prot_modality_output"],
            "registration_mapping": rd,
            "ingest_mode": "write",
            "uns_keys": [],
            "X_layer_name": "raw",
        }
        func_to_call = tiledbsoma.io.from_h5ad
        _log_arguments(func_to_call, prot_write_args)
        func_to_call(**prot_write_args)

        _write_varm_obsm_indices(par["tiledb_dir"], "prot", multidim_column_indices)

    logger.info("Finished!")


if __name__ == "__main__":
    main(par)
VIASHMAIN
python -B "\$tempscript" &
wait "\$!"

VIASHEOF


if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then
  # strip viash automount from file paths
  
  if [ ! -z "$VIASH_PAR_INPUT" ]; then
    VIASH_PAR_INPUT=$(ViashDockerStripAutomount "$VIASH_PAR_INPUT")
  fi
  if [ ! -z "$VIASH_PAR_TILEDB_DIR" ]; then
    VIASH_PAR_TILEDB_DIR=$(ViashDockerStripAutomount "$VIASH_PAR_TILEDB_DIR")
  fi
  if [ ! -z "$VIASH_META_RESOURCES_DIR" ]; then
    VIASH_META_RESOURCES_DIR=$(ViashDockerStripAutomount "$VIASH_META_RESOURCES_DIR")
  fi
  if [ ! -z "$VIASH_META_EXECUTABLE" ]; then
    VIASH_META_EXECUTABLE=$(ViashDockerStripAutomount "$VIASH_META_EXECUTABLE")
  fi
  if [ ! -z "$VIASH_META_CONFIG" ]; then
    VIASH_META_CONFIG=$(ViashDockerStripAutomount "$VIASH_META_CONFIG")
  fi
  if [ ! -z "$VIASH_META_TEMP_DIR" ]; then
    VIASH_META_TEMP_DIR=$(ViashDockerStripAutomount "$VIASH_META_TEMP_DIR")
  fi
fi


# check whether required files exist
if [ ! -z "$VIASH_PAR_TILEDB_DIR" ] && [ ! -e "$VIASH_PAR_TILEDB_DIR" ]; then
  ViashError "Output file '$VIASH_PAR_TILEDB_DIR' does not exist."
  exit 1
fi


exit 0
