diff --git a/extensions/chaeo/accessors.py b/extensions/chaeo/accessors.py
new file mode 100644
index 0000000000000000000000000000000000000000..32fb702061c66469d32b84377f1d4c40c9fcd67e
--- /dev/null
+++ b/extensions/chaeo/accessors.py
@@ -0,0 +1,51 @@
+import numpy as np
+from model_server.accessors import InMemoryDataAccessor
+
+class MonoPatchStack(InMemoryDataAccessor):
+
+    def __init__(self, data):
+        """
+        A sequence of n monochrome images of the same size
+        :param data: either np.ndarray of dimensions YXn, or a list of np.ndarrays of size YX
+        """
+
+        if isinstance(data, np.ndarray): # interpret as YXZ
+            assert len(data.shape) == 3
+            self._data = np.expand_dims(data, 2)
+        elif isinstance(data, list): # list of YX patches
+            self._data = np.expand_dims(
+                np.moveaxis(
+                    np.array(data),
+                    [1, 2, 0],
+                    [0, 1, 2]),
+                2
+            )
+        else:
+            raise InvalidDataForPatchStackError(f'Cannot create accessor from {type(data)}')
+
+    def make_tczyx(self):
+        assert self.chroma == 1
+        tyx = np.moveaxis(
+            self.data[:, :, 0, :], # YX(C)Z
+            [2, 0, 1],
+            [0, 1, 2]
+        )
+        return np.expand_dims(tyx, (1, 2))
+
+    @property
+    def count(self):
+        return self.nz
+
+    def iat(self, i):
+        return self.data[:, :, 0, i]
+
+    def get_list(self):
+        n = self.nz
+        return [self.data[:, :, 0, zi] for zi in range(0, n)]
+
+
+class Error(Exception):
+    pass
+
+class InvalidDataForPatchStackError(Error):
+    pass
\ No newline at end of file
diff --git a/extensions/chaeo/products.py b/extensions/chaeo/products.py
index e665d878208321ee78f89f5b41a5724623505c09..a9310e3706b868073b588bba71b0cc199ce70ddf 100644
--- a/extensions/chaeo/products.py
+++ b/extensions/chaeo/products.py
@@ -9,6 +9,7 @@ from skimage.io import imsave
 from skimage.measure import find_contours, shannon_entropy
 from tifffile import imwrite
 
+from extensions.chaeo.accessors import MonoPatchStack
 from extensions.chaeo.annotators import draw_box_on_patch, draw_contours_on_patch
 from model_server.accessors import GenericImageDataAccessor, InMemoryDataAccessor
 from model_server.process import pad, rescale, resample_to_8bit
@@ -55,16 +56,13 @@ def _write_patch_to_file(where, fname, data):
     else:
         raise Exception(f'Unsupported file extension: {ext}')
 
-def export_patch_masks_from_zstack(
-    where: Path,
+def get_patch_masks_from_zmask_meta(
     stack: GenericImageDataAccessor,
     zmask_meta: list,
     pad_to: int = 256,
-    prefix='mask',
-):
-    exported = []
+) -> MonoPatchStack:
+    patches = []
     for mi in zmask_meta:
-        obj = mi['info']
         sl = mi['slice']
 
         rbb = mi['relative_bounding_box']
@@ -82,27 +80,48 @@ def export_patch_masks_from_zstack(
         if pad_to:
             patch = pad(patch, pad_to)
 
+        patches.append(patch)
+    return MonoPatchStack(patches)
+
+def export_patch_masks_from_zstack(
+    where: Path,
+    stack: GenericImageDataAccessor,
+    zmask_meta: list,
+    pad_to: int = 256,
+    prefix='mask',
+    **kwargs
+):
+    patches_acc = get_patch_masks_from_zmask_meta(
+        stack,
+        zmask_meta,
+        pad_to=pad_to,
+        **kwargs
+    )
+    assert len(zmask_meta) == patches_acc.count
+
+    exported = []
+    for i in range(0, len(zmask_meta)):
+        mi = zmask_meta[i]
+        obj = mi['info']
+        patch = patches_acc.iat(i)
         ext = 'png'
         fname = f'{prefix}-la{obj.label:04d}-zi{obj.zi:04d}.{ext}'
         _write_patch_to_file(where, fname, patch)
         exported.append(fname)
     return exported
 
-def export_patches_from_zstack(
-        where: Path,
+def get_patches_from_zmask_meta(
         stack: GenericImageDataAccessor,
         zmask_meta: list,
         rescale_clip: float = 0.0,
         pad_to: int = 256,
         make_3d: bool = False,
-        prefix='patch',
         focus_metric: str = None,
         **kwargs
-):
-
-    exported = []
+) -> MonoPatchStack:
+    patches = []
     for mi in zmask_meta:
-        obj = mi['info']
+
         sl = mi['slice']
         rbb = mi['relative_bounding_box']
         idx = mi['df_index']
@@ -177,6 +196,37 @@ def export_patches_from_zstack(
         if pad_to:
             patch = pad(patch, pad_to)
 
+        patches.append(patch)
+    return MonoPatchStack(patches)
+
+def export_patches_from_zstack(
+        where: Path,
+        stack: GenericImageDataAccessor,
+        zmask_meta: list,
+        rescale_clip: float = 0.0,
+        pad_to: int = 256,
+        make_3d: bool = False,
+        prefix='patch',
+        focus_metric: str = None,
+        **kwargs
+):
+    patches_acc = get_patches_from_zmask_meta(
+        stack,
+        zmask_meta,
+        rescale_clip=rescale_clip,
+        pad_to=pad_to,
+        make_3d=make_3d,
+        focus_metric=focus_metric,
+        **kwargs
+    )
+    assert len(zmask_meta) == patches_acc.count
+
+    exported = []
+    for i in range(0, len(zmask_meta)):
+        mi = zmask_meta[i]
+        patch = patches_acc.iat(i)
+        obj = mi['info']
+        idx = mi['df_index']
         ext = 'tif' if make_3d else 'png'
         fname = f'{prefix}-la{obj.label:04d}-zi{obj.zi:04d}.{ext}'
 
diff --git a/extensions/chaeo/tests/test_accessors.py b/extensions/chaeo/tests/test_accessors.py
new file mode 100644
index 0000000000000000000000000000000000000000..465f15e0e1507936e949a900585054bdb93d1ebe
--- /dev/null
+++ b/extensions/chaeo/tests/test_accessors.py
@@ -0,0 +1,27 @@
+import unittest
+
+import numpy as np
+
+from extensions.chaeo.accessors import MonoPatchStack
+
+
+
+class TestCziImageFileAccess(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def test_make_patch_stack_from_list(self):
+        w = 256
+        h = 512
+        n = 4
+        acc = MonoPatchStack(np.random.rand(h, w, n))
+        assert acc.count == n
+        assert acc.hw == (h, w)
+
+    def test_make_patch_stack_from_3d_array(self):
+        w = 256
+        h = 512
+        n = 4
+        acc = MonoPatchStack([np.random.rand(h, w) for _ in range(0, 4)])
+        assert acc.count == n
+        assert acc.hw == (h, w)
\ No newline at end of file