Skip to content

Commit 2fc9a57

Browse files
committed
[#1385] support shallow camera image payload change recording
1 parent 335b401 commit 2fc9a57

6 files changed

Lines changed: 52 additions & 35 deletions

File tree

docs/source/Learn/bskPrinciples/bskPrinciples-4.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ The recorder can also be configured to record only when the message payload cont
6161

6262
To turn off this mode use ``scRec.recordOnChange(False)``. The default argument of this method is ``True``.
6363

64-
This change-only mode is only available for message payload types that support field-wise equality comparison. Basilisk generates this support for payloads whose fields can be compared directly. If a payload contains an unknown field type, or a pointer field that cannot be compared safely, then ``recordOnChange()`` emits an error instead of silently falling back to normal interval recording. Use the regular recorder mode for these payloads, or add a ``PayloadEqualityTraits`` specialization when the payload can be compared safely.
64+
This change-only mode is only available for message payload types that support field-wise equality comparison. Basilisk generates this support for payloads whose fields can be compared directly. If a payload contains an unknown field type, or a pointer field without explicit comparison semantics, then ``recordOnChange()`` emits an error instead of silently falling back to normal interval recording. Use the regular recorder mode for these payloads, or add a ``PayloadEqualityTraits`` specialization only when the payload comparison behavior can be defined safely. For example, ``CameraImageMsgPayload`` supports change-only recording through a shallow metadata comparison. Its image pointer is compared by address, but the pointed-to image buffer is not deep-copied or compared by the recorder.
6565

6666
That is all that is required to set up message recording. Next the code initializes the simulation and executes it.
6767

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
- Updated message recorders so ``updateTimeInterval()`` reschedules the next recording opportunity when the minimum update time is changed between simulation runs.
22
- Added a message recorder mode to record only when message payload content changes after the minimum update time has elapsed.
33
- Added explicit errors when change-only recording is requested for payload types without supported equality comparison.
4+
- Added shallow metadata comparison support for ``CameraImageMsgPayload`` without comparing pointed-to image bytes.

src/architecture/messaging/_UnitTest/test_RecorderUpdateTimeInterval.py

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -177,61 +177,70 @@ def test_record_on_change_unsupported_payload():
177177

178178

179179
def test_record_on_change_camera_image_payload():
180-
"""CameraImageMsgPayload has a hand-written PayloadEqualityTraits specialization.
180+
"""Test CameraImageMsgPayload change-only recording with shallow metadata equality.
181181
182-
recordOnChange() should only store a frame when the scalar fields differ from the
183-
last recorded payload. imagePointer is left null here, so equality reduces to
184-
comparing timeTag, valid, cameraID, imageType, and imageBufferLength.
182+
CameraImageMsgPayload equality compares payload fields, including the image
183+
pointer address, but it does not compare the pointed-to image bytes.
185184
"""
186185

187186
sc_sim = SimulationBaseClass.SimBaseClass()
188187
sim_process = sc_sim.CreateNewProcess("testProcess")
189-
sim_process.addTask(sc_sim.CreateNewTask("testTask", macros.sec2nano(1.0)))
188+
189+
task_time = macros.sec2nano(1.0) # [ns]
190+
sim_process.addTask(sc_sim.CreateNewTask("testTask", task_time))
190191

191192
test_module = ChangingCameraModule()
192193
sc_sim.AddModelToTask("testTask", test_module)
193194

194-
minimum_record_time = macros.sec2nano(4.0)
195+
minimum_record_time = macros.sec2nano(4.0) # [ns]
195196
msg_recorder = test_module.cameraOutMsg.recorder(minimum_record_time)
196197
msg_recorder.recordOnChange()
197198
sc_sim.AddModelToTask("testTask", msg_recorder)
198199

199200
sc_sim.InitializeSimulation()
200-
sc_sim.ConfigureStopTime(macros.sec2nano(10.0))
201+
202+
final_stop_time = macros.sec2nano(10.0) # [ns]
203+
sc_sim.ConfigureStopTime(final_stop_time)
201204
sc_sim.ExecuteSimulation()
202205

203-
# cameraID changes at t = 5 s. With a 4 s minimum interval:
204-
# t = 0: recorded (cameraID = 0), next eligible = t = 4 s
205-
# t = 4: payload unchanged → skipped, next eligible = t = 8 s
206-
# t = 8: payload changed (cameraID = 1) → recorded
206+
first_record_time = macros.sec2nano(0.0) # [ns]
207+
first_eligible_change_time = macros.sec2nano(8.0) # [ns]
207208
expected_times = np.array([
209+
first_record_time,
210+
first_eligible_change_time,
211+
]) # [ns]
212+
expected_time_tags = np.array([
208213
macros.sec2nano(0.0),
209-
macros.sec2nano(8.0),
210-
])
211-
expected_camera_ids = np.array([0, 1])
214+
test_module.image_time_tag,
215+
]) # [ns]
212216

213217
np.testing.assert_array_equal(msg_recorder.times(), expected_times)
214-
np.testing.assert_array_equal(msg_recorder.cameraID, expected_camera_ids)
218+
np.testing.assert_array_equal(msg_recorder.timeTag, expected_time_tags)
215219

216220

217221
class ChangingCameraModule(sysModel.SysModel):
218-
"""Write a CameraImageMsgPayload whose cameraID steps up at a fixed simulation time."""
222+
"""Write a CameraImageMsgPayload whose metadata changes at a fixed time."""
219223

220224
def __init__(self):
221225
super().__init__()
222226
self.cameraOutMsg = messaging.CameraImageMsg()
223-
self.change_time = macros.sec2nano(5.0)
227+
self.change_time = macros.sec2nano(5.0) # [ns]
228+
self.image_time_tag = macros.sec2nano(5.0) # [ns]
224229

225230
def Reset(self, CurrentSimNanos):
226-
self._write(CurrentSimNanos)
231+
self.write_payload(CurrentSimNanos)
227232

228233
def UpdateState(self, CurrentSimNanos):
229-
self._write(CurrentSimNanos)
234+
self.write_payload(CurrentSimNanos)
230235

231-
def _write(self, CurrentSimNanos):
236+
def write_payload(self, CurrentSimNanos):
232237
payload = self.cameraOutMsg.zeroMsgPayload
233-
payload.cameraID = 1 if CurrentSimNanos >= self.change_time else 0
234238
payload.valid = 1
239+
payload.cameraID = 7
240+
payload.imageBufferLength = 10 # [bytes]
241+
payload.imageType = 1
242+
if CurrentSimNanos >= self.change_time:
243+
payload.timeTag = self.image_time_tag
235244
self.cameraOutMsg.write(payload, CurrentSimNanos, self.moduleID)
236245

237246

src/architecture/messaging/msgAutoSource/generatePayloadEqualityHeader.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,5 +163,6 @@ def generateEqualityHeader(meta: dict, payloadType: str, searchDir: str) -> str
163163
"// one or more fields have unsupported types (e.g., raw pointers).\n"
164164
"// To support recordOnChange() for this payload, add a manual\n"
165165
f"// PayloadEqualityTraits<{payloadTypeName}> specialization\n"
166-
"// in the payload header file, guarded by #ifdef __cplusplus.\n"
166+
"// in the payload header file, guarded by #ifdef __cplusplus, only\n"
167+
"// when the comparison semantics can be defined safely.\n"
167168
)

src/architecture/messaging/payloadEqualityTraits.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
2424
* To enable recordOnChange() for a payload type, provide a specialization either:
2525
* - automatically, via generatePayloadEqualityHeader.py (for fully-supported struct fields), or
2626
* - manually, as a PayloadEqualityTraits<T> specialization in the payload header file
27-
* (guarded by \#ifdef __cplusplus).
27+
* (guarded by \#ifdef __cplusplus) when the comparison semantics can be defined safely.
2828
*/
2929
template<typename T>
3030
struct PayloadEqualityTraits {

src/architecture/msgPayloadDefC/CameraImageMsgPayload.h

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,30 @@ typedef struct {
3636
int8_t imageType; //!< -- Number of channels in each pixel, RGB = 3, RGBA = 4
3737
}CameraImageMsgPayload;
3838

39-
#ifdef __cplusplus
40-
#include <cstring>
39+
#if defined(__cplusplus) && !defined(SWIG)
4140
#include "architecture/messaging/payloadEqualityTraits.h"
4241

42+
/*! @brief Enable shallow payload equality for CameraImageMsgPayload.
43+
*
44+
* The image pointer is compared by address only. The recorder stores this
45+
* payload structure, but it does not deep-copy or compare the pointed-to image
46+
* buffer. Thus, recordOnChange() detects changes in image metadata, such as
47+
* timeTag, cameraID, imageBufferLength, imageType, valid, or imagePointer
48+
* address, but it does not detect in-place changes to image bytes behind a
49+
* reused imagePointer.
50+
*/
4351
template<>
4452
struct PayloadEqualityTraits<CameraImageMsgPayload> {
4553
static constexpr bool supported = true;
4654
static bool equal(const CameraImageMsgPayload& lhs, const CameraImageMsgPayload& rhs) {
47-
if (lhs.timeTag != rhs.timeTag) return false;
48-
if (lhs.valid != rhs.valid) return false;
49-
if (lhs.cameraID != rhs.cameraID) return false;
50-
if (lhs.imageType != rhs.imageType) return false;
51-
if (lhs.imageBufferLength != rhs.imageBufferLength) return false;
52-
if (lhs.imagePointer == rhs.imagePointer) return true;
53-
if (!lhs.imagePointer || !rhs.imagePointer) return false;
54-
return std::memcmp(lhs.imagePointer, rhs.imagePointer, lhs.imageBufferLength) == 0;
55+
return lhs.timeTag == rhs.timeTag
56+
&& lhs.valid == rhs.valid
57+
&& lhs.cameraID == rhs.cameraID
58+
&& lhs.imagePointer == rhs.imagePointer
59+
&& lhs.imageBufferLength == rhs.imageBufferLength
60+
&& lhs.imageType == rhs.imageType;
5561
}
5662
};
57-
#endif /* __cplusplus */
63+
#endif /* defined(__cplusplus) && !defined(SWIG) */
5864

5965
#endif

0 commit comments

Comments
 (0)