Skip to content

Commit

Permalink
Add ChaosCamera (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
TinyTinni authored Mar 9, 2024
1 parent 13f952c commit 1e5f84a
Show file tree
Hide file tree
Showing 17 changed files with 792 additions and 55 deletions.
31 changes: 26 additions & 5 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ The intention of this library is to make automatic testing possible without rely

- `AnyCamera`: can hold any `cv::VideoCapture` interface compatible camera.

- `MemoryCamera`: grabs images from a defined vector of images wich are already loaded in memory. Returns an empty image and error when the sequence was reached the end, or, when the `circular` option is enabled, starts from the start of the sequence again.

- `FileCamera`: grabs images from a provided sequence of paths. Returns an empty image and error when the sequence was reached the end, or, when the `circular` option is enabled, starts from the start of the sequence again.

- `DirectoryCamera` : grabs images from a provided directory. Returns an empty image anderror when the all images in the directory were shown once. When the `circular` option is enabled, the images in the directory are shown endlessly.
Expand All @@ -27,6 +29,26 @@ The intention of this library is to make automatic testing possible without rely

- `HttpCamera` : opens a HTTP server where you can connect and can images (.png/.jpg) via "POST /images" \<body containing the encoded image data\>. Queues those images. "DELETE /images" empties the queue.

### Camera Error Testing

It is possible to control the error with a given camera. For this purpose, `ChaosCamera` exists. It takes a camera and a error controlling sequence, for example `RanomSequence` which has a change of throwing an exception based on randomness.

Here is an example:
```cpp
AnyCamera camera = ...;
ChaosCamera chaos_cam(std::move(camera), RandomSequence({.isOpen = {0.95}})); // isOpen will fail 95% of the time. It will throw the exception "NotOpenException".
```
You can also add custom exceptions and weight them
```cpp
AnyCamera camera = ...;
ChaosCamera chaos_cam(std::move(camera), RandomSequence({.isOpen = RandomSequence::Fail(0.5).with<MyException>(5).with<MySecondException>(0.5) })); // "isOpen will fail 50% of the time, The ratio of MyException:MySecondException will be 10:1"
```

`ChaosCamera` supports `setExceptionMode` to enable/disable exceptions. It is on by default, so disable it if you don't want any. In this case, the corresponding functions will return `false`.

All standard exception which will be thrown when no custom exceptions where defined, are derived from `std::exception`.

## Build Requirements

- C++20
Expand All @@ -35,7 +57,7 @@ The intention of this library is to make automatic testing possible without rely

## How-To Use

FairyCam provides a new class `AnyCamera` which is an interface of `cv::VideoCapture` without relying on inheritance. It
FairyCam provides a new class `FairyCam::AnyCamera` which is an interface of `cv::VideoCapture` without relying on inheritance. It
can take any Camera which fulfills the `cv::VideoCapture`/`FairyCam::IsAnyCamera`-Concept.

An example using dynamic polymorphism where you can change the camera at runtime:
Expand Down Expand Up @@ -64,10 +86,9 @@ int main()
using namespace FairyCam;
if (config::isTestingEnabled())
{
auto opts = FileCamera::Options{.files={"myFile1.png""differentFile.jpg"}};
return startSystem(AnyCamera::create<FileCamera>(std::move(opts)));
return startSystem(FileCamera({.files={"myFile1.png", "differentFile.jpg"}));
}
return startSystem(AnyCamera::create<cv::VideoCapture>());
return startSystem(cv::VideoCapture());
}


Expand Down Expand Up @@ -103,7 +124,7 @@ int main()
if (config::isTestingEnabled())
{
return startSystem(FairyCamera::HttpCamera());
return startSystem(FileCamera({.files={"myFile1.png", "differentFile.jpg"}));
}
return startSystem(cv::VideoCapture());
}
Expand Down
6 changes: 6 additions & 0 deletions src/AnyCamera.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ class AnyCamera
std::forward<Args...>(args...)));
}

template <IsAnyCamera CameraType>
static AnyCamera create(typename CameraType::Options opts)
{
return AnyCamera(std::make_unique<Model<CameraType>>(std::move(opts)));
}

template <IsAnyCamera CameraType>
std::optional<std::reference_wrapper<CameraType>> dynamicCast() noexcept
{
Expand Down
23 changes: 16 additions & 7 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
find_package(OpenCV REQUIRED)
find_package(Poco REQUIRED Foundation Net)

add_library(FairyCam STATIC
HttpCamera.cpp
DirectoryTriggerCamera.cpp
FileCamera.cpp
)

set (HEADERS
"AnyCamera.hpp"
"MemoryCamera.hpp"
"FileCamera.hpp"
"HttpCamera.hpp"
"DirectoryCamera.hpp"
"DirectoryTriggerCamera.hpp"
"IsAnyCamera.hpp"

"chaos/ChaosCamera.hpp"
"chaos/Exceptions.hpp"
"chaos/Sequence.hpp"
"chaos/RandomSequence.hpp"
)

add_library(FairyCam STATIC
HttpCamera.cpp
DirectoryTriggerCamera.cpp
FileCamera.cpp
MemoryCamera.cpp

${HEADERS}
)

target_sources(FairyCam PUBLIC FILE_SET HEADERS
Expand Down Expand Up @@ -60,4 +69,4 @@ install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION cmake
)
)
43 changes: 43 additions & 0 deletions src/MemoryCamera.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "MemoryCamera.hpp"

#include <opencv2/imgcodecs.hpp>

