Repository: openvinotoolkit/datumaro

Issue:
Rare test failures on windows CI
It doesn't fail every time.

Example: https://github.com/openvinotoolkit/datumaro/pull/407/checks?check_run_id=3359354762

```
__________________________ BytesImageTest.test_ctors __________________________

self = <tests.test_images.BytesImageTest testMethod=test_ctors>

    @mark_requirement(Requirements.DATUM_GENERAL_REQ)
    def test_ctors(self):
        with TestDir() as test_dir:
            path = osp.join(test_dir, 'path.png')
            image = np.ones([2, 4, 3])
            image_bytes = encode_image(image, 'png')
    
            for args in [
                { 'data': image_bytes },
                { 'data': lambda _: image_bytes },
                { 'data': lambda _: image_bytes, 'ext': '.jpg' },
                { 'data': image_bytes, 'path': path },
                { 'data': image_bytes, 'path': path, 'size': (2, 4) },
                { 'data': image_bytes, 'path': path, 'size': (2, 4) },
                { 'path': path },
                { 'path': path, 'size': (2, 4) },
            ]:
                with self.subTest(**args):
                    img = ByteImage(**args)
                    # pylint: disable=pointless-statement
                    self.assertEqual('data' in args, img.has_data)
                    if img.has_data:
>                       self.assertEqual(img, image)

tests\test_images.py:126: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
datumaro\util\image.py:366: in __eq__
    return super().__eq__(other)
datumaro\util\image.py:311: in __eq__
    return self.has_data and np.array_equal(self.data, other)
datumaro\util\image.py:282: in data
    data = self._data()
datumaro\util\image.py:227: in __call__
    image = self.loader(self.path)
datumaro\util\image.py:338: in <lambda>
    loader = lambda _: decode_image(self.get_bytes())
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

image_bytes = <object object at 0x0000025118C7B6D0>
dtype = <class 'numpy.float32'>

    def decode_image(image_bytes, dtype=np.float32):
        if _IMAGE_BACKEND == _IMAGE_BACKENDS.cv2:
            import cv2
>           image = np.frombuffer(image_bytes, dtype=np.uint8)
E           TypeError: a bytes-like object is required, not 'object'

datumaro\util\image.py:155: TypeError
```


Discussion:

Resolving patch:
diff --git a/datumaro/util/image.py b/datumaro/util/image.py
--- a/datumaro/util/image.py
+++ b/datumaro/util/image.py
@@ -12,6 +12,7 @@
 import os.path as osp
 import shlex
 import shutil
+import weakref
 
 import numpy as np
 
@@ -205,28 +206,28 @@ class lazy_image:
     def __init__(self, path, loader=None, cache=None):
         if loader is None:
             loader = load_image
-        self.path = path
-        self.loader = loader
+        self._path = path
+        self._loader = loader
 
         # Cache:
         # - False: do not cache
         # - None: use the global cache
         # - object: an object to be used as cache
         assert cache in {None, False} or isinstance(cache, object)
-        self.cache = cache
+        self._cache = cache
 
     def __call__(self):
         image = None
-        image_id = hash(self) # path is not necessary hashable or a file path
+        cache_key = weakref.ref(self)
 
-        cache = self._get_cache(self.cache)
+        cache = self._get_cache(self._cache)
         if cache is not None:
-            image = cache.get(image_id)
+            image = cache.get(cache_key)
 
         if image is None:
-            image = self.loader(self.path)
+            image = self._loader(self._path)
             if cache is not None:
-                cache.push(image_id, image)
+                cache.push(cache_key, image)
         return image
 
     @staticmethod
@@ -237,9 +238,6 @@ def _get_cache(cache):
             return None
         return cache
 
-    def __hash__(self):
-        return hash((id(self), self.path, self.loader))
-
 class Image:
     def __init__(self, data: Union[None, Callable, np.ndarray] = None,
             path: Optional[str] = None, loader: Optional[Callable] = None,


Test patch: