Repository: Project-MONAI/MONAI

Issue:
CI errors test_data_stats
failed randomly difficult to reproduce:
```
FAIL: test_file_0 (tests.test_data_statsd.TestDataStatsd)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/opt/conda/lib/python3.6/site-packages/parameterized/parameterized.py", line 530, in standalone_func
    return func(*(a + p.args), **p.kwargs)
  File "/opt/monai/tests/test_data_statsd.py", line 150, in test_file
    self.assertEqual(content, expected_print)
AssertionError: '' != 'test data statistics:\nShape: (2, 2)\nVal[60 chars].0\n'
+ test data statistics:
+ Shape: (2, 2)
+ Value range: (0, 2)
+ Value: [[0 1]
+  [1 2]]
+ Additional info: 1.0
```

```
FAIL: test_file_0 (tests.test_data_stats.TestDataStats)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/opt/conda/lib/python3.6/site-packages/parameterized/parameterized.py", line 530, in standalone_func
    return func(*(a + p.args), **p.kwargs)
DiceLoss iter: 0, acc: 0.5625
DiceLoss iter: 10, acc: 0.9375
DiceLoss iter: 20, acc: 1.0
  File "/opt/monai/tests/test_data_stats.py", line 136, in test_file
    self.assertEqual(content, expected_print)
AssertionError: '' != 'test data statistics:\nShape: (2, 2)\nVal[60 chars].0\n'
+ test data statistics:
+ Shape: (2, 2)
+ Value range: (0, 2)
+ Value: [[0 1]
+  [1 2]]
+ Additional info: 1.0
```


**Environment (please complete the following information; e.g. using `sh runtests.sh -v`):**
https://github.com/Project-MONAI/MONAI/runs/1477554074?check_suite_focus=true#step:3:5150

cc @IsaacYangSLA @YuanTingHsieh @behxyz @yanchengnv



