Skip to content
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-f https://download.pytorch.org/whl/cpu/torch-1.6.0%2Bcpu-cp37-cp37m-linux_x86_64.whl
torch>=1.6
pytorch-ignite==0.4.9
pytorch-ignite==0.4.10
numpy>=1.17
itk>=5.2
nibabel
Expand Down
1 change: 0 additions & 1 deletion monai/apps/auto3dseg/ensemble_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ def __init__(self, n_fold: int = 5):
self.n_fold = n_fold

def collect_algos(self):

"""
Rank the algos by finding the best model in each cross-validation fold
"""
Expand Down
5 changes: 0 additions & 5 deletions monai/apps/reconstruction/networks/nets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@


def reshape_complex_to_channel_dim(x: Tensor) -> Tensor:

"""
Swaps the complex dimension with the channel dimension so that the network treats real/imaginary
parts as two separate channels.
Expand All @@ -50,7 +49,6 @@ def reshape_complex_to_channel_dim(x: Tensor) -> Tensor:


def reshape_channel_complex_to_last_dim(x: Tensor) -> Tensor:

"""
Swaps the complex dimension with the channel dimension so that the network output has 2 as its last dimension

Expand Down Expand Up @@ -128,7 +126,6 @@ def reshape_batch_channel_to_channel_dim(x: Tensor, batch_size: int) -> Tensor:


def complex_normalize(x: Tensor) -> Tuple[Tensor, Tensor, Tensor]:

"""
Performs layer mean-std normalization for complex data. Normalization is done for each batch member
along each part (part refers to real and imaginary parts), separately.
Expand Down Expand Up @@ -171,7 +168,6 @@ def complex_normalize(x: Tensor) -> Tuple[Tensor, Tensor, Tensor]:
def divisible_pad_t(
x: Tensor, k: int = 16
) -> Tuple[Tensor, Tuple[Tuple[int, int], Tuple[int, int], Tuple[int, int], int, int, int]]:

"""
Pad input to feed into the network (torch script compatible)

