diff --git a/model_server/base/accessors.py b/model_server/base/accessors.py
index fec80c819ad808a1f8ca2afa09b6b0fd65a8b4fe..4b91eb5cda12dc79c3b1bd300c5630d1370d2c91 100644
--- a/model_server/base/accessors.py
+++ b/model_server/base/accessors.py
@@ -3,12 +3,12 @@ import os
 from pathlib import Path
 
 import numpy as np
-from skimage.io import imread
+from skimage.io import imread, imsave
 
 import czifile
 import tifffile
 
-from extensions.chaeo.accessors import InvalidDataForPatchStackError
+from base.process import make_rgb
 from model_server.base.process import is_mask
 
 class GenericImageDataAccessor(ABC):
@@ -184,33 +184,49 @@ class CziImageFileAccessor(GenericImageFileAccessor):
         return sc
 
 
-def write_accessor_data_to_file(fpath: Path, accessor: GenericImageDataAccessor, mkdir=True) -> bool:
+def write_accessor_data_to_file(fpath: Path, acc: GenericImageDataAccessor, mkdir=True) -> bool:
     """
     Export an image accessor to file.
     :param fpath: complete path including filename and extension
-    :param accessor: image accessor to be written
+    :param acc: image accessor to be written
     :param mkdir: create any needed subdirectories in fpath if True
     :return: True
     """
+    if 'P' in acc.shape_dict.keys():
+        raise FileWriteError(f'Can only write single-position accessor to file')
+    ext = fpath.suffix.upper()
 
     if mkdir:
         fpath.parent.mkdir(parents=True, exist_ok=True)
-    try:
+
+    if ext == '.PNG':
+        if acc.dtype != 'uint8':
+            raise FileWriteError(f'Invalid data type {acc.dtype}')
+        if acc.chroma == 1:
+            data = acc.data[:, :, 0, 0]
+        elif acc.chroma == 2:  # add a blank blue channel
+            data = make_rgb(acc.data)
+        else:  # preserve RGB order
+            data = acc.data[:, :, :, 0]
+        imsave(fpath, data, check_contrast=False)
+        return True
+
+    elif ext in ['.TIF', '.TIFF']:
         zcyx= np.moveaxis(
-            accessor.data, # yxcz
+            acc.data,  # yxcz
             [3, 2, 0, 1],
             [0, 1, 2, 3]
         )
-        if accessor.is_mask():
-            if accessor.dtype == 'bool':
+        if acc.is_mask():
+            if acc.dtype == 'bool':
                 data = (zcyx * 255).astype('uint8')
             else:
                 data = zcyx.astype('uint8')
             tifffile.imwrite(fpath, data, imagej=True)
         else:
             tifffile.imwrite(fpath, zcyx, imagej=True)
-    except:
-        raise FileWriteError(f'Unable to write data to file')
+    else:
+        raise FileWriteError(f'Unable to write data to file of extension {ext}')
     return True
 
 
@@ -256,7 +272,7 @@ class PatchStack(InMemoryDataAccessor):
         self._data = nda
 
     def iat(self, i):
-        return self.data[i, :, :, :, :]
+        return InMemoryDataAccessor(self.data[i, :, :, :, :])
 
     def iat_yxcz(self, i):
         return self.iat(i)
@@ -307,6 +323,7 @@ def make_patch_stack_from_file(fpath):  # interpret z-dimension as patch positio
     return PatchStack(pyxcz)
 
 
+
 class Error(Exception):
     pass
 
@@ -328,5 +345,8 @@ class InvalidAxisKey(Error):
 class InvalidDataShape(Error):
     pass
 
+class InvalidDataForPatchStackError(Error):
+    pass
+
 
 
diff --git a/model_server/base/roiset.py b/model_server/base/roiset.py
index 819f8552660f1051f2bf3e713faf74d21e58d508..a66f97414c61b163403abfb75d96c119f616400b 100644
--- a/model_server/base/roiset.py
+++ b/model_server/base/roiset.py
@@ -17,7 +17,6 @@ from model_server.base.accessors import GenericImageDataAccessor, InMemoryDataAc
 from model_server.base.models import InstanceSegmentationModel
 from model_server.base.process import pad, rescale, resample_to_8bit, make_rgb
 from base.annotators import draw_box_on_patch, draw_contours_on_patch, draw_boxes_on_3d_image
-from model_server.extensions.chaeo.accessors import write_patch_to_file
 from base.accessors import PatchStack
 from base.process import mask_largest_object
 
@@ -294,7 +293,7 @@ class RoiSet(object):
         for i, roi in enumerate(self):
             oc = np.unique(
                 mask_largest_object(
-                    obmap_patches.iat(i)
+                    obmap_patches.iat(i).data
                 )
             )[1]
             self._df.loc[roi.Index, 'classify_by_' + name] = oc
@@ -309,7 +308,7 @@ class RoiSet(object):
             patch = patches_acc.iat_yxcz(i)
             ext = 'png'
             fname = f'{prefix}-la{roi.label:04d}-zi{roi.zi:04d}.{ext}'
-            write_patch_to_file(where, fname, patch)
+            write_accessor_data_to_file(where / fname, patch)
             exported.append(fname)
         return exported
 
@@ -324,9 +323,10 @@ class RoiSet(object):
             fname = f'{prefix}-la{roi.label:04d}-zi{roi.zi:04d}.{ext}'
 
             if patch.dtype is np.dtype('uint16'):
-                write_patch_to_file(where, fname, resample_to_8bit(patch.data))
+                resampled = InMemoryDataAccessor(resample_to_8bit(patch.data))
+                write_accessor_data_to_file(where / fname, resampled)
             else:
-                write_patch_to_file(where, fname, patch)
+                write_accessor_data_to_file(where / fname, patch)
 
             exported.append({
                 'df_index': roi.Index,
diff --git a/model_server/extensions/chaeo/accessors.py b/model_server/extensions/chaeo/accessors.py
deleted file mode 100644
index 7c9f74e57caf5413e515e89eff49622967cd4c2c..0000000000000000000000000000000000000000
--- a/model_server/extensions/chaeo/accessors.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import numpy as np
-from skimage.io import imsave
-from tifffile import imwrite
-
-from base.process import make_rgb
-
-
-# TODO: move this as method in base.accessors
-def write_patch_to_file(where, fname, yxcz):
-    ext = fname.split('.')[-1].upper()
-    where.mkdir(parents=True, exist_ok=True)
-
-    if ext == 'PNG':
-        assert yxcz.dtype == 'uint8', f'Invalid data type {yxcz.dtype}'
-        assert yxcz.shape[2] <= 3, f'Cannot export images with more than 3 channels as PNGs'
-        assert yxcz.shape[3] == 1, f'Cannot export z-stacks as PNGs'
-        if yxcz.shape[2] == 1:
-            outdata = yxcz[:, :, 0, 0]
-        elif yxcz.shape[2] == 2:  # add a blank blue channel
-            outdata = make_rgb(yxcz)
-        else: # preserve RGB order
-            outdata = yxcz[:, :, :, 0]
-        imsave(where / fname, outdata, check_contrast=False)
-        return True
-
-    elif ext in ['TIF', 'TIFF']:
-        zcyx = np.moveaxis(yxcz, [3, 2, 0, 1], [0, 1, 2, 3])
-        imwrite(where / fname, zcyx, imagej=True)
-        return True
-
-    else:
-        raise Exception(f'Unsupported file extension: {ext}')
-
-
-class Error(Exception):
-    pass
-
-class InvalidDataForPatchStackError(Error):
-    pass
-
-
diff --git a/model_server/extensions/chaeo/batch_jobs/coloring_book.py b/model_server/extensions/chaeo/batch_jobs/coloring_book.py
index a3a9c5268a12cac204897d9ecd5dba4d8a648ad6..e65dd1d27345852d2687715de5d91cfd90f770b9 100644
--- a/model_server/extensions/chaeo/batch_jobs/coloring_book.py
+++ b/model_server/extensions/chaeo/batch_jobs/coloring_book.py
@@ -7,7 +7,7 @@ from skimage.measure import label, regionprops_table
 
 import tifffile
 
-from model_server.extensions.chaeo.accessors import MonoPatchStack
+from model_server.base.accessors import PatchStack
 from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
 from model_server.base.accessors import write_accessor_data_to_file, InMemoryDataAccessor
 
@@ -19,7 +19,7 @@ if __name__ == '__main__':
     min_area = 400
 
     tf = tifffile.imread(root / '20231008-162336-z04-TL.tif')
-    instack = MonoPatchStack(np.moveaxis(tf, 0, -1))
+    instack = PatchStack(np.moveaxis(tf, 0, -1))
     px_mod = IlastikPixelClassifierModel(params={'project_file': px_ilp})
     pxmaps = []
 
diff --git a/model_server/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py b/model_server/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py
index 5d9625b55b03cc25a8c9476d6ac27af166de38dd..04e0c20e31d2da78ab11bec16908333e56a7c74f 100644
--- a/model_server/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py
+++ b/model_server/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py
@@ -3,7 +3,7 @@ import numpy as np
 import pandas as pd
 import skimage
 
-from model_server.extensions.chaeo.accessors import MonoPatchStackFromFile
+from model_server.base.accessors import make_patch_stack_from_file
 from model_server.extensions.chaeo.models import generate_ilastik_object_classifier
 from extensions.ilastik.models import PatchStackObjectClassifier
 from model_server.base.accessors import GenericImageDataAccessor, write_accessor_data_to_file
@@ -69,9 +69,9 @@ if __name__ == '__main__':
     classifier_file = generate_ilastik_object_classifier(
         template_ilp,
         root / 'new_auto_obj.ilp',
-        MonoPatchStackFromFile(root / 'zstack_train_raw.tif'),
-        MonoPatchStackFromFile(root / 'zstack_train_mask.tif'),
-        MonoPatchStackFromFile(root / 'zstack_train_label.tif'),
+        make_patch_stack_from_file(root / 'zstack_train_raw.tif'),
+        make_patch_stack_from_file(root / 'zstack_train_mask.tif'),
+        make_patch_stack_from_file(root / 'zstack_train_label.tif'),
         label_names,
         allow_multiple_objects=False
     )
@@ -81,17 +81,17 @@ if __name__ == '__main__':
     infer_and_compare(
         classifier,
         'train',
-        MonoPatchStackFromFile(root / 'zstack_train_raw.tif'),
-        MonoPatchStackFromFile(root / 'zstack_train_mask.tif'),
-        MonoPatchStackFromFile(root / 'zstack_train_label.tif')
+        make_patch_stack_from_file(root / 'zstack_train_raw.tif'),
+        make_patch_stack_from_file(root / 'zstack_train_mask.tif'),
+        make_patch_stack_from_file(root / 'zstack_train_label.tif')
     )
 
     # run test set
     infer_and_compare(
         classifier,
         'test',
-        MonoPatchStackFromFile(root / 'zstack_test_raw.tif'),
-        MonoPatchStackFromFile(root / 'zstack_test_mask.tif'),
-        MonoPatchStackFromFile(root / 'zstack_test_label.tif'),
+        make_patch_stack_from_file(root / 'zstack_test_raw.tif'),
+        make_patch_stack_from_file(root / 'zstack_test_mask.tif'),
+        make_patch_stack_from_file(root / 'zstack_test_label.tif'),
     )
 
diff --git a/model_server/extensions/chaeo/models.py b/model_server/extensions/chaeo/models.py
index 58235d81ed8d56845d220cd6203b7ed22ec6624c..865537e67ba79738a82e5b7e63472210026657f2 100644
--- a/model_server/extensions/chaeo/models.py
+++ b/model_server/extensions/chaeo/models.py
@@ -5,7 +5,7 @@ import h5py
 import numpy as np
 import skimage
 
-from model_server.extensions.chaeo.accessors import PatchStack
+from model_server.base.accessors import PatchStack
 
 
 def generate_ilastik_object_classifier(
diff --git a/model_server/extensions/ilastik/tests/test_ilastik.py b/model_server/extensions/ilastik/tests/test_ilastik.py
index 5f765ba709eb30364eda1e2c9fa60db57f5c0f69..89fe2b98d1f8ea25702e8a221c6178e8fe3a645b 100644
--- a/model_server/extensions/ilastik/tests/test_ilastik.py
+++ b/model_server/extensions/ilastik/tests/test_ilastik.py
@@ -295,6 +295,6 @@ class TestIlastikObjectClassification(unittest.TestCase):
         res_patches, _ = self.object_classifier.infer(raw_patches, patch_masks)
         self.assertEqual(res_patches.count, self.roiset.count)
         for pi in range(0, res_patches.count):  # assert that there is only one nonzero label per patch
-            unique = np.unique(res_patches.iat(pi))
+            unique = np.unique(res_patches.iat(pi).data)
             self.assertEqual(len(unique), 2)
             self.assertEqual(unique[0], 0)