Skip to content

Commit

Permalink
docs(scripts): add logging and informative messages to the octseg app…
Browse files Browse the repository at this point in the history
…lication
  • Loading branch information
Oli4 committed Jan 21, 2022
1 parent c25e805 commit 9c36c91
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 19 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,5 @@ docker load -i image_name.tar.gz
To use the image you need to start a container from the image, having the data mounted you want to process.

```shell
docker run -u $(id -u):$(id -g) -v YOUR_DATA_PATH:/home/data -it medvisbonn/octseg:0.1-cpu
docker run -u $(id -u):$(id -g) --gpus=all -v YOUR_DATA_PATH:/home/data -it medvisbonn/octseg:0.1-gpu
```
Empty file.
53 changes: 53 additions & 0 deletions octseg/scripts/commands/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import click
import logging
from octseg.scripts.utils import find_volumes

from tqdm import tqdm
import numpy as np
import eyepy as ep

logger = logging.getLogger("octseg.check")
logger.setLevel("INFO")


@click.command()
@click.pass_context
def check(
ctx: click.Context,
):
"""Check XML exports for common problems"""

input_path = ctx.obj["input_path"]

volumes = find_volumes(input_path)["xml"]

error_count_multiple = check_multiple_exports(volumes)
error_count_white = check_white_background(volumes)

if error_count_white + error_count_multiple == 0:
msg = "All XML exports are checked and 0 problems were found."
logger.info(msg)
else:
msg = f"Please fix {error_count_white+error_count_multiple} errors before processing your data."
logger.info(msg)

click.echo(msg)


def check_multiple_exports(v_paths):
errors = 0
for p in tqdm(v_paths, desc="Check for double exports: "):
if len(list(p.glob("*.xml"))) != 1:
logger.warning(f"{p.name}: Folder contains more than 1 XML export.")
errors += 1
return errors


def check_white_background(v_paths):
errors = 0
for p in tqdm(v_paths, desc="Check for inverted contrast: "):
data = ep.Oct.from_heyex_xml(p)
if np.mean(data[0].scan) > 128:
logger.warning(f"{p.name}: B-scans have inverted contrast.")
errors += 1
return errors
18 changes: 13 additions & 5 deletions octseg/scripts/drusen.py → octseg/scripts/commands/drusen.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from octseg.scripts.utils import find_volumes
from octseg.grids import grid

logger = logging.getLogger(__name__)
logger = logging.getLogger("octseg.drusen")


@click.command()
Expand Down Expand Up @@ -91,10 +91,18 @@ def drusen(ctx: click.Context, drusen_threshold, radii, sectors, offsets):
results.append(quantify_drusen(data, radii, sectors, offsets))

# Save quantification results as csv
csv = pd.DataFrame.from_records(results)
csv = csv.set_index(["Visit", "Laterality"])
csv = csv.sort_index()
csv.to_csv(output_path / f"drusen_results.csv")
if len(results) > 0:
csv = pd.DataFrame.from_records(results)
csv = csv.set_index(["Visit", "Laterality"])
csv = csv.sort_index()
csv.to_csv(output_path / f"drusen_results.csv")

click.echo(f"Drusen quantification saved for {len(csv)} volumes.")

if len(no_layers_volumes) > 0:
click.echo(
f"No retinal layers found for {len(no_layers_volumes)} volumes. To predict layers run the 'layers' command."
)


def quantify_drusen(oct_obj, radii, n_sectors, offsets):
Expand Down
14 changes: 9 additions & 5 deletions octseg/scripts/layers.py → octseg/scripts/commands/layers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import click
from pathlib import Path
import logging

from importlib import resources
Expand All @@ -13,7 +12,7 @@
import eyepy as ep
import pickle

logger = logging.getLogger(__name__)
logger = logging.getLogger("octseg.layers")


@click.command()
Expand Down Expand Up @@ -46,7 +45,7 @@ def layers(ctx: click.Context, model_id, overwrite, gpu):
# Check if specified model is available
if not model_id in list(resources.contents(weights_resources)):
msg = f"A model with ID {model_id} is not available. Check 'octseg layers --help' for available models."
logging.error(msg)
logger.error(msg)
raise ValueError(msg)

# Find volumes
Expand All @@ -71,12 +70,15 @@ def layers(ctx: click.Context, model_id, overwrite, gpu):
tf.config.experimental.set_visible_devices(gpus[gpu], "GPU")
except IndexError:
msg = "No GPU found, using the CPU instead."
logging.info(msg)
logger.warning(msg)

data_readers = {"vol": ep.Oct.from_heyex_vol, "xml": ep.Oct.from_heyex_xml}
# Predict layers and save
for datatype, volumes in volumes.items():
for path in tqdm(volumes):
for path in tqdm(
volumes,
desc="Volumes: ",
):
# Load data
data = data_readers[datatype](path)

Expand All @@ -88,6 +90,8 @@ def layers(ctx: click.Context, model_id, overwrite, gpu):
with open(output_dir / ("layers.pkl"), "wb") as myfile:
pickle.dump(data.layers, myfile)

click.echo("\nPredicted OCT layers are saved. You can now use the 'drusen' command")


def get_layers(data, model_id):
layer_model, model_config = load_model(model_id, (512, data[0].shape[1], 1))
Expand Down
33 changes: 27 additions & 6 deletions octseg/scripts/main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import click
from pathlib import Path
from octseg.scripts.layers import layers
from octseg.scripts.drusen import drusen
from octseg.scripts.commands.layers import layers
from octseg.scripts.commands.drusen import drusen
from octseg.scripts.commands.check import check

import logging
import warnings

logger = logging.getLogger("octseg")

formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch = logging.StreamHandler()
ch.setLevel("WARNING")
ch.setFormatter(formatter)
logger.addHandler(ch)


@click.group()
Expand All @@ -23,7 +33,7 @@
@click.option(
"--log-level",
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
default="INFO",
default="WARNING",
help="Set the logging level for the script",
)
@click.pass_context
Expand All @@ -38,17 +48,28 @@ def main(ctx, input_path, output_path, log_level):
"""
ctx.ensure_object(dict)

logger = logging.getLogger(__name__)
logger.setLevel(getattr(logging, log_level))

if output_path is None:
output_path = Path(input_path) / "processed"
else:
output_path = Path(output_path)
output_path.mkdir(parents=True, exist_ok=True)

fh = logging.FileHandler(output_path / "octseg.log")
fh.setLevel("DEBUG")
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.setLevel(log_level)

if log_level != logging.DEBUG:
warnings.filterwarnings("ignore")

ctx.obj["input_path"] = Path(input_path)
ctx.obj["output_path"] = Path(output_path)


main.add_command(drusen)
main.add_command(layers)
main.add_command(check)
4 changes: 2 additions & 2 deletions scripts/make_docker_image.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
poetry build
DOCKER_BUILDKIT=1 docker build -t medvisbonn/octseg:0.1-gpu -f ./docker/Dockerfile --target gpu .
docker save -o ./dist/octseg_gpu_docker.tar medvisbonn/octseg:0.1-cpu
gzip ./dist/octseg_gpu_docker.tar
docker save -o ./dist/docker_octseg-0.1-gpu.tar medvisbonn/octseg:0.1-gpu
gzip ./dist/docker_octseg-0.1-gpu.tar

0 comments on commit 9c36c91

Please sign in to comment.