diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
index 551870921..615049084 100644
--- a/include/libcamera/internal/software_isp/swstats_cpu.h
+++ b/include/libcamera/internal/software_isp/swstats_cpu.h
@@ -38,14 +38,6 @@ public:
 	SwStatsCpu(const CameraManager &cm);
 	~SwStatsCpu() = default;
 
-	/*
-	 * The combination of pipeline + sensor delays means that
-	 * exposure changes can take up to 3 frames to get applied,
-	 * Run stats once every 4 frames to ensure any previous
-	 * exposure changes have been applied.
-	 */
-	static constexpr uint32_t kStatPerNumFrames = 4;
-
 	bool isValid() const { return sharedStats_.fd().isValid(); }
 
 	const SharedFD &getStatsFD() { return sharedStats_.fd(); }
@@ -54,13 +46,14 @@ public:
 
 	int configure(const StreamConfiguration &inputCfg, unsigned int statsBufferCount = 1);
 	void setWindow(const Rectangle &window);
+	bool updating(uint32_t frame);
 	void startFrame(uint32_t frame);
 	void finishFrame(uint32_t frame, uint32_t bufferId);
 	void processFrame(uint32_t frame, uint32_t bufferId, MappedFrameBuffer &input);
 
 	void processLine0(uint32_t frame, unsigned int y, const uint8_t *src[], unsigned int statsBufferIndex = 0)
 	{
-		if (frame % kStatPerNumFrames)
+		if (!updating(frame))
 			return;
 
 		if ((y & ySkipMask_) || y < static_cast<unsigned int>(window_.y) ||
@@ -72,7 +65,7 @@ public:
 
 	void processLine2(uint32_t frame, unsigned int y, const uint8_t *src[], unsigned int statsBufferIndex = 0)
 	{
-		if (frame % kStatPerNumFrames)
+		if (!updating(frame))
 			return;
 
 		if ((y & ySkipMask_) || y < static_cast<unsigned int>(window_.y) ||
@@ -88,6 +81,14 @@ private:
 	using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[], SwIspStats &stats);
 	using processFrameFn = void (SwStatsCpu::*)(MappedFrameBuffer &in);
 
+	/*
+	 * The combination of pipeline + sensor delays means that
+	 * exposure changes can take up to 3 frames to get applied,
+	 * Run stats once every 4 frames to ensure any previous
+	 * exposure changes have been applied.
+	 */
+	static constexpr uint32_t kStatPerNumFrames = 4;
+
 	int setupStandardBayerOrder(BayerFormat::Order order);
 	/* Bayer 8 bpp unpacked */
 	void statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats);
diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
index 33933b11d..af5eaab93 100644
--- a/src/libcamera/software_isp/debayer_egl.cpp
+++ b/src/libcamera/software_isp/debayer_egl.cpp
@@ -646,9 +646,7 @@ void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output
 	metadata.planes()[0].bytesused = output->planes()[0].length;
 
 	/* Calculate stats for the whole frame */
-	if (frame % SwStatsCpu::kStatPerNumFrames) {
-		stats_->finishFrame(frame, 0);
-	} else {
+	if (stats_->updating(frame)) {
 		if (!inMapped) {
 			/*
 			 * The buffer was directly imported into EGL and thus
@@ -660,6 +658,8 @@ void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output
 			inMapped.emplace(input, MappedFrameBuffer::MapFlag::Read);
 		}
 		stats_->processFrame(frame, 0, inMapped.value());
+	} else {
+		stats_->finishFrame(frame, 0);
 	}
 	inDmaSyncer.reset();
 
diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
index 74633815e..0aa5597a9 100644
--- a/src/libcamera/software_isp/swstats_cpu.cpp
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -398,7 +398,7 @@ void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats)
  */
 void SwStatsCpu::startFrame(uint32_t frame)
 {
-	if (frame % kStatPerNumFrames)
+	if (!updating(frame))
 		return;
 
 	if (window_.width == 0)
@@ -420,7 +420,7 @@ void SwStatsCpu::startFrame(uint32_t frame)
  */
 void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId)
 {
-	bool valid = frame % kStatPerNumFrames == 0;
+	bool valid = updating(frame);
 
 	if (valid) {
 		sharedStats_->sum_ = RGB<uint64_t>({ 0, 0, 0 });
@@ -622,7 +622,7 @@ void SwStatsCpu::processBayerFrame2(MappedFrameBuffer &in)
  */
 void SwStatsCpu::processFrame(uint32_t frame, uint32_t bufferId, MappedFrameBuffer &input)
 {
-	if (frame % kStatPerNumFrames) {
+	if (!updating(frame)) {
 		finishFrame(frame, bufferId);
 		return;
 	}
@@ -634,4 +634,14 @@ void SwStatsCpu::processFrame(uint32_t frame, uint32_t bufferId, MappedFrameBuff
 	bench_.finishFrame();
 }
 
+/**
+ * \brief Determine whether stats update is required for the given frame
+ * \param[in] frame The frame number
+ * \return Whether the stats are updated for the given frame
+ */
+bool SwStatsCpu::updating(uint32_t frame)
+{
+	return frame % kStatPerNumFrames == 0;
+}
+
 } /* namespace libcamera */
