Skip to content

Commit

Permalink
feat: add singularity runner
Browse files Browse the repository at this point in the history
  • Loading branch information
raylim committed Aug 25, 2023
1 parent f8b5097 commit 93c0e34
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 39 deletions.
93 changes: 93 additions & 0 deletions src/luna/common/runners.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import spython

import docker


class DockerRunner:
def __init__(self, image, command, volumes_map, num_cores):
self._image = image
self._command = command
self._volumes = {}
self._num_cores = num_cores
for k, v in volumes_map.items():
self._volumes[k] = {"bind": v, "mode": "rw"}

def run(self):
client = docker.from_env()
container = client.containers.run(
volumes=self._volumes,
nano_cpus=int(self._num_cores * 1e9),
image=self._image,
command=self._command,
detach=False,
stream=True,
)
return container


class DockerRunnerBuilder:
def __init__(self):
self._instance = None

def __call__(self, image, command, volumes_map, num_cores, **_ignored):
if not self._instance:
self._instance = DockerRunner(image, command, volumes_map, num_cores)
return self._instance


class SingularityRunner:
def __init__(self, image, command, volumes_map, num_cores, use_gpu):
self._image = image
self._command = command
self._num_cores = num_cores
self._use_gpu = use_gpu
self._volumes = []
for k, v in volumes_map.items():
self._volumes.append(f"{k}:{v}")

def run(self):
executor = spython.main.Client.execute(
image=self._image,
command=self._command,
bind=self._volumes,
nv=self._use_gpu,
singularity_options=[f"cpus={self._num_cores}"],
stream=True,
)
return executor


class SingularityRunnerBuilder:
def __init__(self):
self._instance = None

def __call__(self, image, command, volumes_map, num_cores, use_gpu, **_ignored):
if not self._instance:
self._instance = SingularityRunner(
image, command, volumes_map, num_cores, use_gpu
)
return self._instance


class RunnerFactory:
def __init__(self):
self._builders = {}

def register_builder(self, key, builder):
self._builders[key] = builder

def create(self, key, **kwargs):
builder = self._builders.get(key)
if not builder:
raise ValueError(key)
return builder(**kwargs)


class RunnerProvider(RunnerFactory):
def get(self, runner_type, **kwargs):
return self.create(runner_type, **kwargs)


runner_provider = RunnerProvider()
runner_provider.register_builder("DOCKER", DockerRunnerBuilder())
runner_provider.register_builder("SINGULARITY", SingularityRunnerBuilder())
103 changes: 64 additions & 39 deletions src/luna/pathology/cli/run_stardist_cell_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pandas as pd
from loguru import logger

import docker
from luna.common.runners import runner_provider
from luna.common.utils import get_config, local_cache_urlpath, save_metadata, timed


