diff --git a/extensions/chaeo/examples/batch_run_patches.py b/extensions/chaeo/examples/batch_run_patches.py
index 0c27af401e263dd502c705cc81d362bb97bad5d8..948a131a03491f089185dc6bfebae4ec5dd339fb 100644
--- a/extensions/chaeo/examples/batch_run_patches.py
+++ b/extensions/chaeo/examples/batch_run_patches.py
@@ -60,7 +60,7 @@ if __name__ == '__main__':
 
         # parse and record results
         df = result['dataframe']
-        df['filename'] = ff.name
+        df['source_path'] = ff
         df.to_csv(where_output / 'df_objects.csv', **csv_args)
         pd.DataFrame(result['timer_results'], index=[0]).to_csv(where_output / 'timer_results.csv', **csv_args)
         pd.json_normalize(export_kwargs).to_csv(where_output / 'workflow_params.csv', **csv_args)
diff --git a/extensions/chaeo/products.py b/extensions/chaeo/products.py
index fe53dbfb13195eb1bcb06f36373f9eda3536a40d..7c97784b87d49e498937b79461704045b0c9f8f2 100644
--- a/extensions/chaeo/products.py
+++ b/extensions/chaeo/products.py
@@ -95,6 +95,7 @@ def export_patches_from_zstack(
         obj = mi['info']
         sl = mi['slice']
         rbb = mi['relative_bounding_box']
+        idx = mi['df_index']
 
         x0 = rbb['x0']
         y0 = rbb['y0']
@@ -142,6 +143,7 @@ def export_patches_from_zstack(
                 patch[:, :, bci, zi] = draw_box_on_patch(
                     patch[:, :, bci, zi],
                     ((x0, y0), (x1, y1)),
+                    linewidth=kwargs.get('bounding_box_linewidth', 1)
                 )
 
         if kwargs.get('draw_mask'):
@@ -168,7 +170,11 @@ def export_patches_from_zstack(
         ext = 'tif' if make_3d else 'png'
         fname = f'{prefix}-la{obj.label:04d}-zi{obj.zi:04d}.{ext}'
         _write_patch_to_file(where, fname, resample_to_8bit(patch))
-        exported.append(fname)
+
+        exported.append({
+            'df_index': idx,
+            'patch_filename': fname,
+        })
     return exported
 
 def export_3d_patches_with_focus_metrics(
@@ -206,6 +212,7 @@ def export_3d_patches_with_focus_metrics(
         obj = mi['info']
         sl = mi['slice']
         rbb = mi['relative_bounding_box']
+        idx = mi['df_index']
 
         patch = stack.data[sl]
 
@@ -242,9 +249,12 @@ def export_3d_patches_with_focus_metrics(
 
         fstem = f'{prefix}-la{obj.label:04d}-zi{obj.zi:04d}'
         _write_patch_to_file(where, fstem + '.tif', resample_to_8bit(patch))
-        exported.append(fstem + '.tif')
         me_df.to_csv(where / (fstem + '.csv'))
-        exported.append(fstem + '.csv')
+        exported.append({
+            'df_index': idx,
+            'patch_filename': fstem + '.tif',
+            'focus_metrics_filename': fstem + '.csv',
+        })
 
     return exported
 
diff --git a/extensions/chaeo/workflows.py b/extensions/chaeo/workflows.py
index c3d2e84b2f66b3954717eaaaa8fb97d3ee31cd86..62fe918b543fb5fa00f43e700bdbffa5cac8bfeb 100644
--- a/extensions/chaeo/workflows.py
+++ b/extensions/chaeo/workflows.py
@@ -1,5 +1,8 @@
 from pathlib import Path
 from typing import Dict
+from uuid import uuid4
+
+import pandas as pd
 
 from extensions.ilastik.models import IlastikPixelClassifierModel
 from extensions.chaeo.annotators import draw_boxes_on_3d_image
@@ -84,16 +87,22 @@ def export_patches_from_multichannel_zstack(
             stack,
             zmask_meta,
             prefix=fstem,
-            rescale_clip=0.0,
+            rescale_clip=0.001,
             make_3d=False,
             focus_metric='max_sobel',
             ch_white=4,
             ch_rgb_overlay=(3, None, None),
             draw_bounding_box=True,
             bounding_box_channel=1,
+            bounding_box_linewidth=2,
             overlay_gain=(0.1, 1.0, 1.0)
         )
+        df_patches = pd.DataFrame(files)
         ti.click('export_2d_patches')
+        # associate 2d patches, dropping labeled objects that were not exported as patches
+        df = pd.merge(df, df_patches, left_index=True, right_on='df_index').drop(columns='df_index')
+        # prepopulate patch UUID
+        df['patch_id'] = df.apply(lambda _: uuid4(), axis=1)
 
     if export_patch_masks:
         files = export_patch_masks_from_zstack(
diff --git a/extensions/chaeo/zmask.py b/extensions/chaeo/zmask.py
index ecca8b2f65b428f8c0fd586e622aa74b2b6cd862..8d10c6e97a16a98702d4ad79aa531ae986e748af 100644
--- a/extensions/chaeo/zmask.py
+++ b/extensions/chaeo/zmask.py
@@ -115,6 +115,7 @@ def build_zmask_from_object_mask(
         assert rbb['y1'] <= (y1 - y0)
 
         meta.append({
+            'df_index': ob.Index,
             'info': ob,
             'slice': sl,
             'relative_bounding_box': rbb,