I'm making a 4:4:4/4:2:2 16/14/12/10-bit codec for Canon DSLR's

tron

CR Pro
Nov 8, 2011
5,222
1,616
Orangutan said:
tron said:
Actual code my .....
These are actual comments .... actually ;D

Different languages use different constructs for inline comments; also, some do not have a Function keyword, and others are case-sensitive. Yours has a combination of language features I don't recognize as pertaining to one, single language. Again, it could be a newer language/variant I haven't used, but...

Plot continuity...
Please try to refer to Harry not me! I have been sarcastic to Harry by the way...

By the way: Actual code my .... was in place for Actual code my @$$ ;D
 
Upvote 0
tron said:
Orangutan said:
tron said:
Actual code my .....
These are actual comments .... actually ;D

Different languages use different constructs for inline comments; also, some do not have a Function keyword, and others are case-sensitive. Yours has a combination of language features I don't recognize as pertaining to one, single language. Again, it could be a newer language/variant I haven't used, but...

Plot continuity...
Please try to refer to Harry not me! I have been sarcastic to Harry by the way...

By the way: Actual code my .... was in place for Actual code my @$$ ;D

Yeah, sorry. Typing fast, thinking slow. :)
 
Upvote 0
bhf3737 said:
HarryFilm said:
Here is some ACTUAL CODEC CODE I am using in this project.
It is just a small slice of what I can do...AND it is VERY VERY REAL !!!!

My code is something people can EASILY read and understand!
I created it all from scratch and it can do things people cannot even imagine!

The Codec BIOS Loader is on-schedule for completion sometime just after the end of NAB.......

Be Here For The Reveal or Be Square!

So you have written your code for the CODEC in Pascal!! :eek: :eek: :eek:
That is your evidence!

===

The reason i did it in Delphi/Lazarus is that I have a REALLY FANCY cross-compiler that will be run on the final code that keeps the output assembler PURELY to Coretex A4 instructions which is what the Canon DIGIC chips use. Normally, BOTH the Lazarus and Delphi compilers can output ARM Chip code but TEND to create assembler instructions and extensions that are specific to Qualcomm Snapdragon and other similar cores which is a PROBLEM!

I MUST HAVE a restriction to ONLY Coretex instructions so the codec works on ALL the major Canon Still Image and Cinema EOS Models from 25 MHz up to 1.5 GHz clock speeds.

AND to muddy the waters EVEN MORE THAN I ALREADY AM....I can get 4K (4096 by 2160 pixels) at 24, 25 and 30 fps video OUT OF a Canon 6D Mk2 and 7Dmk2 AND the Canon M5/M6 cameras AND the Canon 5D Mk2 and mk3 (mk4 already has 4k)

I HAVE THE SUPER-DUPER-SECRET CODING SAUCE TO DO IT !!!!!

ha ha ha ha ha ha ha aaahh aha aha aha aha I DID IT CANON !!!! I DID IT !!!!!!!

.... OOGAH OOGAH OOGAH ....

May the Fish (or the Force!) Be With You! ALWAYS!
 
Upvote 0
HarryFilm said:
bhf3737 said:
HarryFilm said:
Here is some ACTUAL CODEC CODE I am using in this project.
It is just a small slice of what I can do...AND it is VERY VERY REAL !!!!

My code is something people can EASILY read and understand!
I created it all from scratch and it can do things people cannot even imagine!

The Codec BIOS Loader is on-schedule for completion sometime just after the end of NAB.......

Be Here For The Reveal or Be Square!

So you have written your code for the CODEC in Pascal!! :eek: :eek: :eek:
That is your evidence!
I DID IT CANON !!!! I DID IT !!!!!!!

The gold medal ain't been won until the lab tests come back; likewise, until your "secret sauce" has been taste-tested (and passed FDA health and safety standards), it's nothing more than


index.php

 
Upvote 0

LDS

Sep 14, 2012
1,763
293
HarryFilm said:
Here is some ACTUAL CODEC CODE I am using in this project.

That's just a long comment for a file header, and a prototype of a function using your logorrheic style. No actual code.

Then there's your usual technobabble - btw, it's "Cortex", not "Coretex"... and I don't remember an A4 one (Apple A4 is a different beast...)
 
Upvote 0

zim

CR Pro
Oct 18, 2011
2,129
318
LDS said:
HarryFilm said:
Here is some ACTUAL CODEC CODE I am using in this project.

That's just a long comment for a file header, and a prototype of a function using your logorrheic style. No actual code.

Then there's your usual technobabble - btw, it's "Cortex", not "Coretex"... and I don't remember an A4 one (Apple A4 is a different beast...)

// Harry is just teasing
// With those stub fn and sub
// He's actually just baiting everyone
// Before releasing on GitHub and blowing your minds
// ^^ hot code comments ^^

Public voido brick7d()
{
Return "I want to believe";
}
 
Upvote 0
LDS said:
HarryFilm said:
Here is some ACTUAL CODEC CODE I am using in this project.

That's just a long comment for a file header, and a prototype of a function using your logorrheic style. No actual code.

Then there's your usual technobabble - btw, it's "Cortex", not "Coretex"... and I don't remember an A4 one (Apple A4 is a different beast...)

---

A4 is a specific sub-model of the Cortex (misspelling fixed!) cores used by embedded device makers which is even MORE RISC-y (i.e. reduced instructions) for power-saving reasons. The compiler is supposed to do all the fancy logic to allow higher-level functions that more CISC-y (i.e. more complex) cpu instruction sets do at the hardware level. A4 is NOT an Apple moniker but rather something originally from either Texas Instruments or Qualcomm (not sure!) who licence the ARM architecture from ARM itself but I am seeing that A4 moniker in the actual ARM company's (i.e. the Advanced RISC Machines company now owned by Softbank) documentation I've got.

Canon uses this A4 core for simple power-saving reasons at 25 MHz up to 1.5 GHz
in its DIGIC series of camera image processing and control CPU's.


I ain't no mere beginner in this...Here is how to do SIMPLE AND EFFECTIVE EDGE DETECTION from MY OWN CODE i designed all by myself from scratch which WILL run on ARM, Intel/AMD, MIPS, IBM Power, SuperSPARC and various DSP processors
INCLUDING the Canon DIGIC series:


