dimanche 16 avril 2023

Architecture for a pipeline that requires multiple references for the same object in Rust

I am currently learning Rust and in order to have hands-on experience I have been playing around with a few small projects.

Currently I am doing a small Computer Vision pipeline for doing multiple different image processing (QRDetection and decode, for now) and display the processed image on the screen alongside a table with some information. For this i thought about having the following main components:

  • Stage: A trait that shall be implemented by any image processor.
  • SourceStage: A trait that shall be implemented by any image source.
  • CameraSource: It implements a SourceStage responsible for retrieving video from the Camera.
  • QrDecoder: It implements the Stage trait, draws a rectangle arround the QR codes and keeps any QR information of the last frame.
  • PipelineManager: Responsible for storing and passing the image trough the different Stages in a sequence order (for now) that may be used in the future on another projects.
  • UIManager: Display all UI components including the processed frames from the pipeline.

For now the the main flow is to just have the PipelineManager be owned by the UIManager and run the pipeline (which contains the CameraSource and the QrDecoder) on every frame.

The problem arrives after, I also want to display the QR information of the last frame in a table that's owned by the UIManager, so I require a reference of the QrDecoder to be saved in the UIManager. The Stages object should be owned by the PipelineManager but the same object could be referenced multiple times (mutability not needed).

The only way I can think of to solve this issue is by using the combination of Rc<RefCell> for every stage and passing the QRDecoder to the UIManager and PipelineManager, but this seems to be bad practice.

PipelineManager

use opencv::{prelude::*};
use std::cell::{RefCell};
use std::rc::Rc;

pub trait Stage {
    fn process(&mut self, input: &mut Mat) -> Result<()>;
}

pub trait SourceStage {
    fn get_frame(&mut self) -> Result<Box<Mat>>;
}

pub struct CVPipelineManager{
    start_stage: Option<Rc<RefCell<dyn SourceStage>>>,
    stages: Vec<Rc<RefCell<dyn Stage>>>
}


impl CVPipelineManager{
    pub fn new() -> Self {
        Self {
            stages: Vec::new(),
            start_stage: None,
        }
    }

    pub fn add_stage(&mut self, stage: Rc<RefCell<dyn Stage>>){
        self.stages.push(stage);
    }

    pub fn process(&mut self) -> Result<Box<Mat>>{

        if let Some(ref mut start_stage) = self.start_stage {
            let mut frame = start_stage.borrow_mut().get_frame()?;
            self.process_image(frame.as_mut());
            return Ok(frame);
        }
        return Err(anyhow!("Process cannot be called if a start stage was not defined"));
    }

    pub fn process_image(&mut self, input: &mut Mat) -> Result<()> {
        for stage in self.stages.iter_mut() {
            stage.borrow_mut().process(input)?;
        }
        Ok(())
    }

This code implements only the PipelineManager and the required traits, to get a better understanding of the problem.

Is there a better architecture for this or does Rust provide better tools for handling this kind of problems? I cannot think of any other way to design this without increasing the the effort too much when implementing a new Stage.

Aucun commentaire:

Enregistrer un commentaire