Skip to content

Commit f8789b9

Browse files
Merge pull request #310 from Dana-Farber-AIOS/v2.1.0
v2.1.0
2 parents 7e64b94 + a3cceaf commit f8789b9

32 files changed

+383
-133
lines changed

README.md

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,13 @@ Stable versions are available as tagged releases on GitHub, or as versioned rele
2424

2525
There are several ways to install `PathML`:
2626

27-
1. `pip install` from PyPI
28-
2. Clone repo to local machine and install from source
27+
1. `pip install` from PyPI (**recommended for users**)
28+
2. Clone repo to local machine and install from source (recommended for developers/contributors)
2929
3. Use the PathML Docker container
3030

31-
Option (1) is recommended for most users. It will install the latest versions of most packages.
32-
Option (2) is a deterministic environment setup, meaning that all package versions are pinned and it will install the
33-
pinned version of a package even if it is not the newest. The automated testing suite is run in this environment. This
34-
is the suggested installation method for users wanting to interface with the Mesmer model for IF workflows, and for
35-
contributors/developers. Option (3) uses the same environment from (2), but in a Docker
36-
container.
37-
38-
Options (1) and (2) require that you first install all external dependencies (namelt, JDK-8 and system libraries used
39-
by OpenSlide and OpenCV):
40-
41-
* Install external dependencies (Linux) with [Apt](https://ubuntu.com/server/docs/package-management):
42-
````
43-
sudo apt-get update && sudo apt-get install openslide-tools g++ gcc libblas-dev liblapack-dev python3-opencv
44-
````
31+
Options (1) and (2) require that you first install all external dependencies:
32+
* openslide
33+
* JDK 8
4534

4635
We recommend using conda for environment management.
4736
Download Miniconda [here](https://docs.conda.io/en/latest/miniconda.html)
@@ -50,12 +39,27 @@ Download Miniconda [here](https://docs.conda.io/en/latest/miniconda.html)
5039

5140
## Installation option 1: pip install
5241

53-
Create conda environment with dependencies:
42+
Create conda environment:
5443
````
55-
conda create --name pathml python=3.8 numpy=1.19.5 openjdk==8.0.152 -c conda-forge
44+
conda create --name pathml python=3.8
5645
conda activate pathml
5746
````
5847

48+
Install external dependencies (Linux) with [Apt](https://ubuntu.com/server/docs/package-management):
49+
````
50+
sudo apt-get install openslide-tools g++ gcc libblas-dev liblapack-dev
51+
````
52+
53+
Install external dependencies (MacOS) with [Brew](www.brew.sh):
54+
````
55+
brew install openslide
56+
````
57+
58+
Install [OpenJDK 8](https://openjdk.java.net/):
59+
````
60+
conda install openjdk==8.0.152
61+
````
62+
5963
Optionally install CUDA (instructions [here](#CUDA))
6064

6165
Install `PathML` from PyPI:
@@ -140,17 +144,6 @@ After installing PyTorch, optionally verify successful PyTorch installation with
140144
python -c "import torch; print(torch.cuda.is_available())"
141145
````
142146

143-
## Troubleshooting installation
144-
145-
Installation can be fragile at times due to external dependencies.
146-
If having difficulty installing, try the following:
147-
148-
* Look through the GitHub issues to see if someone else has run into the same problem before
149-
* Ensure that the correct versions of all dependencies are installed
150-
* Make sure to use a fresh conda environment
151-
* Use pip's `--no-cache-dir` to prevent using cached files
152-
* Use deterministic environment specifications such as those used in installation options (2) and (3)
153-
154147
# Using with Jupyter
155148

156149
Jupyter notebooks are a convenient way to work interactively. To use `PathML` in Jupyter notebooks:

docs/source/api_utils_reference.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ Utilities API
33

44
Documentation for various utilities from all modules.
55

6+
Logging Utils
7+
-------------
8+
9+
.. autoapiclass:: pathml.PathMLLogger
10+
611
Core Utils
712
----------
813

environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ dependencies:
2929
- scanpy==1.8.2
3030
- anndata==0.7.8
3131
- tqdm==4.62.3
32+
- loguru==0.5.3

pathml/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
from . import datasets as ds
88
from . import ml
99
from . import preprocessing as pp
10+
from ._logging import PathMLLogger
1011
from ._version import __version__

pathml/_logging.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""
2+
Copyright 2021, Dana-Farber Cancer Institute and Weill Cornell Medicine
3+
License: GNU GPL 2.0
4+
"""
5+
6+
from loguru import logger
7+
import functools
8+
import sys
9+
10+
11+
class PathMLLogger:
12+
"""
13+
Convenience methods for turning on or off and configuring logging for PathML.
14+
Note that this can also be achieved by interfacing with loguru directly
15+
16+
Example::
17+
18+
from pathml import PathMLLogger as pml
19+
20+
# turn on logging for PathML
21+
pml.enable()
22+
23+
# turn off logging for PathML
24+
pml.disable()
25+
26+
# turn on logging and output logs to a file named 'logs.txt', with colorization enabled
27+
pml.enable(sink="logs.txt", colorize=True)
28+
"""
29+
30+
logger.disable("pathml")
31+
logger.disable(__name__)
32+
33+
@staticmethod
34+
def disable():
35+
"""
36+
Turn off logging for PathML
37+
"""
38+
logger.disable("pathml")
39+
logger.disable(__name__)
40+
logger.info(
41+
"Disabled Logging For PathML! If you are seeing this, there is a problem"
42+
)
43+
44+
@staticmethod
45+
def enable(
46+
sink=sys.stderr,
47+
level="DEBUG",
48+
fmt="PathML:{level}:{time:HH:mm:ss} | {module}:{function}:{line} | {message}",
49+
**kwargs
50+
):
51+
"""
52+
Turn on and configure logging for PathML
53+
54+
Args:
55+
sink (str or io._io.TextIOWrapper, optional):
56+
Destination sink for log messages. Defaults to ``sys.stderr``.
57+
level (str):
58+
level of logs to capture. Defaults to 'DEBUG'.
59+
fmt (str):
60+
Formatting for the log message. Defaults to: 'PathML:{level}:{time:HH:mm:ss} | {module}:{function}:{line} | {message}'
61+
**kwargs (dict, optional):
62+
additional options passed to configure logger. See:
63+
`loguru documentation <https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add>`_
64+
"""
65+
logger.enable("pathml")
66+
logger.enable(__name__)
67+
# remove pre-configured logger (https://github.com/Delgan/loguru/issues/208#issuecomment-581002215)
68+
logger.remove(0)
69+
handler_id = logger.add(sink=sink, level=level, format=fmt, **kwargs)
70+
logger.info("Enabled Logging For PathML!")
71+
return handler_id
72+
73+
74+
# courtesy of the people at loguru
75+
# https://loguru.readthedocs.io/en/stable/resources/recipes.html#:~:text=or%20fallback%20policy.-,Logging%20entry%20and%20exit%20of%20functions%20with%20a%20decorator,-%EF%83%81
76+
def logger_wraps(*, entry=True, exit=True, level="DEBUG"):
77+
def wrapper(func):
78+
name = func.__name__
79+
80+
@functools.wraps(func)
81+
def wrapped(*args, **kwargs):
82+
logger_ = logger.opt(depth=1)
83+
if entry:
84+
logger_.bind(enter_exit=True).log(
85+
level, "Entering '{}' (args={}, kwargs={})", name, args, kwargs
86+
)
87+
result = func(*args, **kwargs)
88+
if exit:
89+
logger_.bind(enter_exit=True).log(
90+
level, "Exiting '{}' (result={})", name, result
91+
)
92+
return result
93+
94+
return wrapped
95+
96+
return wrapper

pathml/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
License: GNU GPL 2.0
44
"""
55

6-
__version__ = "2.0.4"
6+
__version__ = "2.1.0"

pathml/core/h5managers.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from collections import OrderedDict
1010

1111
import anndata
12+
from loguru import logger
1213
import h5py
1314
import numpy as np
1415
import pathml.core
@@ -95,7 +96,7 @@ def add_tile(self, tile):
9596
tile(pathml.core.tile.Tile): Tile object
9697
"""
9798
if str(tile.coords) in self.h5["tiles"].keys():
98-
print(f"Tile is already in tiles. Overwriting {tile.coords} inplace.")
99+
logger.info(f"Tile is already in tiles. Overwriting {tile.coords} inplace.")
99100
# remove old cells from self.counts so they do not duplicate
100101
if tile.counts:
101102
if "tile" in self.counts.obs.keys():
@@ -114,7 +115,6 @@ def add_tile(self, tile):
114115
raise ValueError(
115116
f"cannot add tile of shape {tile.image.shape}. Must match shape of existing tiles: {existing_shape}"
116117
)
117-
118118
if self.slide_type and tile.slide_type:
119119
# check that slide types match
120120
if tile.slide_type != self.slide_type:
@@ -127,7 +127,7 @@ def add_tile(self, tile):
127127

128128
# create a group for tile and write tile
129129
if str(tile.coords) in self.h5["tiles"]:
130-
print(f"overwriting tile at {str(tile.coords)}")
130+
logger.info(f"overwriting tile at {str(tile.coords)}")
131131
del self.h5["tiles"][str(tile.coords)]
132132
self.h5["tiles"].create_group(str(tile.coords))
133133
self.h5["tiles"][str(tile.coords)].create_dataset(
@@ -155,7 +155,9 @@ def add_tile(self, tile):
155155
# add tile-level masks
156156
for key, mask in tile.masks.items():
157157
self.h5["tiles"][str(tile.coords)]["masks"].create_dataset(
158-
str(key), data=mask, dtype="float16",
158+
str(key),
159+
data=mask,
160+
dtype="float16",
159161
)
160162

161163
# add coords
@@ -209,8 +211,7 @@ def get_tile(self, item):
209211
item = list(self.h5["tiles"].keys())[item]
210212
else:
211213
raise KeyError(
212-
f"invalid item type: {type(item)}. must getitem by coord (type tuple[int]),"
213-
f"index (type int), or name (type str)"
214+
f"invalid item type: {type(item)}. must getitem by coord (type tuple[int]), index (type int), or name (type str)"
214215
)
215216
tile = self.h5["tiles"][item]["array"][:]
216217

@@ -339,7 +340,7 @@ def remove_mask(self, key):
339340
f"masks keys must be of type(str) but key was passed of type {type(key)}"
340341
)
341342
if key not in self.h5["masks"].keys():
342-
raise KeyError("key is not in Masks")
343+
raise KeyError(f"key is not in Masks")
343344
del self.h5["masks"][key]
344345

345346
def get_slidetype(self):

pathml/core/masks.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from collections import OrderedDict
1010
import h5py
1111
import reprlib
12+
from loguru import logger
1213

1314
import pathml.core.h5managers
1415

@@ -31,7 +32,7 @@ def __init__(self, h5manager, masks=None):
3132
if masks:
3233
if not isinstance(masks, dict):
3334
raise ValueError(
34-
f"masks must be passed as dicts of the form key1:mask1,key2:mask2,..."
35+
"masks must be passed as dicts of the form {key1:mask1, key2:mask2, ...}"
3536
)
3637
for val in masks.values():
3738
if not isinstance(val, np.ndarray):

0 commit comments

Comments
 (0)