-
Notifications
You must be signed in to change notification settings - Fork 17
RefactoringProposal
Relja Ljubobratovic edited this page Jun 19, 2017
·
3 revisions
#+TITLE: DCV Refactoring Proposal
* Introduction
These guidelines correspond to ongoing conversations originating in issues [[https://github.com/libmir/dcv/issues/87][#87]] [[https://github.com/libmir/dcv/issues/64][#64]] etc.
As previously discussed, major refactoring bullet points would be:
- removal of the dcv.core.image.Image class from the API
- solid design form of high level algorithms in the library
- introduction of common helper types (e.g. Size, Point, Rectangle, etc.)
- strongly formulated error handling techniques used thought the library
- introduce =option= parameter over individual parameters
* Implementation Status
| | dcv.core.image.Image | solid API | Common Types | Error Handling | Option Params |
|--------------+----------------------+-----------------+-----------------+-----------------+-----------------|
| contributor | @rljubobratovic | @rljubobratovic | @rljubobratovic | @rljubobratovic | @rljubobratovic |
| relevant PRs | n/a | n/a | n/a | n/a | n/a |
| status | n/a | n/a | n/a | n/a | n/a |
* Removal of the dcv.core.image.Image
Motivation behind this point is to achieve more type safe and self-documenting code,
and potentially to avoid runtime overhead introduced by using runtime info on
data type contained in the =Image= container. Also, the existence of such type in
the library API introduces ambiguity of the "First Class Type" in the library.
** Current example of the API
Code that would probably mostly be affected by this change is =dcv.io= package. Due
to the runtime-morphable data type, dcv client code doesn't need to define the expected
type of the image to be read:
#+BEGIN_SRC d
import dcv.core.image;
import dcv.io.image;
Image image = imread("/path/to/the/image.ext");
BitDepth depth = image.depth(); // bit-depth of the image
ImageFormat fomat = image.format(); // rgb, bgr etc.
size_t channelCount = image.channels();
// After the runtime inspection of the image format, slice it
// to the mir.ndslice.Slice of given type, and start the real
// work.
Slice!(Contiguous, [3], ubyte*) imageData = image.sliced!ubyte;
#+END_SRC
From this it can be concluded that =Image= type is used merely as convenient data
and metadata holder for the image IO. But, being formulated as nearly complete image
representation type, it can be confusing for users that this class should be used
throughout the library instead of =mir.ndslice.Slice=.
** Proposition
If =Image= is to be removed, =Slice= would probably become the data reciever type,
and =imread= should be templated:
#+BEGIN_SRC d
// Read RGB image as 32-bit (floating point)
Slice!(Contiguous, [3], float*) image = imread!(float, ImageFormat.RGB)("/path/to/image.ext");
#+END_SRC
Or we could introduce =Metadata= struct, as runtime info holder on the image, and store
the image data into a "default" =ubyte= return value slice:
#+BEGIN_SRC d
struct Metadata { ... }
Slice!(Contiguous, [3], ubyte*) imread(in string path, out Metadata metadata);
Metadata metadata;
Slice!(Contiguous, [3], ubyte*) image = imread("/path/to/image.ext", metadata);
BitDepth depth = metadata.depth(); // bit-depth of the image
ImageFormat fomat = metadata.format(); // rgb, bgr etc.
size_t channelCount = metadata.channels();
// And other image metadata (EXIF)
uint sensorWidth = metadata["sensorWidth"];
#+END_SRC
After the interpretation of the metadata, user can cast (reinterpret) and reshape
the returned =Slice= value to fit the image actual format. This variant is obviously
much less safe, but offers the same functionality as the current solution with the =Image=.
* Solid design of the High-Level algorithms
This point implies the need for more extensible code in the library. If rules of the API
design are well defined, users are much easier accustomed to it. It is also much easier
for potential contributors to build new modules in the library (such as *Feature Matching*,
*Optical Flow*, *Stereo* etc.).
** Current state
Present design can be observed in following packages:
- dcv.tracking.opticalflow
- dcv.multiview.stereo
- dcv.features.corner.fast
- dcv.features.rht
Most of these packages are designed in *OOP* style, except the =rht= module. /Random Hough Transform/
algorithms are implemented by @DmitryOlshansky. He has utilized =template mixin= feature to implement
overall API and most of the repeated code in it (=dcv.features.rht.BaseRht=), where the special (derived) code
is implemented in the actual API structs - =RhtLines=, =RhtCircles= etc.
So, options are:
- OOP
- =template mixin= basis
- what else?
* Introduction of Common types in the library
The idea is to implement common types used throughout the library - =Point=, =Size=, =Rectangle= etc.
to boost type safety and self-documentation of the code:
#+BEGIN_SRC d
Slice!(Contiguous, [2], V*) gaussian(V = double)(V sigma, size_t width, size_t height) pure
// should be
Slice!(Contiguous, [2], V*) gaussian(V = double)(V sigma, size_t[2] dim) pure
// or use a
struct Dim {size_t width, size_t height}
// and have a
Slice!(Contiguous, [2], V*) gaussian(V = double)(V sigma, Dim dim) pure
#+END_SRC
* Error Handling
The inspiration of this bullet point comes from the =imwrite= function:
#+BEGIN_SRC d
bool imwrite(in string path, size_t width, size_t height, ImageFormat format, BitDepth depth, ubyte[] data);
#+END_SRC
The idea is to avoid C-ish error handling with =bool= and =int= return types, and rather use "modern" solutions.
** Solutions
**** Throwing
@timotheecour suggests we should not run from the exception handling, especially for =non-@nogc=, and not performant
critical calls.
**** Nullable
There's maybe one more option to be considered: using the =Optional= values (e.g. Alexandescu's =Expected=,
or =std.typecons.Nullable= from Phobos). This strategy would allow unified error handling for all code -
=non-@nogc= and =@nogc= alike.
* Option Parameters
Idea is to wrap multiple function parameters into (option) packs. Example given by @timotheecour:
#+BEGIN_SRC d
struct radialKernelOption(T){
size_t radius;
T foreground = 1;
T background = 0;
}
Slice!(Contiguous, [2], T*) radialKernel(T)(radialKernelOption option);
#+END_SRC
Again, this empowers the self-documentation of the code, but also is much easier to
extend without the API breakage.
This strategy could be utilized practically anywhere and everywhere in the library.