diff --git a/model_server/extensions/chaeo/products.py b/model_server/extensions/chaeo/products.py
index 3027f97cbfd1215ed222a4e8117b5af9f3a6cdd7..4b0d197f6a859c7cc88c03d3089799542e8fa2d9 100644
--- a/model_server/extensions/chaeo/products.py
+++ b/model_server/extensions/chaeo/products.py
@@ -118,7 +118,7 @@ def get_patches_from_zmask_meta(
             assert white_channel < raw.chroma
             stack = raw.data[:, :, [white_channel, white_channel, white_channel], :]
         else:
-            stack = np.zeros([*raw.shape[0:1], 3, raw.shape[3]], dtype=raw.dtype)
+            stack = np.zeros([*raw.shape[0:2], 3, raw.shape[3]], dtype=raw.dtype)
 
         for ii, ci in enumerate(rgb_overlay_channels):
             if ci is None:
@@ -128,12 +128,24 @@ def get_patches_from_zmask_meta(
             stack[:, :, ii, :] = _safe_add(
                 stack[:, :, ii, :], # either black or grayscale channel
                 rgb_overlay_weights[ii],
-                stack[:, :, ci, :]
+                raw.data[:, :, ci, :]
             )
     else:
-        if white_channel:
+        if white_channel:  # interpret as just a single channel
             assert white_channel < raw.chroma
-            stack = raw.data[:, :, [white_channel], :]
+            annotate_rgb = False
+            for k in ['contour_channel', 'bounding_box_channel', 'mask_channel']:
+                ca = kwargs.get(k)
+                if ca is None:
+                    continue
+                assert(ca < raw.chroma)
+                if ca != white_channel:
+                    annotate_rgb = True
+                    break
+            if annotate_rgb:  # make RGB patches anyway to include annotation color
+                stack = raw.data[:, :, [white_channel, white_channel, white_channel], :]
+            else: # make monochrome patches
+                stack = raw.data[:, :, [white_channel], :]
         else:
             stack = raw.data
 
diff --git a/model_server/extensions/chaeo/tests/test_zstack.py b/model_server/extensions/chaeo/tests/test_zstack.py
index 816c3f25eb5e56f019741097486cc8e512c5ccc5..6cf76c9c9e783f06948d2a8c6c655aec752cf7b0 100644
--- a/model_server/extensions/chaeo/tests/test_zstack.py
+++ b/model_server/extensions/chaeo/tests/test_zstack.py
@@ -39,6 +39,17 @@ class TestZStackDerivedDataProducts(unittest.TestCase):
         )
         write_accessor_data_to_file(output_path / 'seg_mask.tif', self.seg_mask)
 
+        id_map = get_label_ids(self.seg_mask)
+        self.roiset = RoiSet(
+            id_map,
+            self.stack,
+            params=RoiSetMetaParams(
+                expand_box_by=(128, 2),
+                mask_type='boxes',
+                filters={'area': {'min': 1e3, 'max': 1e4}},
+            )
+        )
+
     def test_zmask_makes_correct_boxes(self, mask_type='boxes', **kwargs):
         id_map = get_label_ids(self.seg_mask)
         roiset = RoiSet(
@@ -159,34 +170,20 @@ class TestZStackDerivedDataProducts(unittest.TestCase):
             InMemoryDataAccessor(img)
         )
 
-    def _setup_multichannel_tests(self, mask_type='boxes', **kwargs):
-        id_map = get_label_ids(self.seg_mask)
-        return RoiSet(
-            id_map,
-            self.stack,
-            params=RoiSetMetaParams(
-                expand_box_by=(128, 2),
-                mask_type=mask_type,
-                filters={'area': {'min': 1e3, 'max': 1e4}},
-            )
-        )
-
-    def test_make_mono_2d_patches_from_multichannel_zmask(self):
-        roiset = self._setup_multichannel_tests()
+    def test_multichannel_to_mono_2d_patches(self):
         files = export_multichannel_patches_from_zstack(
-            output_path / 'test_make_mono_2d_patches_from_multichannel_zmask',
-            roiset,
+            output_path / 'test_multichannel_to_mono_2d_patches',
+            self.roiset,
             white_channel=3,
             draw_bounding_box=True,
         )
         result = generate_file_accessor(Path(files[0]['location']) / files[0]['patch_filename'])
         self.assertEqual(result.chroma, 1)
 
-    def test_make_rgb_annotated_mono_2d_patches_from_multichannel_zmask(self):
-        roiset = self._setup_multichannel_tests()
+    def test_multichannnel_to_mono_2d_patches_rgb_bbox(self):
         files = export_multichannel_patches_from_zstack(
-            output_path / 'test_make_rgb_annotated_mono_2d_patches_from_multichannel_zmask',
-            roiset,
+            output_path / 'multichannnel_to_mono_2d_patches_rgb_bbox',
+            self.roiset,
             white_channel=3,
             draw_bounding_box=True,
             bounding_box_channel=1,
@@ -194,11 +191,10 @@ class TestZStackDerivedDataProducts(unittest.TestCase):
         result = generate_file_accessor(Path(files[0]['location']) / files[0]['patch_filename'])
         self.assertEqual(result.chroma, 3)
 
-    def test_make_multichannel_2d_patches_with_mask_overlay(self):
-        roiset = self._setup_multichannel_tests()
+    def test_multichannnel_to_rgb_2d_patches_bbox(self):
         files = export_multichannel_patches_from_zstack(
-            output_path / 'test_make_multichannel_2d_patches_with_mask_overlay',
-            roiset,
+            output_path / 'multichannnel_to_rgb_2d_patches_bbox',
+            self.roiset,
             white_channel=4,
             rgb_overlay_channels=(3, None, None),
             draw_mask=True,
@@ -208,26 +204,24 @@ class TestZStackDerivedDataProducts(unittest.TestCase):
         result = generate_file_accessor(Path(files[0]['location']) / files[0]['patch_filename'])
         self.assertEqual(result.chroma, 3)
 
-    def test_make_multichannel_2d_patches_with_contour_overlay(self):
-        roiset = self._setup_multichannel_tests()
+    def test_multichannnel_to_rgb_2d_patches_contour(self):
         files = export_multichannel_patches_from_zstack(
-            output_path / '2d_patches_chlorophyl_contour_overlay',
-            # InMemoryDataAccessor(self.stack.data),
-            roiset,
-            rgb_white_channel=4,
-            ch_rgb_overlay=(3, None, None),
+            output_path / 'multichannnel_to_rgb_2d_patches_contour',
+            self.roiset,
+            rgb_overlay_channels=(3, None, None),
             draw_contour=True,
             contour_channel=1,
-            overlay_gain=(0.1, 1.0, 1.0)
+            rgb_overlay_weights=(0.1, 1.0, 1.0)
         )
         result = generate_file_accessor(Path(files[0]['location']) / files[0]['patch_filename'])
         self.assertEqual(result.chroma, 3)
+        self.assertEqual(result.get_one_channel_data(2).data.max(), 0)  # blue channel is black
 
-    def test_make_2d_patches_as_multichannel_tif(self):
-        roiset = self._setup_multichannel_tests()
+    def test_multichannel_to_multichannel_tif_patches(self):
+        # roiset = self._setup_multichannel_tests()
         files = export_multichannel_patches_from_zstack(
-            output_path / '2d_patches_multichannel_tif',
-            roiset,
+            output_path / 'multichannel_to_multichannel_tif_patches',
+            self.roiset,
         )
         result = generate_file_accessor(Path(files[0]['location']) / files[0]['patch_filename'])
         self.assertEqual(result.chroma, 5)