COPY this code to a Landscape-mode using 10 point narrow font in a word processing document to see the entire RATHER NICELY DOCUMENTED code segment.
THIS BELOW CODE SEGMENT IS FULLY GNU GPL 3.0 OPEN SOURCE LICENCE FOR YOU programmers: Have at it for modification but REMEMBER to keep your work and modifications to this code to the GNU GPL 3.0 licence terms!


Procedure Apply_Convolution_Filter_to_Pixel( Var Any_Bitmap, Temporary_Storage_Bitmap: Virtual_Bitmap; Starting_X_Coordinate, Starting_Y_Coordinate, Index_to_Start_of_Filter: Large_Integer; Var Convolution_Kernel: Array of Large_Integer; Procedure_to_Run_After_Each_Convolution_Kernel: After_Each_Convolution_Kernel_Procedure_Type );

Var
x, y, i,

Current_X_Coordinate,
Current_Y_Coordinate,

Current_Pixel_Brightness,
Original_Pixel_Brightness,

Final_Convolution_Result,
Final_Convolution_Grid_Result,

Convolution_Kernel_Width,
Convolution_Kernel_Height,

X_Centre_of_Kernel,
Y_Centre_Of_Kernel,

Edge_Sensitivity_Threshold,

Type_of_Output_Image_Desired : Large_Integer;

New_Pixel,
Sampled_Pixel,
Original_Pixel : RGB_Pixel_Type;

New_Pixel_Brightness : Floating_Point;

Convolution_Operation_is_Additive : Boolean_Type;

Begin
Try
// Find out if this is the first convolution kernel applied in a series of filters
if Index_to_Start_of_Filter = ZERO then
Convolution_Operation_is_Additive := FALSE // Assume that this is the first in a series of kernels being applied
else
Convolution_Operation_is_Additive := TRUE; // Assume that this is the second or later in a series of kernels being applied

Original_Pixel := Get_Pixel( Any_Bitmap, Starting_X_Coordinate, Starting_Y_Coordinate );
Original_Pixel_Brightness := Get_Greyscale_Value_of_RGB_Pixel( Original_Pixel );

Final_Convolution_Result := ZERO;
Final_Convolution_Grid_Result := ZERO;

Convolution_Kernel_Height := Convolution_Kernel[ Index_to_Start_of_Filter ];
Convolution_Kernel_Width := Convolution_Kernel[ Index_to_Start_of_Filter + 1 ];

// The location of where the X, Y coordinate of where the convolution kernel itself should be centred over a pixel
X_Centre_of_Kernel := Convolution_Kernel[ Index_to_Start_of_Filter + 2 ];
Y_Centre_Of_Kernel := Convolution_Kernel[ Index_to_Start_of_Filter + 3 ];

// Any pixels luminances under this threshold will be ignored
Edge_Sensitivity_Threshold := Round( ( Convolution_Kernel[ Index_to_Start_of_Filter + 4 ] * 0.01 ) * MAXIMUM_COLOUR_CHANNEL_VALUE );

// Details whether the image should output a black & white image, greyscale or original image with filter applied
Type_of_Output_Image_Desired := Convolution_Kernel[ Index_to_Start_of_Filter + 5 ];

{
This is the main convolution loop which multiples all the elements of the convolution kernel
and all the elements of the sampled image region. The coefficients in the Kernel will determine
whether the difference between the sampled pixel and surrounding pixels is amplified or mitigated.
}
i := Index_to_Start_of_Filter + 6;
for y := 1 to Convolution_Kernel_Height do
for x := 1 to Convolution_Kernel_Width do
Begin
Current_X_Coordinate := ( Starting_X_Coordinate + x ) - X_Centre_of_Kernel;
Current_Y_Coordinate := ( Starting_Y_Coordinate + y ) - Y_Centre_of_Kernel;

// The convolution kernel can be equal in width and height or asymmetrical thus make sure we overlay the kernel overtop the correct pixel
Sampled_Pixel := Get_Pixel( Any_Bitmap, Current_X_Coordinate, Current_Y_Coordinate );

Current_Pixel_Brightness := Get_Greyscale_Value_of_RGB_Pixel( Sampled_Pixel );

// multiple each Kernel element with each pixel of the sampled image region
Final_Convolution_Result := Final_Convolution_Result + ( Current_Pixel_Brightness * Convolution_Kernel[ i ] );

// add to the sum total of the all kernel multiplication operations
Final_Convolution_Grid_Result := Final_Convolution_Grid_Result + Convolution_Kernel[ i ];

Inc( i );
End;

// Get the final convolution filter result
if Final_Convolution_Grid_Result = ZERO then
Final_Convolution_Result := ( Final_Convolution_Result + 1 ) div ( Final_Convolution_Grid_Result + 1 )
else
Final_Convolution_Result := Final_Convolution_Result div Final_Convolution_Grid_Result;

// Set each colour channel to legal RGB values
Final_Convolution_Result := RGB_Clamp( Final_Convolution_Result, RGB_Colour_Space );

{
Determine how the kernel result should be interpreted and how Temporary_Storage_Bitmap
should have its pixels created or copied from the original bitmap.

If this is the first or only application of a convolution kernel, then any
convolution kernel result that is above the Edge_Sensitivity_Threshold is copied to the current X, Y coordinate
of Temporary_Storage_Bitmap. Those convolution kernel results that fall below Edge_Sensitivity_Threshold
cause the pixel located at current X,Y coordinate of Temporary_Storage_Bitmap
to be set to a default value of dark black or the pixel value from the original bitmap
may be copied to Temporary_Storage_Bitmap.

If this is the second or later in a series of convolutions being applied,
then all convolution operations are additive to Temporary_Storage_Bitmap.
This means that only pixels that are above the Edge_Sensitivity_Threshold
are added to Temporary_Storage_Bitmap. Pixels that fall below Edge_Sensitivity_Threshold
are ignored and no pixel is modified on Temporary_Storage_Bitmap.
}
Case Type_of_Output_Image_Desired of
OUTPUT_BLACK_AND_WHITE_IMAGE : Begin
{
Used for creating a reference bitmap image (i.e Temporary_Storage_Bitmap) which is used
in other subsequent image manipulation of the original image.
i.e. if pixel at reference image X,Y location is White then the original bitmap's pixel
at that location needs to be changed or looked for further processing otherwise
if Black then pixel at X,Y on the original can be ignored and no futher processing need be done.
}
If Final_Convolution_Result >= Edge_Sensitivity_Threshold then
New_Pixel := RGB_Colour_100_Percent_White
else // if necessary, set the pixel that is below the edge sensivity threshold to dark black
if Convolution_Operation_is_Additive = FALSE then
New_Pixel := RGB_Colour_ZERO_Percent_Black
else
Exit;
End;


