diff --git a/src/calng/AgipdCorrection.py b/src/calng/AgipdCorrection.py
index baaaa95c6e7b1649814bd7b7e9ba11a956778563..5c18952a35ba481c2225ad3edef58adb4d89490b 100644
--- a/src/calng/AgipdCorrection.py
+++ b/src/calng/AgipdCorrection.py
@@ -705,15 +705,14 @@ class AgipdCorrection(BaseCorrection):
 
         self._write_output(data_hash, metadata)
         if do_generate_preview:
-            self._write_combiner_previews(
+            self._write_preview_outputs(
                 (
                     ("preview.outputRaw", preview_raw),
                     ("preview.outputCorrected", preview_corrected),
                     ("preview.outputRawGain", preview_raw_gain),
                     ("preview.outputGainMap", preview_gain_map),
                 ),
-                train_id,
-                source,
+                metadata,
             )
 
     def _load_constant_to_runner(self, constant, constant_data):
diff --git a/src/calng/DetectorAssembler.py b/src/calng/DetectorAssembler.py
index aa5f00eced4bc4cc7b3620747ca2004360d35753..3cf5989a6b80aee725f34266b205a1d25375ab0f 100644
--- a/src/calng/DetectorAssembler.py
+++ b/src/calng/DetectorAssembler.py
@@ -37,6 +37,7 @@ from karabo import version as karaboVersion
 from . import scenes
 from ._version import version as deviceVersion
 
+
 assembled_schema = Schema()
 (
     NODE_ELEMENT(assembled_schema).key("image").commit(),
@@ -76,19 +77,9 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
             .setNewDefaultValue(["overview", "trainMatcherScene"])
             .commit(),
 
-            FLOAT_ELEMENT(expected)
-            .key("processingTime")
-            .unit(Unit.SECOND)
-            .metricPrefix(MetricPrefix.MILLI)
-            .readOnly()
-            .initialValue(0)
-            .warnHigh(500)
-            .info("Cannot keep up with GUI limit")
-            .needsAcknowledging(False)
-            .commit(),
-
             FLOAT_ELEMENT(expected)
             .key("timeOfFlight")
+            .displayedName("Time of flight")
             .unit(Unit.SECOND)
             .metricPrefix(MetricPrefix.MILLI)
             .readOnly()
@@ -151,8 +142,10 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
             .key("preview.trainStride")
             .displayedName("Train stride")
             .description("Only trains which are a multiple of this are sent to preview")
+            .unit(Unit.COUNT)
             .assignmentOptional()
             .defaultValue(10)
+            .reconfigurable()
             .commit(),
 
             STRING_ELEMENT(expected)
@@ -176,6 +169,10 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
             .commit(),
         )
 
+    def __init__(self, conf):
+        super().__init__(conf)
+        self.info.merge(Hash("timeOfFlight", 0))
+
     def initialization(self):
         super().initialization()
 
@@ -270,6 +267,7 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
         )
 
         module_indices_unfilled = set(range(self._stack_input_buffer.shape[0]))
+        earliest_source_timestamp = float("inf")
         for source, (data, source_timestamp) in sources.items():
             # regular TrainMatcher output
             self.output.write(data, ChannelMetaData(source, source_timestamp))
@@ -283,6 +281,9 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
                 data.get(self._path_to_stack)
             )
             module_indices_unfilled.discard(module_index)
+            earliest_source_timestamp = min(
+                earliest_source_timestamp, source_timestamp.toTimestamp()
+            )
 
         self.output.update()
         if bridge_output_choice is BridgeOutputOptions.MATCHED:
@@ -344,6 +345,9 @@ class DetectorAssembler(TrainMatcher.TrainMatcher):
                 )
                 self.zmq_output.update()
 
+        self.info["timeOfFlight"] = (
+            Timestamp().toTimestamp() - earliest_source_timestamp
+        ) * 1000
         self.rate_out.update()
 
     def on_new_data(self, channel, data, meta):