Expand All @@ -19,6 +19,8 @@ def stardist_simple(
output_urlpath: str = ".",
debug_opts: str = "",
num_cores: int = 1,
image: str = "docker://mskmind/qupath-stardist:0.4.3",
use_singularity: bool = False,
storage_options: dict = {},
output_storage_options: dict = {},
local_config: str = "",
Expand All @@ -32,8 +34,11 @@ def stardist_simple(
image_type (str): qupath image type (BRIGHTFIELD_H_DAB)
output_urlpath (str): output url/path
debug_opts (str): debug options passed as arguments to groovy script
image (str): docker/singularity image
use_singularity (bool): use singularity instead of docker
storage_options (dict): storage options to pass to reading functions
output_storage_options (dict): storage options to pass to writing functions
local_config (str): local config yaml file
Returns:
pd.DataFrame: metadata about function call
Expand All @@ -56,6 +61,8 @@ def stardist_simple(
config["output_urlpath"],
config["debug_opts"],
config["num_cores"],
config["image"],
config["use_singularity"],
config["storage_options"],
config["output_storage_options"],
)
Expand All @@ -66,7 +73,7 @@ def stardist_simple(
logger.info("generated cell data:")
logger.info(df)

output_geojson_file = Path(output_path) / f"cell_detections.geojson"
output_geojson_file = Path(output_path) / "cell_detections.geojson"

properties = {
"cell_objects": str(output_header_file),
Expand All @@ -91,6 +98,8 @@ def stardist_simple_main(
output_urlpath: str,
debug_opts: str,
num_cores: int,
image: str,
use_singularity: bool,
storage_options: dict,
output_storage_options: dict,
) -> pd.DataFrame:
Expand All @@ -103,6 +112,8 @@ def stardist_simple_main(
image_type (str): qupath image type (BRIGHTFIELD_H_DAB)
output_urlpath (str): output url/path
debug_opts (str): debug options passed as arguments to groovy script
image (str): docker/singularity image
use_singularity (bool): use singularity instead of docker
storage_options (dict): storage options to pass to reading functions
output_storage_options (dict): storage options to pass to writing functions
Expand All @@ -113,30 +124,32 @@ def stardist_simple_main(
ofs, output_path = fsspec.core.url_to_fs(output_urlpath, **output_storage_options)

slide_filename = Path(slide_path).name
docker_image = "mskmind/qupath-stardist:current"
command = f"QuPath script --image /inputs/{slide_filename} --args [cellSize={cell_expansion_size},imageType={image_type},{debug_opts}] /scripts/stardist_simple.groovy"
logger.info(f"Launching QuPath via {docker_image} ...")
logger.info(f"Launching QuPath via {image} ...")
logger.info(
f"\tvolumes={slide_urlpath}:'/inputs/{slide_filename}', {slide_path}:'/output_dir'"
)
logger.info(f"\tnano_cpus={int(num_cores * 1e9)}")
logger.info(f"\timage='{docker_image}'")
logger.info(f"\timage='{image}'")
logger.info(f"\tcommand={command}")

client = docker.from_env()
container = client.containers.run(
volumes={
slide_path: {"bind": f"/inputs/{slide_filename}", "mode": "ro"},
output_path: {"bind": "/output_dir", "mode": "rw"},
},
nano_cpus=int(num_cores * 1e9),
image=docker_image,
command=command,
detach=True,
)
volumes_map = {
slide_path: f"/inputs/{slide_filename}",
output_path: "/output_dir",
}

runner_config = {
"image": image,
"command": command,
"num_cores": num_cores,
"volumes_map": volumes_map,
}
runner_type = "DOCKER"
if use_singularity:
runner_type = "SINGULARITY"

for line in container.logs(stream=True):
print(line.decode(), end="")
runner = runner_provider.get(runner_type, **runner_config)
runner.run()

stardist_output = Path(output_path) / "cell_detections.tsv"

Expand All @@ -158,15 +171,19 @@ def stardist_cell_lymphocyte(
output_urlpath: str = ".",
num_cores: int = 1,
use_gpu: bool = False,
image: str = "docker://mskmind/qupath-stardist:0.4.3",
use_singularity: bool = False,
storage_options: dict = {},
output_storage_options: dict = {},
):
"""Run stardist using qupath CLI
Args:
input_slide_image (str): path to slide image (virtual slide formats compatible with openslide, .svs, .tif, .scn, ...)
num_cores (int): Number of cores to use for CPU parallelization
slide_urlpath (str): url/path to slide image (virtual slide formats compatible with openslide, .svs, .tif, .scn, ...)
output_urlpath (str): output url/path
num_cores (int): Number of cores to use for CPU parallelization
use_gpu (bool): use GPU
use_singularity (bool): use singularity instead of docker
storage_options (dict): storage options to pass to reading functions
output_storage_options (dict): storage options to pass to writing functions
Expand All @@ -189,18 +206,19 @@ def stardist_cell_lymphocyte(
config["output_urlpath"],
config["num_cores"],
config["use_gpu"],
config["image"],
config["use_singularity"],
config["storage_options"],
config["output_storage_options"],
)


with fs.open(output_header_file, "wb") as of:
df.to_parquet(of)

logger.info("generated cell data:")
logger.info(df)

output_geojson_file = Path(output_path) / f"cell_detections.geojson"
output_geojson_file = Path(output_path) / "cell_detections.geojson"

properties = {
"cell_objects": str(output_header_file),
Expand All @@ -223,15 +241,19 @@ def stardist_cell_lymphocyte_main(
output_urlpath: str,
num_cores: int,
use_gpu: bool = False,
image: str = "docker://mskmind/qupath-stardist:0.4.3",
use_singularity: bool = False,
storage_options: dict = {},
output_storage_options: dict = {},
) -> pd.DataFrame:
"""Run stardist using qupath CLI
Args:
input_slide_image (str): path to slide image (virtual slide formats compatible with openslide, .svs, .tif, .scn, ...)
num_cores (int): Number of cores to use for CPU parallelization
slide_urlpath (str): url/path to slide image (virtual slide formats compatible with openslide, .svs, .tif, .scn, ...)
output_urlpath (str): output url/path
num_cores (int): Number of cores to use for CPU parallelization
use_gpu (bool): use GPU
use_singularity (bool): use singularity instead of docker
storage_options (dict): storage options to pass to reading functions
Returns:
Expand All @@ -246,30 +268,33 @@ def stardist_cell_lymphocyte_main(
qupath_cmd = "QuPath-gpu"

slide_filename = Path(slide_path).name
docker_image = "mskmind/qupath-tensorflow:latest"
command = f"{qupath_cmd} script --image /inputs/{slide_filename} /scripts/stardist_nuclei_and_lymphocytes.groovy"
logger.info("Launching docker container:")
logger.info(
f"\tvolumes={slide_path}:'/inputs/{slide_filename}', {output_path}:'/output_dir'"
)
logger.info(f"\tnano_cpus={int(num_cores * 1e9)}")
logger.info(f"\timage='{docker_image}'")
logger.info(f"\timage='{image}'")
logger.info(f"\tcommand={command}")

client = docker.from_env()
container = client.containers.run(
volumes={
slide_path: {"bind": f"/inputs/{slide_filename}", "mode": "ro"},
output_path: {"bind": "/output_dir", "mode": "rw"},
},
nano_cpus=int(num_cores * 1e9),
image=docker_image,
command=command,
detach=True,
)
volumes_map = {
slide_path: f"/inputs/{slide_filename}",
output_path: "/output_dir",
}

runner_config = {
"image": image,
"command": command,
"num_cores": num_cores,
"volumes_map": volumes_map,
"use_gpu": use_gpu,
}
runner_type = "DOCKER"
if use_singularity:
runner_type = "SINGULARITY"

for line in container.logs(stream=True):
print(line.decode(), end="")
runner = runner_provider.get(runner_type, **runner_config)
runner.run()

stardist_output = Path(output_path) / "cell_detections.tsv"

Expand Down

0 comments on commit 93c0e34

Please sign in to comment.