OUTPUT_ORIGINAL_IMAGE_WITH_FILTER_APPLIED : Begin
{
Copies the original pixel data to Temporary_Storage_Bitmap and applies
the convolution filter to the copied pixel data.
When all processing is done to Temporary_Storage_Bitmap,
it can be copied back to the original bitmap.
i.e. original bitmap is overwritten by Temporary_Storage_Bitmap.
}
If Final_Convolution_Result >= Edge_Sensitivity_Threshold then
Begin
New_Pixel := Original_Pixel;
Set_Pixel_to_Absolute_Luminance_Level( New_Pixel, Final_Convolution_Result );
End
else // if necessary, set the pixel that is below the edge sensivity threshold to dark black
if Convolution_Operation_is_Additive = FALSE then
New_Pixel := Original_Pixel
else
Exit;
End;

OUTPUT_AS_GREY_SCALE_IMAGE : Begin
{
Temporary_Storage_Bitmap is changed such that each Final_Convolution_Result is output
as a greyscale level pixel.

Future image processing operations can interpretthe differing Greyscale levels
of Temporary_Storage_Bitmap as a degree of interest or degree of change that should
be applied to each pixel of the original bitmap.

i.e. Dark Black = no change to original pixels is required
50% grey = partially apply further image processing operations
Bright White = fully apply subsequent image processing operations
}
If Final_Convolution_Result >= Edge_Sensitivity_Threshold then
Set_Each_Channel_of_RGB_Pixel_to_Same_Value( New_Pixel, Final_Convolution_Result )
else // if necessary, set the pixel that is below the edge sensivity threshold to dark black
if Convolution_Operation_is_Additive = FALSE then
New_Pixel := RGB_Colour_ZERO_Percent_Black
else
Exit;
End;
else
Begin
// if unable to determine how to interpret a kernel result give a default answer
New_Pixel := RGB_Colour_ZERO_Percent_Black;

Set_Bitmap_Error( 'Unable to the perform the designated convolution because the Type_of_Output_Image_Desired'+ CRLF +
'parameter may be bad. It is currently set at: ' + NumberToString( Type_of_Output_Image_Desired ) + DOUBLE_SPACE +
'Output pixel has been changed to RGB_Colour_ZERO_Percent_Black.', 'Apply_Convolution_Filter_to_Pixel()' );
End;
End;

// Make sure the procedure actually exists before running the code that will examine
// and if necessary, modify each output pixel of a convolution kernel before it is copied to the Temporary_Storage_Bitmap
if @Procedure_to_Run_After_Each_Convolution_Kernel <> RUN_NO_OTHER_PROCEDURE then
Procedure_to_Run_After_Each_Convolution_Kernel( Any_Bitmap, Temporary_Storage_Bitmap, Starting_X_Coordinate, Starting_Y_Coordinate, Original_Pixel, New_Pixel );

// Save the convolution result to the reference image which can then be interpreted in subsequent image processing operations
Set_Pixel( Temporary_Storage_Bitmap, Starting_X_Coordinate, Starting_Y_Coordinate, New_Pixel );

Except // on any errors return a default result
New_Pixel := RGB_Colour_ZERO_Percent_Black;

Set_Bitmap_Error( 'Unable to the perform the designated convolution because of a number of reasons including'+ CRLF +
'parameters may be out-of-range, Convolution_Kernel is improperly declared or has values that are to big or too small,' + DOUBLE_SPACE +
'Output pixel has been changed to RGB_Colour_ZERO_Percent_Black.', 'Apply_Convolution_Filter_to_Pixel()' );