diff --git a/src/calng/DsscCorrection.py b/src/calng/DsscCorrection.py
index 650dcd47258dd6bfeddbfd8e948a82e0018a6db2..446cb064fa3090eac55c3f97094f145bbd01dd28 100644
--- a/src/calng/DsscCorrection.py
+++ b/src/calng/DsscCorrection.py
@@ -270,13 +270,12 @@ class DsscCorrection(BaseCorrection):
         data_hash.set("calngShmemPaths", [self._image_data_path])
         self._write_output(data_hash, metadata)
         if do_generate_preview:
-            self._write_combiner_previews(
+            self._write_preview_outputs(
                 (
                     ("preview.outputRaw", preview_raw),
                     ("preview.outputCorrected", preview_corrected),
                 ),
-                train_id,
-                source,
+                metadata,
             )
 
     def _load_constant_to_runner(self, constant, constant_data):
diff --git a/src/calng/JungfrauCorrection.py b/src/calng/JungfrauCorrection.py
index e78d671ba8c5919523b0286fc86d9386132c652e..8caa81c6d88a03fe48e6e84a76e567c8acd23262 100644
--- a/src/calng/JungfrauCorrection.py
+++ b/src/calng/JungfrauCorrection.py
@@ -383,14 +383,13 @@ class JungfrauCorrection(BaseCorrection):
         self._write_output(data_hash, metadata)
 
         if do_generate_preview:
-            self._write_combiner_previews(
+            self._write_preview_outputs(
                 (
                     ("preview.outputRaw", preview_raw),
                     ("preview.outputCorrected", preview_corrected),
                     ("preview.outputGainMap", preview_gain_map),
                 ),
-                train_id,
-                source,
+                metadata,
             )
 
     def _load_constant_to_runner(self, constant, constant_data):
diff --git a/src/calng/base_correction.py b/src/calng/base_correction.py
index db273a91f30e62a3cf78bb6e8f28f31b214270d1..42e9c95c812e84bdc6bb5953bead37bca0dab362 100644
--- a/src/calng/base_correction.py
+++ b/src/calng/base_correction.py
@@ -24,14 +24,12 @@ from karabo.bound import (
     VECTOR_STRING_ELEMENT,
     VECTOR_UINT32_ELEMENT,
     ChannelMetaData,
-    Epochstamp,
     Hash,
     MetricPrefix,
     PythonDevice,
     Schema,
     State,
     Timestamp,
-    Trainstamp,
     Unit,
 )
 from karabo.common.api import KARABO_SCHEMA_DISPLAY_TYPE_SCENES as DT_SCENES
@@ -531,6 +529,7 @@ class BaseCorrection(PythonDevice):
                 "Keep in mind that the GUI has limited refresh rate. Extra care must "
                 "be taken if DAQ train stride is >1."
             )
+            .unit(Unit.COUNT)
             .assignmentOptional()
             .defaultValue(1)
             .reconfigurable()
@@ -798,17 +797,16 @@ class BaseCorrection(PythonDevice):
         channel.write(data, metadata, False)
         channel.update()
 
-    def _write_combiner_previews(self, channel_data_pairs, train_id, source):
-        # TODO: send as ImageData (requires updated assembler)
+    def _write_preview_outputs(self, channel_data_pairs, old_metadata):
         # TODO: allow sending *all* frames for commissioning (request: Jola)
+        timestamp = Timestamp.fromHashAttributes(
+            old_metadata.getAttributes("timestamp")
+        )
         preview_hash = Hash()
-        preview_hash.set("image.trainId", train_id)
-
-        # note: have to construct because setting .tid after init is broken
-        timestamp = Timestamp(Epochstamp(), Trainstamp(train_id))
-        metadata = ChannelMetaData(source, timestamp)
+        preview_hash.set("image.trainId", timestamp.getTrainId())
+        metadata = ChannelMetaData(old_metadata.get("source"), timestamp)
         for channel_name, data in channel_data_pairs:
-            preview_hash.set(self._image_data_path, data)
+            preview_hash.set("image.data", data)
             channel = self.signalSlotable.getOutputChannel(channel_name)
             channel.write(preview_hash, metadata, False)
             channel.update()