Repository: Project-MONAI/MONAI

Issue:
Add SmartCacheHandler in MONAI workflow
**Is your feature request related to a problem? Please describe.**
In order to seamlessly use `SmartCacheDataset` in MONAI workflows, we can create a ignite handler to automatically `start`, `update` and `shutdown` based on events.



Discussion:

Resolving patch:
diff --git a/monai/handlers/__init__.py b/monai/handlers/__init__.py
--- a/monai/handlers/__init__.py
+++ b/monai/handlers/__init__.py
@@ -17,6 +17,7 @@
 from .metric_logger import MetricLogger
 from .roc_auc import ROCAUC
 from .segmentation_saver import SegmentationSaver
+from .smartcache_handler import SmartCacheHandler
 from .stats_handler import StatsHandler
 from .tensorboard_handlers import TensorBoardImageHandler, TensorBoardStatsHandler
 from .utils import *
diff --git a/monai/handlers/smartcache_handler.py b/monai/handlers/smartcache_handler.py
new file mode 100644
--- /dev/null
+++ b/monai/handlers/smartcache_handler.py
@@ -0,0 +1,78 @@
+# Copyright 2020 MONAI Consortium
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#     http://www.apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import TYPE_CHECKING
+
+from monai.data import SmartCacheDataset
+from monai.utils import exact_version, optional_import
+
+Events, _ = optional_import("ignite.engine", "0.3.0", exact_version, "Events")
+if TYPE_CHECKING:
+    from ignite.engine import Engine
+else:
+    Engine, _ = optional_import("ignite.engine", "0.3.0", exact_version, "Engine")
+
+
+class SmartCacheHandler:
+    """
+    Attach SmartCache logic to the engine in Ignite.
+    Mainly include the `start`, `update_cache`, and `shutdown` functions of SmartCacheDataset.
+
+    """
+
+    def __init__(self, smartcacher: SmartCacheDataset) -> None:
+        """
+        Args:
+            smartcacher: predefined SmartCacheDataset, will attach it to the engine.
+
+        Raises:
+            TypeError: When ``smartcacher`` is not a ``monai.data.SmartCacheDataset``.
+
+        """
+        if not isinstance(smartcacher, SmartCacheDataset):
+            raise TypeError("smartcacher must be a monai.data.SmartCacheDataset.")
+        self.smartcacher = smartcacher
+
+    def attach(self, engine: Engine) -> None:
+        """
+        Args:
+            engine: Ignite Engine, it can be a trainer, validator or evaluator.
+        """
+        engine.add_event_handler(Events.STARTED, self.started)
+        engine.add_event_handler(Events.EPOCH_COMPLETED, self.epoch_completed)
+        engine.add_event_handler(Events.COMPLETED, self.completed)
+
+    def started(self, engine: Engine) -> None:
+        """Callback for train or validation/evaluation started Event.
+        Start the replacement thread of SmartCacheDataset.
+
+        Args:
+            engine: Ignite Engine, it can be a trainer, validator or evaluator.
+        """
+        self.smartcacher.start()
+
+    def epoch_completed(self, engine: Engine) -> None:
+        """Callback for train or validation/evaluation epoch completed Event.
+        Update cache content with replacement data.
+
+        Args:
+            engine: Ignite Engine, it can be a trainer, validator or evaluator.
+        """
+        self.smartcacher.update_cache()
+
+    def completed(self, engine: Engine) -> None:
+        """Callback for train or validation/evaluation completed Event.
+        Stop the replacement thread of SmartCacheDataset.
+
+        Args:
+            engine: Ignite Engine, it can be a trainer, validator or evaluator.
+        """
+        self.smartcacher.shutdown()


Test patch:
diff --git a/tests/test_handler_smartcache.py b/tests/test_handler_smartcache.py
new file mode 100644
--- /dev/null
+++ b/tests/test_handler_smartcache.py
@@ -0,0 +1,47 @@
+# Copyright 2020 MONAI Consortium
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#     http://www.apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+import torch
+from ignite.engine import Engine
+
+from monai.data import SmartCacheDataset
+from monai.handlers import SmartCacheHandler
+
+
+class TestHandlerSmartCache(unittest.TestCase):
+    def test_content(self):
+        data = [0, 1, 2, 3, 4, 5, 6, 7, 8]
+        expected = [
+            [0, 1, 2, 3, 4],
+            [1, 2, 3, 4, 5],
+            [2, 3, 4, 5, 6],
+            [3, 4, 5, 6, 7],
+            [4, 5, 6, 7, 8],
+        ]
+
+        # set up engine
+        def _train_func(engine, batch):
+            self.assertListEqual(batch.tolist(), expected[engine.state.epoch - 1])
+
+        engine = Engine(_train_func)
+
+        # set up testing handler
+        dataset = SmartCacheDataset(data, transform=None, replace_rate=0.2, cache_num=5)
+        data_loader = torch.utils.data.DataLoader(dataset, batch_size=5)
+        SmartCacheHandler(dataset).attach(engine)
+
+        engine.run(data_loader, max_epochs=5)
+
+
+if __name__ == "__main__":
+    unittest.main()