Set_Pixel( Temporary_Storage_Bitmap, Starting_X_Coordinate, Starting_Y_Coordinate, New_Pixel );
End;
End;
{
Convolution is both a mathematical concept and an important tool in data processing,
in particular in digital signal and image processing.

Convolution allows one to multiply the individual elements of two numerical arrays together
and return an averaged numerical representation of the contents of both numerical arrays.
These convolution functions can interpret or discern the edges of image elements or objects,
find specific levels of pixel brightness, obtain the degree of colour or luminance transition
between two adjacent pixels and other useful functions.

Is essence, convolution is the process of finding the difference between two numbers and then
either mitigating that difference or amplifying the difference.
Applying single or multiple convolution kernels to a pixel sample can achieve effects
such as image smoothing, sharpening, edge detection and embossing.

for example:

x1, x2, x3 y1, y2, y3
x4, x5, x6 X y4, y5, y6 = averaged result after multiplying all the elements together
x7, x8, x9 y7, y8, y9

A Convolution Filter Kernel is an odd-sized, even-sized, or asymmetrically-sized matrix of integers
usually 3x3 or 5x5, 7x7 or 9x9, 2x2 or 4x4 or 6x6 elements in size containing a patterned series of
positive or negative numbers. These numbers can be multipled with each
Red, Green and Blue colour channel of a pixel or just the single computed Greyscale value of each RGB pixel.

The convolution result can saved to the secondary reference bitmap called Temporary_Storage_Bitmap
as a Black and White bitmap, Greyscale bitmap, or original pixels with convolution applied.
This will give you the option of using the convolution kernel results as a reference that
details which pixels on the original bitmap to take a closer look at or process further.

This is chosen by passes to the parameter Type_of_Output_Image_Desired one of the following parameters:

OUTPUT_BLACK_AND_WHITE_IMAGE
OUTPUT_ORIGINAL_IMAGE_WITH_FILTER_APPLIED
OUTPUT_AS_GREY_SCALE_IMAGE
DO_NOT_CHANGE_IMAGE

The resulting bitmap can be used as is, with the convolution effect applied or the output bitmap
can be referenced by other subsequent image processing operations.

i.e. any pixels on the reference bitmap which is Dark Black indicates that the pixel on the Original bitmap
located at the sampled X,Y coordinate should be ignored, otherwise a Bright White pixel indicates the original pixel
should have subsequent image processing operations fully applied. In-between greyscale values indicate
the degree of which any subsequent special effect or image processing operation should be applied.

Note that the larger the convolution kernel is in terms of number-of-elemnts, the longer the operation will take.

1024 by 768 pixel, 24 bit image = 2,359,296 Bytes using a 3x3 convolution filter = 21,233,664 operations

the sames image using a 5x5 filter will require 58,982,400 operations, A 7x7 filter will require 115,605,504 operations
and a 9x9 filter will require 191,102,976 operations. We get a rather large increase in the amount of time that is required
for each convolution to be applied if a convolution filter is larger than 3x3.

The larger convolution filters are used where accuracy of result is more important than execution speed.

PLEASE NOTE that the convolution kernels used in this routine can be be odd sized, even sized or asymmetric
including kernel sizes such as 3x3 or 5x5, 7x7, 9x9 ..or.. 2x2, 4x4, 8x8, ...or... 4x1, 3x4 or 3x5 or 7x2.
This allows very advanced image processing operations on square-shaped groups of pixels, single horizontal lines of pixels,
single vertical lines of pixels and rectangular groups of pixels.


The parameter Any_Bitmap is the original bitmap upon which the convolution kernel will be applied

Temporary_Storage_Bitmap is a holding area (or reference bitmap) which will temporarily store the
convolution kernel results.

Starting_X_Coordinate and Starting_Y_Coordinate detail upon which pixel of the original bitmap
the convolution kernel will be applied. Screen coordinate 0,0 is the upper left corner of original bitmap.

Convolution_Kernel is an array of Large_Integers which contains the information about a single convolution kernel
or it can also contain multiple convolution kernels which can then be applied one after another.
The convolutions kernels do NOT have to be the same size which thus allows the possibility of
applying a 3x3 kernel, then a 7x2 kernel, a 4x4 kernel and then a 5x5 kernel in succession.

The Large_Integer Array which is passed to the Convolution_Kernel parameter must follow the following convention:

Index ZERO = width of the convolution kernel itself from 1..x
Index 1 = height of the convolution kernel itself from 1..y
Index 2 = the X coordinate of where the kernel will be centred over the sampled pixel from the original bitmap - this is 1..x based NOT ZERO based
Index 3 = the Y coordinate of where the kernel will be centred over the sampled pixel from the original bitmap - this is 1..y based NOT ZERO based
Index 4 = An edge sensitivity flag which details that any convolution results that are under this optimal threshold are ignored
and not saved to Temporary_Storage_Bitmap. The integer is changed to a percentage of the maximum allowable
value in the individual colour channel of an RGB_Pixel_Type.
thus integer value example could be 50 = 50% of 255 = 127, or 25 = 25% of 255 = 63 or 75 = 75% of 255 = 191
This makes sure that only relevant pixels are considered for processsing and not stray pixels or unimportant ones.
This can be changed on the fly if neccessary by assigning another Large_Integer value to this index location.
Index 5 = One of the 4 constants OUTPUT_BLACK_AND_WHITE_IMAGE, OUTPUT_ORIGINAL_IMAGE_WITH_FILTER_APPLIED,
OUTPUT_AS_GREY_SCALE_IMAGE, DO_NOT_CHANGE_IMAGE which tells the routine how to interpret the convolution kernel results.
This allows for further image processing where the pixels stored in Temporary_Storage_Bitmap can be referenced
and the level of greyscale can be interpreted to mean how much attention should be focused on the pixel located at the
same X,Y location of the original bitmap.
Index 6 = Start of the Convolution Kernel which MUST be of the width and height detailed in Index ZERO and Index 1

Any further Large_integers after the above is interpreted as another Convolution kernel following the same structure as above

Index_to_Start_of_Filter is the starting ZERO-based index to the start of each convolution kernel i.e. ZERO..n

Procedure_to_Run_After_Each_Convolution_Kernel is a pointer to a user-defined procedure which allows the user
to interpret and further process the convolution kernel result for each pixel in the original bitmap.
Usually used to clamp the kernel result values to within specific integer ranges or to change the output pixel values.
If you have not defined any procedure to run after each application of a convolution filter specify the
parameter Procedure_to_Run_After_Each_Convolution_Kernel to be the pre-defined constant RUN_NO_OTHER_PROCEDURE

The procedure must of type After_Each_Convolution_Kernel_Procedure_Type
which following the convention:

Procedure Name_of_Procedure( Var Any_Bitmap, Temporary_Storage_Bitmap: Virtual_Bitmap; Current_X_Coordinate, Current_Y_Coordinate: Large_Integer; Var Original_Pixel, Changed_Pixel: RGB_Pixel_Type );


Example:

Uses Routines;

Const
// Width, Height, X_Centre, Y_Centre, Image Output Type
Sobel_Edge_Detection_Convolution_Filter : Array[ 1..30 ] of Large_Integer = ( // Detects Vertical edges and sharp transients between luminance values
3, 3, 1, 1, 35, OUTPUT_BLACK_AND_WHITE_IMAGE,
-1, 0, 1,
-2, 0, 2,
-1, 0, 1,

// Detects Horizontal edges and sharp transients between luminance values
3, 3, 1, 1, 35, OUTPUT_BLACK_AND_WHITE_IMAGE,
1, 2, 1,
0, 0, 0,
-1, -2, -1 );
Var
x, y,

Index_to_Start_of_Kernel,
End_of_Convolution_Kernels : Large_Integer;

Original_Bitmap,
Temporary_Bitmap : Virtual_Bitmap;

Begin
Initialize_Bitmap( Original_Bitmap, 'Original Image.bmp' ); // Allocate memory for bitmap variable and then load image from disk file
Initialize_Bitmap( Temporary_Bitmap ); // Allocate only the simple basic memory for this bitmap variable

// Get the last index to the array of integers denoted by Convolutions_to_Apply
End_of_Convolution_Kernels := High( Sobel_Edge_Detection_Convolution_Filter );

// Apply a series of pre-defined convolution filters to each pixel in the original image
i := ZERO;
Repeat
// Run each convolution kernel over the entire bitmap and save the kernel results to Temporary_Bitmap
For y := ZERO to Original_Bitmap.Height - 1 do
For x := ZERO to Original_Bitmap.Width - 1 do
Apply_Convolution_Filter_to_Pixel( Original_Bitmap, Temporary_Bitmap, x, y, Index_to_Start_of_Kernel, Sobel_Edge_Detection_Convolution_Filter, RUN_NO_OTHER_PROCEDURE );

// Increment the counter I so that it points to the next set of integers that represent a convolution filter record
Inc( Index_to_Start_of_Kernel, ( Sobel_Edge_Detection_Convolution_Filter[ Index_to_Start_of_Kernel ] * Sobel_Edge_Detection_Convolution_Filter[ Index_to_Start_of_Kernel + 1 ] ) + 6 );

Until i >= End_of_Convolution_Kernels;

// Overwrite the original bitmap with the reference bitmap image
Original_Bitmap.Assign( Temporary_Bitmap );

Original_Bitmap.SaveToFile( 'Changed Image.bmp' );

Destroy_All_These_Bitmaps( [ Original_Bitmap, Temporary_Bitmap ] );
End.
}
 