Expand Down Expand Up @@ -234,7 +230,6 @@ def divisible_pad_t(
def inverse_divisible_pad_t(
x: Tensor, pad_sizes: Tuple[Tuple[int, int], Tuple[int, int], Tuple[int, int], int, int, int]
) -> Tensor:

"""
De-pad network output to match its original shape

Expand Down
1 change: 0 additions & 1 deletion monai/auto3dseg/algo_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class Algo:
"""

def set_data_stats(self, *args, **kwargs):

"""Provide dataset (and summaries) so that the model creation can depend on the input datasets."""
pass

Expand Down
91 changes: 58 additions & 33 deletions monai/fl/client/client_algo.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,71 +9,96 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
from typing import Optional

from monai.fl.utils.exchange_object import ExchangeObject


class ClientAlgo(abc.ABC):
class ClientAlgo:
"""
objective: provide an abstract base class for defining algo to run on any platform.
To define a new algo script, subclass this class and implement the
following abstract methods:

- #Algo.train()
- #Algo.get_weights()
- #Algo.evaluate()
- self.train()
- self.get_weights()
- self.evaluate()

initialize() and finalize() can be optionally be implemented to help with lifecycle management of the object.
initialize(), abort(), and finalize() can be optionally be implemented to help with lifecycle management
of the class object.
"""

def initialize(self, extra=None):
"""call to initialize the ClientAlgo class"""
def initialize(self, extra: Optional[dict] = None):
"""
Call to initialize the ClientAlgo class

Args:
extra: optional extra information, e.g. dict of `ExtraItems.CLIENT_NAME` and/or `ExtraItems.APP_ROOT`
Comment thread
holgerroth marked this conversation as resolved.
"""
pass

def finalize(self, extra=None):
"""call to finalize the ClientAlgo class"""
def finalize(self, extra: Optional[dict] = None):
"""
Call to finalize the ClientAlgo class

Args:
extra: optional extra information
"""
pass

def abort(self, extra=None):
"""call to abort the ClientAlgo training or evaluation"""
def abort(self, extra: Optional[dict] = None):
"""
Call to abort the ClientAlgo training or evaluation

Args:
extra: optional extra information
"""

pass

@abc.abstractmethod
def train(self, data: ExchangeObject, extra=None) -> None:
def train(self, data: ExchangeObject, extra: Optional[dict] = None) -> None:
"""
objective: train network and produce new network from train data.
# Arguments
data: ExchangeObject containing current network weights to base training on.
Train network and produce new network from train data.

Args:
data: ExchangeObject containing current network weights to base training on.
extra: optional extra information

Returns:
None
"""
raise NotImplementedError
raise NotImplementedError(f"Subclass {self.__class__.__name__} must implement this method.")
Comment thread
Nic-Ma marked this conversation as resolved.

@abc.abstractmethod
def get_weights(self, extra=None) -> ExchangeObject:
def get_weights(self, extra: Optional[dict] = None) -> ExchangeObject:
"""
objective: get current local weights or weight differences
Get current local weights or weight differences

# Returns
ExchangeObject: current local weights or weight differences.
Args:
extra: optional extra information

# Example returns ExchangeObject, e.g.::
Returns:
ExchangeObject: current local weights or weight differences.

ExchangeObject example::

ExchangeObject(
weights = self.trainer.network.state_dict(),
optim = None, # could be self.optimizer.state_dict()
weight_type = WeightType.WEIGHTS
)

"""
raise NotImplementedError
raise NotImplementedError(f"Subclass {self.__class__.__name__} must implement this method.")

@abc.abstractmethod
def evaluate(self, data: ExchangeObject, extra=None) -> ExchangeObject:
def evaluate(self, data: ExchangeObject, extra: Optional[dict] = None) -> ExchangeObject:
"""
objective: get evaluation metrics on test data.
# Arguments
data: ExchangeObject with network weights to use for evaluation
Get evaluation metrics on test data.

Args:
data: ExchangeObject with network weights to use for evaluation
extra: optional extra information

# Returns
metrics: ExchangeObject with evaluation metrics.
Returns:
metrics: ExchangeObject with evaluation metrics.
"""
raise NotImplementedError
raise NotImplementedError(f"Subclass {self.__class__.__name__} must implement this method.")
24 changes: 5 additions & 19 deletions monai/fl/client/monai_algo.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
import logging
import os
import sys
from typing import TYPE_CHECKING, Optional
from typing import Optional

import torch

import monai
from monai.bundle import ConfigParser
from monai.bundle.config_item import ConfigComponent, ConfigItem
from monai.config import IgniteInfo
from monai.fl.client.client_algo import ClientAlgo
from monai.fl.utils.constants import (
BundleKeys,
Expand All @@ -33,12 +32,7 @@
)
from monai.fl.utils.exchange_object import ExchangeObject
from monai.networks.utils import copy_model_state, get_state_dict
from monai.utils import min_version, optional_import

if TYPE_CHECKING:
from ignite.engine import Events
else:
Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events")
from monai.utils import min_version, require_pkg

logging.basicConfig(stream=sys.stdout, level=logging.INFO, format="%(asctime)s - %(message)s")

Expand Down Expand Up @@ -93,6 +87,7 @@ def disable_ckpt_loaders(parser):
h["_disabled_"] = True


@require_pkg(pkg_name="ignite", version="0.4.10", version_checker=min_version)
class MonaiAlgo(ClientAlgo):
"""
Implementation of ``ClientAlgo`` to allow federated learning with MONAI bundle configurations.
Expand Down Expand Up @@ -407,29 +402,20 @@ def abort(self, extra=None):
Args:
extra: Dict with additional information that can be provided by FL system.
"""

self.logger.info(f"Aborting {self.client_name} during {self.phase} phase.")
if isinstance(self.trainer, monai.engines.Trainer):
self.logger.info(f"Aborting {self.client_name} trainer...")
self.trainer.terminate()
self.trainer.state.dataloader_iter = self.trainer._dataloader_iter

if self.trainer.state.iteration % self.trainer.state.epoch_length == 0:
self.trainer._fire_event(Events.EPOCH_COMPLETED)
self.trainer.interrupt()
if isinstance(self.evaluator, monai.engines.Trainer):
self.logger.info(f"Aborting {self.client_name} evaluator...")
self.evaluator.terminate()
self.evaluator.interrupt()

def finalize(self, extra=None):
"""
Finalize the training or evaluation.
Args:
extra: Dict with additional information that can be provided by FL system.
"""

# TODO: finalize feature could be built into the MONAI Trainer class
if extra is None:
extra = {}
self.logger.info(f"Terminating {self.client_name} during {self.phase} phase.")
if isinstance(self.trainer, monai.engines.Trainer):
self.logger.info(f"Terminating {self.client_name} trainer...")
Expand Down
1 change: 0 additions & 1 deletion monai/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,6 @@ def label_union(x: List) -> List:


def prob2class(x, sigmoid: bool = False, threshold: float = 0.5, **kwargs):

"""
Compute the lab from the probability of predicted feature maps

Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Full requirements for developments
-r requirements-min.txt
pytorch-ignite==0.4.9
pytorch-ignite==0.4.10
gdown>=4.4.0
scipy
itk>=5.2
Expand Down
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ all =
pillow
tensorboard
gdown>=4.4.0
pytorch-ignite==0.4.9
pytorch-ignite==0.4.10
torchvision
itk>=5.2
tqdm>=4.47.0
Expand Down Expand Up @@ -67,7 +67,7 @@ tensorboard =
gdown =
gdown>=4.4.0
ignite =
pytorch-ignite==0.4.9
pytorch-ignite==0.4.10
torchvision =
torchvision
itk =
Expand Down