Age | Commit message (Collapse) | Author |
|
Add awb, blc, cproc, filter, and gamma to the tuning file. These don't
need any configuration.
At the moment there are no inter-module dependencies in the tuning
process. We can therefore safely sort them alphabetically. As soon as
the first dependency gets introduced (most likely lsc -> ccm) we will
see how to solve that.
The output order controls the order of processing in the IPA. It is now
also in alphabetical order which happens to be no change for the modules
that existed previously. For the others, there is no need for a specific
order.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
|
|
Add a static module class, that can be used to add static data to the
tuning file. This is propably not the best solution, but allows us to
progress without writing lots of dummy classes for static cases.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
There are valid cases where a module returns None. E.g. no images were
provided for LSC calibration. We should however define proper semantics
there. Continue with a warning for now.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
Make it clear that no lsc calibration was done by returning None instead
of a incomplete configuration.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
Implement a minimal ccm calibration module. For now it doesn't take the
results from lsc into account and supports rkisp1 only.
Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
|
|
Remove the need for the Cam object, as we don't want to port
it from Raspberry Pi.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
In the Image class the variable holding the color temperature is named
color instead of col which was used by the raspberry pi scripts. Rename
accordingly.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
Based on the input images, the lsc values could exceed the range allowed
by the rkisp1. As we are now clipping the values, we can simplify the
value mapping.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
Print a info on every image that gets processed and a warning on every
image that gets ignored.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
|
|
Add the missing pieces and store the result inside the image object.
This solution is not very nice, and should be refactored soon. For that
we need a concept to collect temperature and/or image specific results
in a central place. For now it serves the purpose.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
At the moment this just reads the yaml file and returns it verbatim.
This needs to evolve further in the near future.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
In the tuning datasets, the files had names like
'imx335_1600l_3000k_1.dng'. That failed on the old filename parsing
function. As there is no need to dictate the order of the tags, split
the big regex into chunks and parse them one by one. This also makes
the code easier to digest.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
The old function uses PIL to save the image, which is not in the
requirements file. As we are already requiring opencv, use that to save
images instead of an additional dependency
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
In ctt_ccm.py the logging functionality of the Cam object was used. As
we don't want to port over that class, it needs to be replaced anyways.
While at it, also replace the eprint function as it doesn't add any
value over the logging framework and misses the ability for easy log
formatting.
For nice output formatting add the coloredlogs library.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
|
|
Fix imports to match new structure in the files copied from Raspberry
Pi. Add missing imports in macbeth.py. Add missing dependencies to
requirements.txt.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
|
|
Add a requirements file to ease the installation and use of the tuning
scripts. Document that in the readme. No debian packages are provided as
rawpy is not packaged as deb. So pip has to be used anyways.
Also add pyyaml which was missing in the dependencies.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
Copy visualize_macbeth_chart from raspberry pi. It is copied verbatim
and does not work in this state.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
Copy ctt_{awb,ccm,colors,ransac} from the raspberrypi tuning scripts as
basis for the libcamera implementation. color.py was renamed to
ctt_colors.py to better express the origin.
The files were taken from commit 66479605baca ("utils: raspberrypi: ctt:
Improve the Macbeth Chart search reliability").
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Acked-by: Paul Elder <paul.elder@ideasonboard.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
Opencv fails to load the image. The added license destroys the magic
number. Fix, by moving the licence below the magic number.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul ELder <paul.elder@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
Port commit 66479605baca ("utils: raspberrypi: ctt: Improve the Macbeth
Chart search reliability") into libtuning.
Previously the code would brighten up images in case the Macbeth Chart
is slightly dark, and also zoom in on sections of it to look for
charts occupying less of the field of view. But it would not do both
together.
This change makes the search for smaller charts also repeat that
search for the brightened up images that it made earlier, thereby
increasing the chances of success for non-optimal tuning images.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
Now that the utils::hex() function supports 8-bit and 16-bit integers,
extend the unit test to cover them.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>
|
|
The utils::hex() function is implemented for 32-bit and 64-bit integers,
but not for 8-bit and 16-bit. This causes a link error (possibly at
runtime for IPA modules due to lazy linking) when trying to print 8-bit
or 16-bit integers. Implement additional specializations to fix it.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>
|
|
The context parameter of the BlackLevelCorrection::init() function is
used. Drop the [[maybe_unused]] attribute.
Fixes: 50c28e135100 ("ipa: rkisp1: blc: Query black levels from camera sensor helper")
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
Enable the simple pipeline handler with software ISP for the IPU6 now
that the IPU6 CSI2 receiver (aka the isys driver) has landed in
media_staging/master.
Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
Move black levels for tuning files that contained a BLC block into
the camera sensor helpers.
ov4689.yaml had 66@12bit while the datasheet states 64@12bit. Use the
value from the datasheet (scaled to 16bit).
ov5640.yaml had 256@12bit while the datasheet states 16@10bit. Looking
at the commit message the 256 most likely stems from the imx219 tuning
file and 16@10bit is the same as the 64@12bit from the ov4689. This
seems more likely and is therefore used.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
The black levels for imx219 and imx258 are now contained in the camera
sensor helpers. Remove them from the tuning file for the imx219. Add a
BLC entry to the imx258 tuning file.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
Add sensor black levels to the metadata of the rkisp1 pipeline.
Additionally enable raw support for this algorithm and add it to
uncalibrated.yaml, so that black levels get reported when capturing
tuning images. This is a bit of a hack, because no actual black level
correction is taking place in raw mode, but it is the easiest way to get
blacklevel reported for raw streams.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
As the camera sensor helper now has the ability to provide the black
level, use it. Black levels can still be overwritten by the tuning
file, but the direction is to remove them from the tuning files and move
them into the sensor helpers.
Additionally interpret all values based on 16bits. The conversion to the
scale required by the hardware is done in process(). It ensures all the
values inside libcamera are the same scale and is in preparation for the
i.MX8MP where black levels are based on a 20bit scale. Note that this
breaks existing tuning files. The tuning files distributed with
libcamera will be fixed in a later patch.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
To be able to query the black levels, the black level correction
algorithm needs access to the camera sensor helper. Allow this by moving
the camHelper_ member from IPARkISP1 into IPAContext.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
For a proper tuning process we need to know the sensor black levels. In
most cases these are fixed and not reported by the kernel driver. Store
them inside the sensor helpers for later retrieval by the algorithms.
Add black level value corresponding to the data pedestal for three
initial sensors as documented in the datasheets. More should be added,
eventually filling the gaps for all supported sensors.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
The converter interface uses the unsigned int output stream index to map
to the output frame buffers. This is cumbersome to implement new
converters because one has to keep around additional book keeping
to track the streams with their correct indexes.
The v4l2_converter_m2m and simple pipeline handler are adapted to
use the new interface. This work roped in software ISP as well,
which also seems to use indexes (although it doesn't implement converter
interface) because of a common conversionQueue_ queue used for
converter_ and swIsp_.
The logPrefix is no longer able to generate an index from a stream, and
is updated to be more expressive by reporting the stream configuration
instead, for example, reporting "1920x1080-MJPEG" in place of
"stream0".
Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Tested-by: Andrei Konovalov <andrey.konovalov.ynk@gmail.com> # sm8250 RB5
|
|
Rename the private Stream class from V4L2M2MConverter::Stream to
V4L2M2MConverter::V4L2M2MStream. This is done to improve readability
of the code when we drop the handling of stream by indexes in a
subsequent patch.
Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
Currently the soft-isp outputs a single output stream. Hence,
drop the unnecessary check for stream indexes.
Another reason to drop is actually the stream indexes is meant to be
unique in outputs std::map<>, hence checking for unique stream indexes
is redundant.
Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
The streams sanity check tries to determine if all the stream indexes
passed in outputs std::map<> are unique. However, since the data
container is std::map<>, all its keys (stream indexes in this case),
are already unique.
Instead, rectify the sanity check to ensure all the framebuffers passed
in the outputs std::map<> are unique to each index. Hence, no two stream
indexes should have same framebuffer. Update the comment to reflect
the change.
Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
|
|
The 16-bit padded raw 10 and raw 12 formats are stored in memory in
little endian order, regardless of the machine's endianness. Read pixel
data as uint8_t values and hardcode bit shifting to little endian to fix
scanline packing.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>
|
|
The 16-bit padded raw 10 and raw 12 formats are stored in memory in
little endian order, regardless of the machine's endianness. Swap the
16-bit values on big-endian machines when reading pixels from memory to
generate thumbnails.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>
|
|
The Image class incorrectly parses data in GBRG bayer formats as the
indices to the channels are set incorrectly - fix it.
Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
Add support for RAW10 and RAW12 to the dng_writer. This is needed on
imx8mp to produce tuning images. Both formats were tested on a debix
som with a imx335.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
Add a thumbnail function for raw formats that are 16bit aligned.
This is needed for the upcoming RAW10 and RAW12 implemntation.
Use the new function for RAW16 as the thumbScanlineRaw_CSI2P produces
incorrect results for that format (it averages over adjacent bytes,
which works for the CSI formats).
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
The old names lead to confusions. Rename to better express the intent.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
Add support for RAW16 formats to the DNGWriter helpers so that we can
produce dng files from the mali-c55.
Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
The gcc used in my current buildroot (Version 12.3) errors out with
-Wmaybe-uninitialized. Fix that.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
|
|
In libtiff version 4.5.1 and later the CFA* tags were missing. This got
fixed in https://gitlab.com/libtiff/libtiff/-/commit/49856998c3d82e65444b47bb4fb11b7830a0c2be
Unfortunately the fix is not released yet, but the faulty libtiff is
contained in current buildroot. As a local fix is pretty easy and
without side effects, let's workaround that.
Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
A recently introduced typo resulted in a function definition not
matching its declaration. As the problem occurred in libipa, and IPA
modules are loaded dynamically and are linked with lazy symbol
resolution, the problem wasn't caught at build time.
To try and catch future similar issues, enable the
-Wmissing-declarations warning.
Suggested-by: Barnabás Pőcze <pobrn@protonmail.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
|
|
The GStreamer tests define a __asan_default_options() function to
influence the behaviour of ASan. The function is declared in
sanitizer/asan_interface.h, but we don't include the header. This will
cause missing declaration warnings when we enable the
-Wmissing-declarations option.
Include the header to fix the issue. It can't be done unconditionally as
not all toolchains provide ASan, so check for its availability at
configuration time.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
|
|
A local function in the unixsocket test is defined in the global
namespace without the static keyword. This compiles fine for now, but
will cause a missing declaration warning when we enable them. To prepare
for that, enclose the function declaration in an anonymous namespace.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
|
|
Multiple local functions are defined in the global namespace without the
static keyword. This compiles fine for now, but will cause a missing
declaration warning when we enable them. To prepare for that, move the
function declaration to an anonymous namespace.
While at it, for consistency, include an existing static function in the
namespace and drop the static keyword.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
|
|
_FORTIFY_SOURCE redirects the open*() calls to __open*_2() functions.
The libcamera V4L2 adaptation layer intercepts those functions to
support applications compiled with _FORTIFY_SOURCE. When _FORTIFY_SOURCE
is not enabled, the C library headers will not provide declarations for
the fortified functions, which will cause missing declaration warnings
when we unable them.
Fix this by disabling the -Wmissing-declarations warnings selectively
for the _FORTIFY_SOURCE functions. To avoid sparkling pragmas around,
move the relevant function definitions next to each other.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
|
|
The close() and ioctl() functions are declared in the unistd.h and
sys/ioctl.h headers. Include them to provide the declarations.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
|
|
The init_py_*() functions are called by the top-level entry point of the
libcamera Python module to initialize different parts of the bindings.
They are declared in py_main.cpp where they are called, and defined in
separate compilation units. This results in functions being defined
without a corresponding declaration, and will generate warnings when we
enable -Wmissing-declarations.
Fix this by moving the function declarations from py_main.c to
py_main.h, and including py_main.h in the various compilation units that
need it.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
|