Upvote 0
HarryFilm said:
LDS said:
HarryFilm said:
Here is some ACTUAL CODEC CODE I am using in this project.

That's just a long comment for a file header, and a prototype of a function using your logorrheic style. No actual code.

Then there's your usual technobabble - btw, it's "Cortex", not "Coretex"... and I don't remember an A4 one (Apple A4 is a different beast...)

---

A4 is a specific sub-model of the Cortex (misspelling fixed!) cores used by embedded device makers which is even MORE RISC-y (i.e. reduced instructions) for power-saving reasons. The compiler is supposed to do all the fancy logic to allow higher-level functions that more CISC-y (i.e. more complex) cpu instruction sets do at the hardware level. A4 is NOT an Apple moniker but rather something originally from either Texas Instruments or Qualcomm (not sure!) who licence the ARM architecture from ARM itself but I am seeing that A4 moniker in the actual ARM company's (i.e. the Advanced RISC Machines company now owned by Softbank) documentation I've got.

Canon uses this A4 core for simple power-saving reasons at 25 MHz up to 1.5 GHz
in its DIGIC series of camera image processing and control CPU's.


I ain't no mere beginner in this...Here is how to do SIMPLE AND EFFECTIVE EDGE DETECTION from MY OWN CODE i designed all by myself from scratch which WILL run on ARM, Intel/AMD, MIPS, IBM Power, SuperSPARC and various DSP processors
INCLUDING the Canon DIGIC series:


COPY this code to a Landscape-mode using 10 point narrow font in a word processing document to see the entire RATHER NICELY DOCUMENTED code segment.
THIS BELOW CODE SEGMENT IS FULLY GNU GPL 3.0 OPEN SOURCE LICENCE FOR YOU programmers: Have at it for modification but REMEMBER to keep your work and modifications to this code to the GNU GPL 3.0 licence terms!


Procedure Apply_Convolution_Filter_to_Pixel( Var Any_Bitmap, Temporary_Storage_Bitmap: Virtual_Bitmap; Starting_X_Coordinate, Starting_Y_Coordinate, Index_to_Start_of_Filter: Large_Integer; Var Convolution_Kernel: Array of Large_Integer; Procedure_to_Run_After_Each_Convolution_Kernel: After_Each_Convolution_Kernel_Procedure_Type );

Var
x, y, i,

Current_X_Coordinate,
Current_Y_Coordinate,

Current_Pixel_Brightness,
Original_Pixel_Brightness,

Final_Convolution_Result,
Final_Convolution_Grid_Result,

Convolution_Kernel_Width,
Convolution_Kernel_Height,

X_Centre_of_Kernel,
Y_Centre_Of_Kernel,

Edge_Sensitivity_Threshold,

Type_of_Output_Image_Desired : Large_Integer;

New_Pixel,
Sampled_Pixel,
Original_Pixel : RGB_Pixel_Type;

New_Pixel_Brightness : Floating_Point;

Convolution_Operation_is_Additive : Boolean_Type;

Begin
Try
// Find out if this is the first convolution kernel applied in a series of filters
if Index_to_Start_of_Filter = ZERO then
Convolution_Operation_is_Additive := FALSE // Assume that this is the first in a series of kernels being applied
else
Convolution_Operation_is_Additive := TRUE; // Assume that this is the second or later in a series of kernels being applied

Original_Pixel := Get_Pixel( Any_Bitmap, Starting_X_Coordinate, Starting_Y_Coordinate );
Original_Pixel_Brightness := Get_Greyscale_Value_of_RGB_Pixel( Original_Pixel );

Final_Convolution_Result := ZERO;
Final_Convolution_Grid_Result := ZERO;

Convolution_Kernel_Height := Convolution_Kernel[ Index_to_Start_of_Filter ];
Convolution_Kernel_Width := Convolution_Kernel[ Index_to_Start_of_Filter + 1 ];

// The location of where the X, Y coordinate of where the convolution kernel itself should be centred over a pixel
X_Centre_of_Kernel := Convolution_Kernel[ Index_to_Start_of_Filter + 2 ];
Y_Centre_Of_Kernel := Convolution_Kernel[ Index_to_Start_of_Filter + 3 ];

// Any pixels luminances under this threshold will be ignored
Edge_Sensitivity_Threshold := Round( ( Convolution_Kernel[ Index_to_Start_of_Filter + 4 ] * 0.01 ) * MAXIMUM_COLOUR_CHANNEL_VALUE );

// Details whether the image should output a black & white image, greyscale or original image with filter applied
Type_of_Output_Image_Desired := Convolution_Kernel[ Index_to_Start_of_Filter + 5 ];

{
This is the main convolution loop which multiples all the elements of the convolution kernel
and all the elements of the sampled image region. The coefficients in the Kernel will determine
whether the difference between the sampled pixel and surrounding pixels is amplified or mitigated.
}
i := Index_to_Start_of_Filter + 6;
for y := 1 to Convolution_Kernel_Height do
for x := 1 to Convolution_Kernel_Width do
Begin
Current_X_Coordinate := ( Starting_X_Coordinate + x ) - X_Centre_of_Kernel;
Current_Y_Coordinate := ( Starting_Y_Coordinate + y ) - Y_Centre_of_Kernel;

// The convolution kernel can be equal in width and height or asymmetrical thus make sure we overlay the kernel overtop the correct pixel
Sampled_Pixel := Get_Pixel( Any_Bitmap, Current_X_Coordinate, Current_Y_Coordinate );

Current_Pixel_Brightness := Get_Greyscale_Value_of_RGB_Pixel( Sampled_Pixel );

// multiple each Kernel element with each pixel of the sampled image region
Final_Convolution_Result := Final_Convolution_Result + ( Current_Pixel_Brightness * Convolution_Kernel[ i ] );

// add to the sum total of the all kernel multiplication operations
Final_Convolution_Grid_Result := Final_Convolution_Grid_Result + Convolution_Kernel[ i ];

