jwm
jwm

Reputation: 5030

Connect specific signal and slot once and then disconnect them

I am going to save an image from a camera that continuously captures images. One signal is created for the camera, that keep sending images, and a slot is created for the mainwindow, which saves a real-time image by clicking an image saving button. What I have don are as follows:

//connection is triggered by button clicking
connect(camera, SIGNAL(sendImage(Mat)), this, SLOT(saveImage(Mat)), Qt::UniqueConnection); 
qDebug()<<"Image saved";

//Here is my slot definition. An image of OpenCV Mat is saved, followed by signal-slot disconnection. 
void MainWindow::saveImage(Mat cvimg)
{
    Mat savedImage;
    savedImage =cvimg.clone();
    imwrite("C:/Debug/Data/Save.jpg", savedImage);
    imshow("SavedImage", savedImage);

    disconnect(camera, SIGNAL(sendImage(Mat)),this, SLOT(saveImage(Mat)));
}

The above codes allow me to save one image and then disconnect slot and slot. My question here, in addition to using the disconnection method, is there any other way, preferably neater, to temporally receive one image for only one time and save it.

Upvotes: 0

Views: 251

Answers (2)

It’s an XY problem: you shouldn’t be messing with ephemeral connections, since they aren’t meant to be used that way. Several solutions come to mind.

You can cache the camera’s image, and save the cached value:

Q_DECLARE_METATYPE(cv::Mat)

const char kImageCache[] = "imageCache";

// connect once, as soon as you have the camera available
connect(camera, &Camera::sendImage, [camera](const cv::Mat &image){
  camera->setProperty(kImageCache, image);
});
connect(saveAction, &QAction::triggered, camera, [camera]{
  auto const image = camera->property(kImageCache).value<cv::Mat>();
  cv::imshow("Saved Image", image);
  QtConcurrent::run([image]{
    static std::atomic_bool saving;
    static bool not_saving;
    if (saving.compare_exchange_strong(not_saving, true)) {
      cv::imwrite("foo", image);
      saving.store(not_saving);
    }
  });
});

You can also have a state machine that reacts to images in the s_waiting state. The state machine lets you keep the image only when it’s needed to be saved: otherwise, a copy is not kept.

The nice thing about state machines is that they allow you to express complex behavior patterns in a clear and standardized manner. They are definitely underutilized in many UIs.

class MyWindow : public QWidget {
  …
  QStateMachine m_machine{this};
  QState s_idle{&m_machine}, s_waiting{&m_machine}, s_saving{&m_machine};
  cv::Mat m_saveImage;
  Q_SIGNAL void saved();
};

MyWindow::MyWindow(…):… {
  m_machine.setInitialState(&s_idle);
  m_machine.start();
  s_idle.addTransition(saveAction, &QAction::triggered, &s_waiting);
  s_waiting.addTransition(camera, &Camera::sendImage, &s_saving);
  s_saving.addTransition(this, &decltype(*this)::saved, &s_idle);
  connect(camera, &Camera::sendImage, &s_waiting, [this](const auto &image){
    if (s_waiting.isActive())
      m_saveImage = image; 
  });
  connect(&s_saving, &QState::entered, [this]{
    cv::imshow(…, m_saveImage);
    QtConcurrent::run([image = m_saveImage, this]{
      cv::imwrite(…, image);
      emit saved();
    });
    m_saveImage = {};
  });
}

Saving the image concurrently is a good idea: otherwise you’re killing the user experience by doing slow disk I/O in the GUI thread.

Finally: cloning of cv::Mat is usually unnecessary: the whole point of that type is that it’s reference counted and implicitly shared - it’s a copy-on-write type. It’s like QImage in this respect.

Upvotes: 3

TrebledJ
TrebledJ

Reputation: 8987

Although I don't really see anything glaringly wrong or uncomforting with the connect/disconnect method currently used, here are a couple brainstorms and suggestions.

QObject::blockSignals

One possibility might be to utilise QObject::blockSignals() and call it on camera. You could call connect in the constructor then toggle blockSignals every time the button is clicked and the slot finishes.

MainWindow::MainWindow(/* QWidget* parent or whatever */)
{
    //  connect called once in constructor
    connect(camera, SIGNAL(sendImage(Mat)), this, SLOT(saveImage(Mat)), Qt::UniqueConnection);
}

void MainWindow::on_saveImageButton_clicked()
{
    camera.blockSignals(false);
}

void MainWindow::saveImage(Mat cvimg)
{
    //  save/write operations
    //  ...

    //  instead of disconnecting...
    camera.blockSignals(true);
}

But as far as I know, this will block all signals emitted from camera. (And you probably have other signals emitted from there.) So this might not be an entirely viable option.

Boolean Flag

Another possibility would be to use a private member variable bool saveNextImage to filter out signals that haven't been preceded by a button click.

MainWindow::MainWindow(/* QWidget* parent or whatever */)
{
    //  connect called once in constructor
    connect(camera, SIGNAL(sendImage(Mat)), this, SLOT(saveImage(Mat)), Qt::UniqueConnection);
}

void MainWindow::on_saveImageButton_clicked()
{
    //  toggle flag
    saveNextImage = true;
}

void MainWindow::saveImage(Mat cvimg)
{
    //  check flag was set
    if (!saveNextImage)
        return;

    //  save/write operations 
    //  ...

    //  instead of disconnecting...
    //  toggle flag
    saveNextImage = false;
}

I feel this may be rather raw, but ¯\_(ツ)_/¯ an idea is an idea.

Upvotes: 1

Related Questions