namespace FairyCam
{

bool MemoryCamera::grab()
{
if (!m_is_open)
return false;

m_current = m_next;
if (m_current == m_opts.images.cend())
return false;

++m_next;
if (m_opts.circular && m_next == m_opts.images.cend())
m_next = m_opts.images.begin();

return !m_current->empty();
}

bool MemoryCamera::retrieve(cv::OutputArray image, int flag)
{
if (!m_is_open || m_current == m_opts.images.cend())
return false;
cv::Mat mat = *m_current;
image.assign(mat);
return !mat.empty();
}

bool MemoryCamera::read(cv::OutputArray image)
{
if (!grab())
{
image.assign(cv::Mat());
return false;
}
return retrieve(image);
}

} // namespace FairyCam
66 changes: 66 additions & 0 deletions src/MemoryCamera.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#pragma once

#include <filesystem>
#include <opencv2/core.hpp>
#include <vector>

namespace FairyCam
{

class MemoryCamera
{
public:
using ImageContainer = std::vector<cv::Mat>;
struct Options
{

ImageContainer images = {};
bool circular = false;
};

private:
Options m_opts;
bool m_is_open = false;
ImageContainer::const_iterator m_current;
ImageContainer::const_iterator m_next;

public:
MemoryCamera() : MemoryCamera(Options{}) {}
MemoryCamera(Options opts)
: m_opts{std::move(opts)}, m_current{m_opts.images.cend()},
m_next{m_opts.images.cend()}
{
}
bool open(int idx, int apiPreference, const std::vector<int> &params)
{
m_is_open = true;
m_next = m_opts.images.cbegin();
return true;
}
bool isOpened() const { return m_is_open; }
void release()
{
m_is_open = false;
m_current = m_opts.images.cend();
m_next = m_opts.images.cend();
}
bool grab();
bool retrieve(cv::OutputArray image, int flag = 0);
bool read(cv::OutputArray image);
bool set(int propId, double value) { return false; }
double get(int propId) const { return -1.0; }
void setExceptionMode(bool enable) {}
bool getExceptionMode() const { return false; }
MemoryCamera &operator>>(CV_OUT cv::Mat &image)
{
read(image);
return *this;
}
MemoryCamera &operator>>(CV_OUT cv::UMat &image)
{
read(image);
return *this;
}
};

} // namespace FairyCam
136 changes: 136 additions & 0 deletions src/chaos/ChaosCamera.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#pragma once

#include "../AnyCamera.hpp"
#include "Exceptions.hpp"
#include "Sequence.hpp"
#include <concepts>
#include <memory>
#include <opencv2/core.hpp>

namespace FairyCam
{

class ChaosCamera
{
private:
bool m_active_exception = true;
AnyCamera m_cam;
std::unique_ptr<Sequence> m_error_sequence;

bool checkIsOpen()
{
try
{
m_error_sequence->checkIsOpen();
}
catch (...)
{
this->release();
if (!m_active_exception)
return false;
throw;
}
return true;
}

bool checkGrab()
{
try
{
m_error_sequence->checkGrab();
}
catch (...)
{
if (!m_active_exception)
return false;
throw;
}
return true;
}

bool checkRetrieve()
{
try
{
m_error_sequence->checkRetrieve();
}
catch (...)
{
if (!m_active_exception)
return false;
throw;
}
return true;
}

public:
template <std::derived_from<Sequence> SeqT>
ChaosCamera(AnyCamera &&camera, SeqT seq)
: m_cam{std::move(camera)},
m_error_sequence{std::make_unique<SeqT>(std::move(seq))}
{
}
bool open(int idx, int apiPreference, const std::vector<int> &params)
{
if (!checkIsOpen())
return false;
return m_cam.open(idx, apiPreference, params);
}
bool isOpened() const
{
if (!const_cast<ChaosCamera *>(this)->checkIsOpen())
return false;
return m_cam.isOpened();
}
void release() { m_cam.release(); }
bool grab()
{
if (!isOpened())
return false;
if (!checkGrab())
return false;
if (!m_cam.grab())
{
if (m_active_exception)
throw GrabException{};
return false;
}
return true;
}
bool retrieve(cv::OutputArray image, int flag = 0)
{
if (!isOpened())
return false;
if (!checkRetrieve())
return false;
if (!m_cam.retrieve(image, flag))
{
if (m_active_exception)
throw RetrieveException{};
return false;
}
return true;
}
bool read(cv::OutputArray image)
{
if (!grab())
return false;
return retrieve(image);
}
bool set(int propId, double value) { return false; }
double get(int propId) const { return -1.0; }
void setExceptionMode(bool enable) noexcept { m_active_exception = enable; }
bool getExceptionMode() const noexcept { return m_active_exception; }
ChaosCamera &operator>>(CV_OUT cv::Mat &image)
{
read(image);
return *this;
}
ChaosCamera &operator>>(CV_OUT cv::UMat &image)
{
read(image);
return *this;
}
};

} // namespace FairyCam
26 changes: 26 additions & 0 deletions src/chaos/Exceptions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <stdexcept>

namespace FairyCam
{

class NotOpenException : public std::runtime_error
{
public:
NotOpenException() : std::runtime_error{"FairyCam closed the camera."} {}
};

class RetrieveException : public std::runtime_error
{
public:
RetrieveException() : std::runtime_error{"FairyCam error on retrieve."} {}
};

class GrabException : public std::runtime_error
{
public:
GrabException() : std::runtime_error{"FairyCam error on grab."} {}
};

} // namespace FairyCam
Loading

0 comments on commit 1e5f84a

Please sign in to comment.