Inc( i );
End;

// Get the final convolution filter result
if Final_Convolution_Grid_Result = ZERO then
Final_Convolution_Result := ( Final_Convolution_Result + 1 ) div ( Final_Convolution_Grid_Result + 1 )
else
Final_Convolution_Result := Final_Convolution_Result div Final_Convolution_Grid_Result;

// Set each colour channel to legal RGB values
Final_Convolution_Result := RGB_Clamp( Final_Convolution_Result, RGB_Colour_Space );

{
Determine how the kernel result should be interpreted and how Temporary_Storage_Bitmap
should have its pixels created or copied from the original bitmap.

If this is the first or only application of a convolution kernel, then any
convolution kernel result that is above the Edge_Sensitivity_Threshold is copied to the current X, Y coordinate
of Temporary_Storage_Bitmap. Those convolution kernel results that fall below Edge_Sensitivity_Threshold
cause the pixel located at current X,Y coordinate of Temporary_Storage_Bitmap
to be set to a default value of dark black or the pixel value from the original bitmap
may be copied to Temporary_Storage_Bitmap.

If this is the second or later in a series of convolutions being applied,
then all convolution operations are additive to Temporary_Storage_Bitmap.
This means that only pixels that are above the Edge_Sensitivity_Threshold
are added to Temporary_Storage_Bitmap. Pixels that fall below Edge_Sensitivity_Threshold
are ignored and no pixel is modified on Temporary_Storage_Bitmap.
}
Case Type_of_Output_Image_Desired of
OUTPUT_BLACK_AND_WHITE_IMAGE : Begin
{
Used for creating a reference bitmap image (i.e Temporary_Storage_Bitmap) which is used
in other subsequent image manipulation of the original image.
i.e. if pixel at reference image X,Y location is White then the original bitmap's pixel
at that location needs to be changed or looked for further processing otherwise
if Black then pixel at X,Y on the original can be ignored and no futher processing need be done.
}
If Final_Convolution_Result >= Edge_Sensitivity_Threshold then
New_Pixel := RGB_Colour_100_Percent_White
else // if necessary, set the pixel that is below the edge sensivity threshold to dark black
if Convolution_Operation_is_Additive = FALSE then
New_Pixel := RGB_Colour_ZERO_Percent_Black
else
Exit;
End;


OUTPUT_ORIGINAL_IMAGE_WITH_FILTER_APPLIED : Begin
{
Copies the original pixel data to Temporary_Storage_Bitmap and applies
the convolution filter to the copied pixel data.
When all processing is done to Temporary_Storage_Bitmap,
it can be copied back to the original bitmap.
i.e. original bitmap is overwritten by Temporary_Storage_Bitmap.
}
If Final_Convolution_Result >= Edge_Sensitivity_Threshold then
Begin
New_Pixel := Original_Pixel;
Set_Pixel_to_Absolute_Luminance_Level( New_Pixel, Final_Convolution_Result );
End
else // if necessary, set the pixel that is below the edge sensivity threshold to dark black
if Convolution_Operation_is_Additive = FALSE then
New_Pixel := Original_Pixel
else
Exit;
End;

OUTPUT_AS_GREY_SCALE_IMAGE : Begin
{
Temporary_Storage_Bitmap is changed such that each Final_Convolution_Result is output
as a greyscale level pixel.

Future image processing operations can interpretthe differing Greyscale levels
of Temporary_Storage_Bitmap as a degree of interest or degree of change that should
be applied to each pixel of the original bitmap.

i.e. Dark Black = no change to original pixels is required
50% grey = partially apply further image processing operations
Bright White = fully apply subsequent image processing operations
}
If Final_Convolution_Result >= Edge_Sensitivity_Threshold then
Set_Each_Channel_of_RGB_Pixel_to_Same_Value( New_Pixel, Final_Convolution_Result )
else // if necessary, set the pixel that is below the edge sensivity threshold to dark black
if Convolution_Operation_is_Additive = FALSE then
New_Pixel := RGB_Colour_ZERO_Percent_Black
else
Exit;
End;
else
Begin
// if unable to determine how to interpret a kernel result give a default answer
New_Pixel := RGB_Colour_ZERO_Percent_Black;

Set_Bitmap_Error( 'Unable to the perform the designated convolution because the Type_of_Output_Image_Desired'+ CRLF +
'parameter may be bad. It is currently set at: ' + NumberToString( Type_of_Output_Image_Desired ) + DOUBLE_SPACE +
'Output pixel has been changed to RGB_Colour_ZERO_Percent_Black.', 'Apply_Convolution_Filter_to_Pixel()' );
End;
End;

// Make sure the procedure actually exists before running the code that will examine
// and if necessary, modify each output pixel of a convolution kernel before it is copied to the Temporary_Storage_Bitmap
if @Procedure_to_Run_After_Each_Convolution_Kernel <> RUN_NO_OTHER_PROCEDURE then
Procedure_to_Run_After_Each_Convolution_Kernel( Any_Bitmap, Temporary_Storage_Bitmap, Starting_X_Coordinate, Starting_Y_Coordinate, Original_Pixel, New_Pixel );

// Save the convolution result to the reference image which can then be interpreted in subsequent image processing operations
Set_Pixel( Temporary_Storage_Bitmap, Starting_X_Coordinate, Starting_Y_Coordinate, New_Pixel );

Except // on any errors return a default result
New_Pixel := RGB_Colour_ZERO_Percent_Black;

Set_Bitmap_Error( 'Unable to the perform the designated convolution because of a number of reasons including'+ CRLF +
'parameters may be out-of-range, Convolution_Kernel is improperly declared or has values that are to big or too small,' + DOUBLE_SPACE +
'Output pixel has been changed to RGB_Colour_ZERO_Percent_Black.', 'Apply_Convolution_Filter_to_Pixel()' );

Set_Pixel( Temporary_Storage_Bitmap, Starting_X_Coordinate, Starting_Y_Coordinate, New_Pixel );
End;
End;
{
Convolution is both a mathematical concept and an important tool in data processing,
in particular in digital signal and image processing.

Convolution allows one to multiply the individual elements of two numerical arrays together
and return an averaged numerical representation of the contents of both numerical arrays.
These convolution functions can interpret or discern the edges of image elements or objects,
find specific levels of pixel brightness, obtain the degree of colour or luminance transition
between two adjacent pixels and other useful functions.

Is essence, convolution is the process of finding the difference between two numbers and then
either mitigating that difference or amplifying the difference.
Applying single or multiple convolution kernels to a pixel sample can achieve effects
such as image smoothing, sharpening, edge detection and embossing.

for example:

x1, x2, x3 y1, y2, y3
x4, x5, x6 X y4, y5, y6 = averaged result after multiplying all the elements together
x7, x8, x9 y7, y8, y9

A Convolution Filter Kernel is an odd-sized, even-sized, or asymmetrically-sized matrix of integers
usually 3x3 or 5x5, 7x7 or 9x9, 2x2 or 4x4 or 6x6 elements in size containing a patterned series of
positive or negative numbers. These numbers can be multipled with each
Red, Green and Blue colour channel of a pixel or just the single computed Greyscale value of each RGB pixel.

The convolution result can saved to the secondary reference bitmap called Temporary_Storage_Bitmap
as a Black and White bitmap, Greyscale bitmap, or original pixels with convolution applied.
This will give you the option of using the convolution kernel results as a reference that
details which pixels on the original bitmap to take a closer look at or process further.

This is chosen by passes to the parameter Type_of_Output_Image_Desired one of the following parameters:

OUTPUT_BLACK_AND_WHITE_IMAGE
OUTPUT_ORIGINAL_IMAGE_WITH_FILTER_APPLIED
OUTPUT_AS_GREY_SCALE_IMAGE
DO_NOT_CHANGE_IMAGE

The resulting bitmap can be used as is, with the convolution effect applied or the output bitmap
can be referenced by other subsequent image processing operations.

i.e. any pixels on the reference bitmap which is Dark Black indicates that the pixel on the Original bitmap
located at the sampled X,Y coordinate should be ignored, otherwise a Bright White pixel indicates the original pixel
should have subsequent image processing operations fully applied. In-between greyscale values indicate
the degree of which any subsequent special effect or image processing operation should be applied.

Note that the larger the convolution kernel is in terms of number-of-elemnts, the longer the operation will take.

1024 by 768 pixel, 24 bit image = 2,359,296 Bytes using a 3x3 convolution filter = 21,233,664 operations

the sames image using a 5x5 filter will require 58,982,400 operations, A 7x7 filter will require 115,605,504 operations
and a 9x9 filter will require 191,102,976 operations. We get a rather large increase in the amount of time that is required
for each convolution to be applied if a convolution filter is larger than 3x3.

The larger convolution filters are used where accuracy of result is more important than execution speed.

PLEASE NOTE that the convolution kernels used in this routine can be be odd sized, even sized or asymmetric
including kernel sizes such as 3x3 or 5x5, 7x7, 9x9 ..or.. 2x2, 4x4, 8x8, ...or... 4x1, 3x4 or 3x5 or 7x2.
This allows very advanced image processing operations on square-shaped groups of pixels, single horizontal lines of pixels,
single vertical lines of pixels and rectangular groups of pixels.


The parameter Any_Bitmap is the original bitmap upon which the convolution kernel will be applied

Temporary_Storage_Bitmap is a holding area (or reference bitmap) which will temporarily store the
convolution kernel results.

Starting_X_Coordinate and Starting_Y_Coordinate detail upon which pixel of the original bitmap
the convolution kernel will be applied. Screen coordinate 0,0 is the upper left corner of original bitmap.

Convolution_Kernel is an array of Large_Integers which contains the information about a single convolution kernel
or it can also contain multiple convolution kernels which can then be applied one after another.
The convolutions kernels do NOT have to be the same size which thus allows the possibility of
applying a 3x3 kernel, then a 7x2 kernel, a 4x4 kernel and then a 5x5 kernel in succession.

The Large_Integer Array which is passed to the Convolution_Kernel parameter must follow the following convention:

Index ZERO = width of the convolution kernel itself from 1..x
Index 1 = height of the convolution kernel itself from 1..y
Index 2 = the X coordinate of where the kernel will be centred over the sampled pixel from the original bitmap - this is 1..x based NOT ZERO based
Index 3 = the Y coordinate of where the kernel will be centred over the sampled pixel from the original bitmap - this is 1..y based NOT ZERO based
Index 4 = An edge sensitivity flag which details that any convolution results that are under this optimal threshold are ignored
and not saved to Temporary_Storage_Bitmap. The integer is changed to a percentage of the maximum allowable
value in the individual colour channel of an RGB_Pixel_Type.
thus integer value example could be 50 = 50% of 255 = 127, or 25 = 25% of 255 = 63 or 75 = 75% of 255 = 191
This makes sure that only relevant pixels are considered for processsing and not stray pixels or unimportant ones.
This can be changed on the fly if neccessary by assigning another Large_Integer value to this index location.
Index 5 = One of the 4 constants OUTPUT_BLACK_AND_WHITE_IMAGE, OUTPUT_ORIGINAL_IMAGE_WITH_FILTER_APPLIED,
OUTPUT_AS_GREY_SCALE_IMAGE, DO_NOT_CHANGE_IMAGE which tells the routine how to interpret the convolution kernel results.
This allows for further image processing where the pixels stored in Temporary_Storage_Bitmap can be referenced
and the level of greyscale can be interpreted to mean how much attention should be focused on the pixel located at the
same X,Y location of the original bitmap.
Index 6 = Start of the Convolution Kernel which MUST be of the width and height detailed in Index ZERO and Index 1

Any further Large_integers after the above is interpreted as another Convolution kernel following the same structure as above

Index_to_Start_of_Filter is the starting ZERO-based index to the start of each convolution kernel i.e. ZERO..n

Procedure_to_Run_After_Each_Convolution_Kernel is a pointer to a user-defined procedure which allows the user
to interpret and further process the convolution kernel result for each pixel in the original bitmap.
Usually used to clamp the kernel result values to within specific integer ranges or to change the output pixel values.
If you have not defined any procedure to run after each application of a convolution filter specify the
parameter Procedure_to_Run_After_Each_Convolution_Kernel to be the pre-defined constant RUN_NO_OTHER_PROCEDURE

The procedure must of type After_Each_Convolution_Kernel_Procedure_Type
which following the convention:

Procedure Name_of_Procedure( Var Any_Bitmap, Temporary_Storage_Bitmap: Virtual_Bitmap; Current_X_Coordinate, Current_Y_Coordinate: Large_Integer; Var Original_Pixel, Changed_Pixel: RGB_Pixel_Type );


Example:

Uses Routines;

Const
// Width, Height, X_Centre, Y_Centre, Image Output Type
Sobel_Edge_Detection_Convolution_Filter : Array[ 1..30 ] of Large_Integer = ( // Detects Vertical edges and sharp transients between luminance values
3, 3, 1, 1, 35, OUTPUT_BLACK_AND_WHITE_IMAGE,
-1, 0, 1,
-2, 0, 2,
-1, 0, 1,

// Detects Horizontal edges and sharp transients between luminance values
3, 3, 1, 1, 35, OUTPUT_BLACK_AND_WHITE_IMAGE,
1, 2, 1,
0, 0, 0,
-1, -2, -1 );
Var
x, y,

Index_to_Start_of_Kernel,
End_of_Convolution_Kernels : Large_Integer;

Original_Bitmap,
Temporary_Bitmap : Virtual_Bitmap;

Begin
Initialize_Bitmap( Original_Bitmap, 'Original Image.bmp' ); // Allocate memory for bitmap variable and then load image from disk file
Initialize_Bitmap( Temporary_Bitmap ); // Allocate only the simple basic memory for this bitmap variable

// Get the last index to the array of integers denoted by Convolutions_to_Apply
End_of_Convolution_Kernels := High( Sobel_Edge_Detection_Convolution_Filter );

// Apply a series of pre-defined convolution filters to each pixel in the original image
i := ZERO;
Repeat
// Run each convolution kernel over the entire bitmap and save the kernel results to Temporary_Bitmap
For y := ZERO to Original_Bitmap.Height - 1 do
For x := ZERO to Original_Bitmap.Width - 1 do
Apply_Convolution_Filter_to_Pixel( Original_Bitmap, Temporary_Bitmap, x, y, Index_to_Start_of_Kernel, Sobel_Edge_Detection_Convolution_Filter, RUN_NO_OTHER_PROCEDURE );

// Increment the counter I so that it points to the next set of integers that represent a convolution filter record
Inc( Index_to_Start_of_Kernel, ( Sobel_Edge_Detection_Convolution_Filter[ Index_to_Start_of_Kernel ] * Sobel_Edge_Detection_Convolution_Filter[ Index_to_Start_of_Kernel + 1 ] ) + 6 );

Until i >= End_of_Convolution_Kernels;

// Overwrite the original bitmap with the reference bitmap image
Original_Bitmap.Assign( Temporary_Bitmap );

Original_Bitmap.SaveToFile( 'Changed Image.bmp' );

Destroy_All_These_Bitmaps( [ Original_Bitmap, Temporary_Bitmap ] );
End.
}

Is this a real thing you are doing? I do not understand what you mean by all the codecs. What would all this do? I have a 7d mk ii and a 5d mk iv. What new powers would this all unleash?
 
Upvote 0
Jul 21, 2010
31,099
12,863
What new powers would this all unleash?

It will make your video frame rate faster than a speeding bullet, your video autofocus more powerful than a locomotive, and your lenses able to capture video of tall buildings without boundless keystoning. But you should avoid using Kryptonite brand memory cards, they'll render the codec powerless and vulnerable.
 
Upvote 0
neuroanatomist said:
What new powers would this all unleash?

It will make your video frame rate faster than a speeding bullet, your video autofocus more powerful than a locomotive, and your lenses able to capture video of tall buildings without boundless keystoning. But you should avoid using Kryptonite brand memory cards, they'll render the codec powerless and vulnerable.

===

Ironically, he is rather CORRECT! The SOBEL edge detection WILL in fact help me to speed up intraframe and interframe video compression AND allow me to do automatic face detection and eye detection for FAST autofocus AND allow me to do realtime video frame distortion to correct for keystoning or correct for framing errors!

SO THANK YOU NEUROANATOMIST for explaining rather well my above code!

MUCHO GRACIAS!

===

SO What does this code do?...Well....you get 4K 24-to-30 fps intraframe AND interfame encoded video out of a Canon M5/M6, 5D mk2/3 and 6D/7D mark 1/2 at 10-bit 4:2:2 colour sampling from the ENTIRE sensor! This means it turns your $1000 to $4000 canon stills camera into a Canon C300 mk2 Cinema EOS camera! It also turns your $3000-to-$10000 Canon C100, C200, C300, C500 cinema EOS cameras into a high end 4K 4:2:2/4:4:4 10/12/14/16 bit colour sampling 24-to-60 fps $30,000+ C700 camera!

Soooooo......Do you want this new codec OR NOT?
 
Upvote 0

hne

Gear limits your creativity
Jan 8, 2016
331
53
That code ain't an example on how to implement an efficient kernel convolution.

Even worse, there are some surprisingly simple optimisations not made that'd easily double the speed of this, if it is to be used only for Sobel edge detection and Gaussian blur (both of them happen to be separable, if you can accept an error lower than base iso noise levels on modern camera sensors).
 
Upvote 0

LDS

Sep 14, 2012
1,763
293
HarryFilm said:
A4 is a specific sub-model of the Cortex (misspelling fixed!) cores used by embedded device makers which is

Please point us to the relevant ARM documentation... I'm aware of an M4 only.

HarryFilm said:
A4 is NOT an Apple moniker

https://en.wikipedia.org/wiki/Apple_A4 - there's even a picture...

Sobel is a half a century old algorithm, with plenty of examples available, not really groundbreaking. We are waiting for the "magic one", that allowing 4K@999fps on a 6D.

Still, your code is far from being optimized, can evidently leak memory (really a no-no especially on resource-constrained devices like a camera), has lame error checking and reporting. There are other evident code issues, but I'll be happy to point them out when you'll publish the code, this is not a software development forum.
 
Upvote 0

zim

CR Pro
Oct 18, 2011
2,129
318
LDS said:
Still, your code is far from being optimized, can evidently leak memory (really a no-no especially on resource-constrained devices like a camera), has lame error checking and reporting. There are other evident code issues, but I'll be happy to point them out when you'll publish the code, this is not a software development forum.

Well in fairness if you had followed his instructions and - COPY this code to a Landscape-mode using 10 point narrow font in a word processing document.

It wouldn't leak so much :p
.... as long as you didn't use MS Word, that leaks :'(

Anyways Harry publish and be damned, when will we be able to download from Github and have a play ;)
OR
As I said umpty tumpty pages ago send your code to ML and let them comment.

The truth is out there!
 
Upvote 0