Discussion:
looks like this error is becoming frequent, i'm attaching the log here
[ci_log.txt](https://github.com/Project-MONAI/MONAI/files/5628378/ci_log.txt)


Resolving patch:
diff --git a/monai/data/dataset.py b/monai/data/dataset.py
--- a/monai/data/dataset.py
+++ b/monai/data/dataset.py
@@ -153,7 +153,9 @@ def _pre_transform(self, item_transformed):
             random transform object
 
         """
-        for _transform in self.transform.transforms:  # pytype: disable=attribute-error
+        if not isinstance(self.transform, Compose):
+            raise ValueError("transform must be an instance of monai.transforms.Compose.")
+        for _transform in self.transform.transforms:
             # execute all the deterministic transforms
             if isinstance(_transform, Randomizable) or not isinstance(_transform, Transform):
                 break
@@ -171,8 +173,10 @@ def _post_transform(self, item_transformed):
             the transformed element through the random transforms
 
         """
+        if not isinstance(self.transform, Compose):
+            raise ValueError("transform must be an instance of monai.transforms.Compose.")
         start_post_randomize_run = False
-        for _transform in self.transform.transforms:  # pytype: disable=attribute-error
+        for _transform in self.transform.transforms:
             if (
                 start_post_randomize_run
                 or isinstance(_transform, Randomizable)
@@ -268,7 +272,9 @@ def _pre_transform(self, item_transformed):
         Returns:
             the transformed element up to the N transform object
         """
-        for i, _transform in enumerate(self.transform.transforms):  # type: ignore
+        if not isinstance(self.transform, Compose):
+            raise ValueError("transform must be an instance of monai.transforms.Compose.")
+        for i, _transform in enumerate(self.transform.transforms):
             if i == self.cache_n_trans:
                 break
             item_transformed = apply_transform(_transform, item_transformed)
@@ -284,7 +290,9 @@ def _post_transform(self, item_transformed):
         Returns:
             the final transformed result
         """
-        for i, _transform in enumerate(self.transform.transforms):  # type: ignore
+        if not isinstance(self.transform, Compose):
+            raise ValueError("transform must be an instance of monai.transforms.Compose.")
+        for i, _transform in enumerate(self.transform.transforms):
             if i >= self.cache_n_trans:
                 item_transformed = apply_transform(_transform, item_transformed)
         return item_transformed
@@ -471,7 +479,9 @@ def __init__(
             num_workers: the number of worker processes to use.
                 If num_workers is None then the number returned by os.cpu_count() is used.
         """
-        super().__init__(data=data, transform=transform if isinstance(transform, Compose) else Compose(transform))
+        if not isinstance(transform, Compose):
+            transform = Compose(transform)
+        super().__init__(data=data, transform=transform)
         self.cache_num = min(int(cache_num), int(len(data) * cache_rate), len(data))
         self.num_workers = num_workers
         if self.num_workers is not None:
@@ -494,7 +504,9 @@ def _load_cache_item(self, idx: int):
             idx: the index of the input data sequence.
         """
         item = self.data[idx]
-        for _transform in self.transform.transforms:  # type: ignore
+        if not isinstance(self.transform, Compose):
+            raise ValueError("transform must be an instance of monai.transforms.Compose.")
+        for _transform in self.transform.transforms:
             # execute all the deterministic transforms
             if isinstance(_transform, Randomizable) or not isinstance(_transform, Transform):
                 break
@@ -510,7 +522,9 @@ def __getitem__(self, index):
         if self._cache is None:
             self._cache = self._fill_cache()
         data = self._cache[index]
-        for _transform in self.transform.transforms:  # pytype: disable=attribute-error
+        if not isinstance(self.transform, Compose):
+            raise ValueError("transform must be an instance of monai.transforms.Compose.")
+        for _transform in self.transform.transforms:
             if start_run or isinstance(_transform, Randomizable) or not isinstance(_transform, Transform):
                 start_run = True
                 data = apply_transform(_transform, data)
diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py
--- a/monai/transforms/compose.py
+++ b/monai/transforms/compose.py
@@ -142,7 +142,7 @@ def randomize(self, data: Any) -> None:
         raise NotImplementedError(f"Subclass {self.__class__.__name__} must implement this method.")
 
 
-class Compose(Randomizable):
+class Compose(Randomizable, Transform):
     """
     ``Compose`` provides the ability to chain a series of calls together in a
     sequence. Each transform in the sequence must take a single argument and


Test patch:
diff --git a/tests/test_data_stats.py b/tests/test_data_stats.py
--- a/tests/test_data_stats.py
+++ b/tests/test_data_stats.py
@@ -133,7 +133,7 @@ def test_file(self, input_data, expected_print):
             transform._logger.removeHandler(handler)
             with open(filename, "r") as f:
                 content = f.read()
-                self.assertEqual(content, expected_print)
+            self.assertEqual(content, expected_print)
 
 
 if __name__ == "__main__":
diff --git a/tests/test_data_statsd.py b/tests/test_data_statsd.py
--- a/tests/test_data_statsd.py
+++ b/tests/test_data_statsd.py
@@ -147,7 +147,7 @@ def test_file(self, input_data, expected_print):
             transform.printer._logger.removeHandler(handler)
             with open(filename, "r") as f:
                 content = f.read()
-                self.assertEqual(content, expected_print)
+            self.assertEqual(content, expected_print)
 
 
 if __name__ == "__main__":
diff --git a/tests/test_integration_classification_2d.py b/tests/test_integration_classification_2d.py
--- a/tests/test_integration_classification_2d.py
+++ b/tests/test_integration_classification_2d.py
@@ -243,7 +243,7 @@ def test_training(self):
             repeated.append(results)
         np.testing.assert_allclose(repeated[0], repeated[1])
 
-    @TimedCall(seconds=500, skip_timing=not torch.cuda.is_available(), daemon=False)
+    @TimedCall(seconds=1000, skip_timing=not torch.cuda.is_available(), daemon=False, force_quit=False)
     def test_timing(self):
         self.train_and_infer()
 
diff --git a/tests/test_integration_segmentation_3d.py b/tests/test_integration_segmentation_3d.py
--- a/tests/test_integration_segmentation_3d.py
+++ b/tests/test_integration_segmentation_3d.py
@@ -85,7 +85,7 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0):
     if cachedataset == 2:
         train_ds = monai.data.CacheDataset(data=train_files, transform=train_transforms, cache_rate=0.8)
     elif cachedataset == 3:
-        train_ds = monai.data.LMDBDataset(data=train_files, transform=train_transforms)
+        train_ds = monai.data.LMDBDataset(data=train_files, transform=train_transforms, cache_dir=root_dir)
     else:
         train_ds = monai.data.Dataset(data=train_files, transform=train_transforms)
     # use batch_size=2 to load images and use RandCropByPosNegLabeld to generate 2 x 4 images for network training
diff --git a/tests/test_integration_workflows.py b/tests/test_integration_workflows.py
--- a/tests/test_integration_workflows.py
+++ b/tests/test_integration_workflows.py
@@ -302,9 +302,14 @@ def test_training(self):
             repeated.append(results)
         np.testing.assert_allclose(repeated[0], repeated[1])
 
-    @TimedCall(seconds=300, skip_timing=not torch.cuda.is_available(), daemon=False)
+    @TimedCall(
+        seconds=300,
+        skip_timing=not torch.cuda.is_available(),
+        daemon=False,
+    )
     def test_timing(self):
-        self.train_and_infer(idx=2)
+        if monai.config.get_torch_version_tuple() >= (1, 6):
+            self.train_and_infer(idx=2)
 
 
 if __name__ == "__main__":