Skip to content

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.

Clone this wiki locally