Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge master #23

Merged
merged 6 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion monai/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
print_gpu_info,
print_system_info,
)
from .type_definitions import IndexSelection, KeysCollection
from .type_definitions import DtypeLike, IndexSelection, KeysCollection, NdarrayTensor
2 changes: 1 addition & 1 deletion monai/config/deviceconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def get_system_info() -> OrderedDict:
_dict_append(
output,
"Avg. sensor temp. (Celsius)",
lambda: round(
lambda: np.round(
np.mean([item.current for sublist in psutil.sensors_temperatures().values() for item in sublist], 1)
),
)
Expand Down
20 changes: 18 additions & 2 deletions monai/config/type_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Collection, Hashable, Iterable, Union
from typing import Collection, Hashable, Iterable, TypeVar, Union

__all__ = ["KeysCollection", "IndexSelection"]
import numpy as np
import torch

__all__ = ["KeysCollection", "IndexSelection", "DtypeLike", "NdarrayTensor"]

"""Commonly used concepts
This module provides naming and type specifications for commonly used concepts
Expand Down Expand Up @@ -51,3 +54,16 @@
The indices must be integers, and if a container of indices is specified, the
container must be iterable.
"""

DtypeLike = Union[
np.dtype,
type,
None,
]
"""Type of datatypes
adapted from https://github.com/numpy/numpy/blob/master/numpy/typing/_dtype_like.py
"""

# Generic type which can represent either a numpy.ndarray or a torch.Tensor
# Unlike Union can create a dependence between parameter(s) / return(s)
NdarrayTensor = TypeVar("NdarrayTensor", np.ndarray, torch.Tensor)
6 changes: 4 additions & 2 deletions monai/data/csv_saver.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import numpy as np
import torch

from monai.utils import ImageMetaKey as Key


class CSVSaver:
"""
Expand Down Expand Up @@ -73,9 +75,9 @@ def save(self, data: Union[torch.Tensor, np.ndarray], meta_data: Optional[Dict]
meta_data: the meta data information corresponding to the data.

"""
save_key = meta_data["filename_or_obj"] if meta_data else str(self._data_index)
save_key = meta_data[Key.FILENAME_OR_OBJ] if meta_data else str(self._data_index)
self._data_index += 1
if torch.is_tensor(data):
if isinstance(data, torch.Tensor):
data = data.detach().cpu().numpy()
if not isinstance(data, np.ndarray):
raise AssertionError
Expand Down
10 changes: 7 additions & 3 deletions monai/data/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,10 @@ class SmartCacheDataset(CacheDataset):
3. Call `update_cache()` before every epoch to replace training items.
4. Call `shutdown()` when training ends.

Note:
This replacement will not work if setting the `multiprocessing_context` of DataLoader to `spawn`
or on windows(the default multiprocessing method is `spawn`) and setting `num_workers` greater than 0.

"""

def __init__(
Expand Down Expand Up @@ -601,7 +605,7 @@ def __init__(
if self._cache is None:
self._cache = self._fill_cache()
if self.cache_num >= len(data):
raise ValueError("cache_num must be smaller than dataset length to support replacement.")
warnings.warn("cache_num is greater or equal than dataset length, fall back to regular CacheDataset.")
if replace_rate <= 0:
raise ValueError("replace_rate must be greater than 0, otherwise, please use CacheDataset.")
self.num_replace_workers: int = num_replace_workers
Expand Down Expand Up @@ -637,7 +641,7 @@ def is_started(self):
"""
if self._replace_mgr is None:
return False
return self._replace_mgr.isAlive()
return self._replace_mgr.is_alive()

def start(self):
"""
Expand Down Expand Up @@ -688,7 +692,7 @@ def update_cache(self):
If the cache has been shutdown before, need to restart the `_replace_mgr` thread.

"""
if not self._replace_mgr.isAlive():
if not self._replace_mgr.is_alive():
self._restart()

# make sure update is done
Expand Down
3 changes: 2 additions & 1 deletion monai/data/image_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import numpy as np
from torch.utils.data import Dataset

from monai.config import DtypeLike
from monai.data.image_reader import ImageReader
from monai.transforms import LoadImage, Randomizable, apply_transform
from monai.utils import MAX_SEED, get_seed
Expand All @@ -36,7 +37,7 @@ def __init__(
transform: Optional[Callable] = None,
seg_transform: Optional[Callable] = None,
image_only: bool = True,
dtype: Optional[np.dtype] = np.float32,
dtype: DtypeLike = np.float32,
reader: Optional[Union[ImageReader, str]] = None,
*args,
**kwargs,
Expand Down
10 changes: 5 additions & 5 deletions monai/data/image_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import numpy as np
from torch.utils.data._utils.collate import np_str_obj_array_pattern

from monai.config import KeysCollection
from monai.config import DtypeLike, KeysCollection
from monai.data.utils import correct_nifti_header_if_necessary
from monai.utils import ensure_tuple, optional_import

Expand Down Expand Up @@ -244,7 +244,7 @@ def _get_affine(self, img) -> np.ndarray:
affine = np.eye(direction.shape[0] + 1)
affine[(slice(-1), slice(-1))] = direction @ np.diag(spacing)
affine[(slice(-1), -1)] = origin
return affine
return np.asarray(affine)

def _get_spatial_shape(self, img) -> np.ndarray:
"""
Expand All @@ -258,7 +258,7 @@ def _get_spatial_shape(self, img) -> np.ndarray:
shape.reverse()
return np.asarray(shape)

def _get_array_data(self, img) -> np.ndarray:
def _get_array_data(self, img):
"""
Get the raw array data of the image, converted to Numpy array.

Expand Down Expand Up @@ -295,7 +295,7 @@ class NibabelReader(ImageReader):

"""

def __init__(self, as_closest_canonical: bool = False, dtype: Optional[np.dtype] = np.float32, **kwargs):
def __init__(self, as_closest_canonical: bool = False, dtype: DtypeLike = np.float32, **kwargs):
super().__init__()
self.as_closest_canonical = as_closest_canonical
self.dtype = dtype
Expand Down Expand Up @@ -385,7 +385,7 @@ def _get_affine(self, img) -> np.ndarray:
img: a Nibabel image object loaded from a image file.

"""
return img.affine.copy()
return np.array(img.affine, copy=True)

def _get_spatial_shape(self, img) -> np.ndarray:
"""
Expand Down
12 changes: 7 additions & 5 deletions monai/data/nifti_saver.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
import numpy as np
import torch

from monai.config import DtypeLike
from monai.data.nifti_writer import write_nifti
from monai.data.utils import create_file_basename
from monai.utils import GridSampleMode, GridSamplePadMode
from monai.utils import ImageMetaKey as Key


class NiftiSaver:
Expand All @@ -36,8 +38,8 @@ def __init__(
mode: Union[GridSampleMode, str] = GridSampleMode.BILINEAR,
padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.BORDER,
align_corners: bool = False,
dtype: Optional[np.dtype] = np.float64,
output_dtype: Optional[np.dtype] = np.float32,
dtype: DtypeLike = np.float64,
output_dtype: DtypeLike = np.float32,
) -> None:
"""
Args:
Expand Down Expand Up @@ -94,13 +96,13 @@ def save(self, data: Union[torch.Tensor, np.ndarray], meta_data: Optional[Dict]
See Also
:py:meth:`monai.data.nifti_writer.write_nifti`
"""
filename = meta_data["filename_or_obj"] if meta_data else str(self._data_index)
filename = meta_data[Key.FILENAME_OR_OBJ] if meta_data else str(self._data_index)
self._data_index += 1
original_affine = meta_data.get("original_affine", None) if meta_data else None
affine = meta_data.get("affine", None) if meta_data else None
spatial_shape = meta_data.get("spatial_shape", None) if meta_data else None

if torch.is_tensor(data):
if isinstance(data, torch.Tensor):
data = data.detach().cpu().numpy()

filename = create_file_basename(self.output_postfix, filename, self.output_dir)
Expand All @@ -109,7 +111,7 @@ def save(self, data: Union[torch.Tensor, np.ndarray], meta_data: Optional[Dict]
while len(data.shape) < 4:
data = np.expand_dims(data, -1)
# change data to "channel last" format and write to nifti format file
data = np.moveaxis(data, 0, -1)
data = np.moveaxis(np.asarray(data), 0, -1)
write_nifti(
data,
file_name=filename,
Expand Down
9 changes: 5 additions & 4 deletions monai/data/nifti_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import numpy as np
import torch

from monai.config import DtypeLike
from monai.data.utils import compute_shape_offset, to_affine_nd
from monai.networks.layers import AffineTransform
from monai.utils import GridSampleMode, GridSamplePadMode, optional_import
Expand All @@ -27,12 +28,12 @@ def write_nifti(
affine: Optional[np.ndarray] = None,
target_affine: Optional[np.ndarray] = None,
resample: bool = True,
output_spatial_shape: Optional[Sequence[int]] = None,
output_spatial_shape: Union[Sequence[int], np.ndarray, None] = None,
mode: Union[GridSampleMode, str] = GridSampleMode.BILINEAR,
padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.BORDER,
align_corners: bool = False,
dtype: Optional[np.dtype] = np.float64,
output_dtype: Optional[np.dtype] = np.float32,
dtype: DtypeLike = np.float64,
output_dtype: DtypeLike = np.float32,
) -> None:
"""
Write numpy data into NIfTI files to disk. This function converts data
Expand Down Expand Up @@ -126,7 +127,7 @@ def write_nifti(
transform = np.linalg.inv(_affine) @ target_affine
if output_spatial_shape is None:
output_spatial_shape, _ = compute_shape_offset(data.shape, _affine, target_affine)
output_spatial_shape_ = list(output_spatial_shape)
output_spatial_shape_ = list(output_spatial_shape) if output_spatial_shape is not None else []
if data.ndim > 3: # multi channel, resampling each channel
while len(output_spatial_shape_) < 3:
output_spatial_shape_ = output_spatial_shape_ + [1]
Expand Down
9 changes: 5 additions & 4 deletions monai/data/png_saver.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from monai.data.png_writer import write_png
from monai.data.utils import create_file_basename
from monai.utils import ImageMetaKey as Key
from monai.utils import InterpolateMode


Expand Down Expand Up @@ -82,11 +83,11 @@ def save(self, data: Union[torch.Tensor, np.ndarray], meta_data: Optional[Dict]
:py:meth:`monai.data.png_writer.write_png`

"""
filename = meta_data["filename_or_obj"] if meta_data else str(self._data_index)
filename = meta_data[Key.FILENAME_OR_OBJ] if meta_data else str(self._data_index)
self._data_index += 1
spatial_shape = meta_data.get("spatial_shape", None) if meta_data and self.resample else None

if torch.is_tensor(data):
if isinstance(data, torch.Tensor):
data = data.detach().cpu().numpy()

filename = create_file_basename(self.output_postfix, filename, self.output_dir)
Expand All @@ -95,12 +96,12 @@ def save(self, data: Union[torch.Tensor, np.ndarray], meta_data: Optional[Dict]
if data.shape[0] == 1:
data = data.squeeze(0)
elif 2 < data.shape[0] < 5:
data = np.moveaxis(data, 0, -1)
data = np.moveaxis(np.asarray(data), 0, -1)
else:
raise ValueError(f"Unsupported number of channels: {data.shape[0]}, available options are [1, 3, 4]")

write_png(
data,
np.asarray(data),
file_name=filename,
output_spatial_shape=spatial_shape,
mode=self.mode,
Expand Down
4 changes: 2 additions & 2 deletions monai/data/png_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ def write_png(
data = np.expand_dims(data, 0) # make a channel
data = xform(data)[0] # first channel
if mode != InterpolateMode.NEAREST:
data = np.clip(data, _min, _max)
data = np.clip(data, _min, _max) # type: ignore

if scale is not None:
data = np.clip(data, 0.0, 1.0) # png writer only can scale data in range [0, 1]
data = np.clip(data, 0.0, 1.0) # type: ignore # png writer only can scale data in range [0, 1]
if scale == np.iinfo(np.uint8).max:
data = (scale * data).astype(np.uint8)
elif scale == np.iinfo(np.uint16).max:
Expand Down
4 changes: 2 additions & 2 deletions monai/data/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def rectify_header_sform_qform(img_nii):
return img_nii


def zoom_affine(affine: np.ndarray, scale: Sequence[float], diagonal: bool = True) -> np.ndarray:
def zoom_affine(affine: np.ndarray, scale: Sequence[float], diagonal: bool = True):
"""
To make column norm of `affine` the same as `scale`. If diagonal is False,
returns an affine that combines orthogonal rotation and the new scale.
Expand Down Expand Up @@ -379,7 +379,7 @@ def zoom_affine(affine: np.ndarray, scale: Sequence[float], diagonal: bool = Tru


def compute_shape_offset(
spatial_shape: np.ndarray, in_affine: np.ndarray, out_affine: np.ndarray
spatial_shape: Union[np.ndarray, Sequence[int]], in_affine: np.ndarray, out_affine: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
"""
Given input and output affine, compute appropriate shapes
Expand Down
2 changes: 1 addition & 1 deletion monai/engines/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

class IterationEvents(EventEnum):
"""
Addtional Events engine can register and trigger in the iteration process.
Additional Events engine can register and trigger in the iteration process.
Refer to the example in ignite: https://github.com/pytorch/ignite/blob/master/ignite/engine/events.py#L146
These Events can be triggered during training iteration:
`FORWARD_COMPLETED` is the Event when `network(image, label)` completed.
Expand Down
8 changes: 7 additions & 1 deletion monai/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,11 @@
from .stats_handler import StatsHandler
from .surface_distance import SurfaceDistance
from .tensorboard_handlers import TensorBoardImageHandler, TensorBoardStatsHandler
from .utils import evenly_divisible_all_gather, stopping_fn_from_loss, stopping_fn_from_metric, write_metrics_reports
from .utils import (
evenly_divisible_all_gather,
stopping_fn_from_loss,
stopping_fn_from_metric,
string_list_all_gather,
write_metrics_reports,
)
from .validation_handler import ValidationHandler
Loading