bonsai
2013-11-16 18:42:51
Greetings fellow scripters. Warning, TLDR :)
The past few weeks I've been working on a library of object finding functions. I am presenting the current work here for community review and feedback. All input welcome!
This library is not 100% debugged and completed but it it's getting there. I thought it would be smart to get some external opinions and review. The bigger it gets the harder it is to change major design issues. I haven't tested the choose functions yet (and didn't write the chooseWithin function).
I started working on this for a few reasons.
What I present here is a data structure that fully defines all of the colors for an object (inlcuding their CTS settings). It allows you to attach a filter specification per color. You can also assign a filter specification to the object as a whole so you are able to choose different settings for the colors and the objects. Or leave the color information raw (no filter) and only filter the final result (object).
Objects can contain a specification for inclusive logic. This is where all of the defined colors must be found within certain distances from each other. I was inispired on this topic by @Runaway @Runaway; in this post. There are a number of other related posts on the topic that I found (a b c). [Why don't mention tags work for me? Argh.]
I'm going to present some examples first, then describe the code a little afterward. Because examples are fun and the rest is boring!
Examples:
Here is the test image used for the examples. It has 8 colors (dark&light blue, dark&light green, dark&light purple, a rusty red, and orange).

Example 1
[INDENT]The first example just finds the blue objects in the image.
Output for example 1: It returns an ATPA but it only has one row since we only used one color.
[/INDENT]

Example 2:
[INDENT]Same thing but with all the colors in the object. Colors are using CTS 0, 1, and 2.
Output for example 2: Note in this output each color got its own TPA in the ATPA returned[/INDENT]

Example 3:
[INDENT]Find all the colors, break the output into TBoxes for us. We have to add an object level filter to do boxing.
Output for example 3 [/INDENT]

Example 4:
[INDENT]Same as example 3 but returns centerpoint instead of boxes.
Output for example 4 [/INDENT]

Example 5:
[INDENT]Now we get fancy. Add an inclusive spec and find the boxes where ALL of the colors are found within distance 35 of each other:
Output for example 5[/INDENT]

Example 6:
[INDENT]Define two objects. One is the blue and purple colors. The second object is has the rust and orange colors. It also has an inclusive spec demanding that both rust and orange are found near each other. Then we find the rust/orange object within the blue/purple object.
Output for example 6[/INDENT]

Code Discussion
Filter Specifiers
[INDENT]The filter object data is stored in a record. There are functions provided to initialize or create filter objects. There is also a default (null) specification defined.
Filters define how a color or object gets split into subclusters. Each filter has a 'style' indicating which of the splitting functions to use during processing (TPointArray.toATAPA(), .split(), .cluster()). There is an enum defining those.
They can hold width and height variables or a dist variable. This allows the internal functions to use either of those variations. Filters also have a low/high pixel count indicating how big these clusters should be in order to be retained.
Here are the TObjFilterSpec type functions to initialize filters.
And some functions that create TObjFilterSpec objects on the fly:
Inclusive Specifiers
[INDENT]An inclusive spec can be attached to the main object. It is also stored in a record with functions to initialize/create it. There is also a default/null instance defined for cases where no inclusive processing will be required.
The inclusive spec defines the algorithm to be used and a minimum/maximum distance between the colors for them to be considered matches. The algorithm is in an enum and the rest is loaded in the TObjInclusiveSpec.init function.
These functions will create a TObjInclusiveSpec on the fly:
Colors
[INDENT]Colors also have their own record, object functions, and functions to create them. The main object holds an array of colors.
Each color in the array can have any CTS setting and a unique filter specifier, allowing you to define processing with precision.
TObjColor Initializers:
Matching creator functions:
The main object
Like the rest, the TObjFind type is a record holding the data and a number of functions to initialize them.
This type also has the "real' processing functions that find the object data for you.
Creation / query functions:
Processing functions:
Credits:
[INDENT]Runaway's algorithm used with permission and credited in the code.[/INDENT]
Code link:
[INDENT]You probably want to remove the include libupdater.simba at the top and the initializer at the end so it won't try to use my autoupdater. The attachment below has that done already.
http://pastebin.com/raw.php?i=pGwsdnSt[/INDENT]
The past few weeks I've been working on a library of object finding functions. I am presenting the current work here for community review and feedback. All input welcome!
This library is not 100% debugged and completed but it it's getting there. I thought it would be smart to get some external opinions and review. The bigger it gets the harder it is to change major design issues. I haven't tested the choose functions yet (and didn't write the chooseWithin function).
I started working on this for a few reasons.
- Most of the functions that accept more than one color do not associate the CTS setting with the color. They take multiple colors but only one CTS spec. This reduces their value since you pretty much always have different hue/saturation per color.
- I wanted to work with 'inclusive' searching (where all of the given colors must be found in a certain distance from each other)
- I had some confusion on how and when to use different splitting techniques like tpa2atpa and split/cluster and wanted to learn more
- The available functions required me to write a lot of the same code repeatedly. I'm a lazy programmer. I would rather see the complexity moved to the data structures and have the code be simplified.
What I present here is a data structure that fully defines all of the colors for an object (inlcuding their CTS settings). It allows you to attach a filter specification per color. You can also assign a filter specification to the object as a whole so you are able to choose different settings for the colors and the objects. Or leave the color information raw (no filter) and only filter the final result (object).
Objects can contain a specification for inclusive logic. This is where all of the defined colors must be found within certain distances from each other. I was inispired on this topic by @Runaway @Runaway; in this post. There are a number of other related posts on the topic that I found (a b c). [Why don't mention tags work for me? Argh.]
I'm going to present some examples first, then describe the code a little afterward. Because examples are fun and the rest is boring!
Examples:
Here is the test image used for the examples. It has 8 colors (dark&light blue, dark&light green, dark&light purple, a rusty red, and orange).

Example 1
[INDENT]The first example just finds the blue objects in the image.
program example1;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter: TObjFilterSpec;
atpa: T2DPointArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
myObj.init();
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
atpa := myObj.findColorsATPA(searchBox);
end.Output for example 1: It returns an ATPA but it only has one row since we only used one color.
[/INDENT]

Example 2:
[INDENT]Same thing but with all the colors in the object. Colors are using CTS 0, 1, and 2.
program example2;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter: TObjFilterSpec;
atpa: T2DPointArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
myObj.init();
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
myObj.addColor(objColor(3642899, {tol} 38, colorsetting(1), colFilter)); // greens cts1
myObj.addColor(objColor(9181579, {tol} 15, {hue} 0.01, {sat} 2.09, colFilter)); // purples cts2
myObj.addColor(objColor(1376392, colFilter)); // rust cts0 pure
myObj.addColor(objColor(2588671, colFilter)); // orange cts0 pure
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
atpa := myObj.findColorsATPA(searchBox);
end.Output for example 2: Note in this output each color got its own TPA in the ATPA returned[/INDENT]

Example 3:
[INDENT]Find all the colors, break the output into TBoxes for us. We have to add an object level filter to do boxing.
program example3;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter, objFilter: TObjFilterSpec;
tba: TBoxArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
objFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
myObj.init(objFilter);
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
myObj.addColor(objColor(3642899, {tol} 38, colorsetting(1), colFilter)); // greens cts1
myObj.addColor(objColor(9181579, {tol} 15, {hue} 0.01, {sat} 2.09, colFilter)); // purples cts2
myObj.addColor(objColor(1376392, colFilter)); // rust cts0 pure
myObj.addColor(objColor(2588671, colFilter)); // orange cts0 pure
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
tba := myObj.findBoxes(searchBox);
end.Output for example 3 [/INDENT]

Example 4:
[INDENT]Same as example 3 but returns centerpoint instead of boxes.
program example4;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter, objFilter: TObjFilterSpec;
tpa: TPointArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
objFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
myObj.init(objFilter);
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
myObj.addColor(objColor(3642899, {tol} 38, colorsetting(1), colFilter)); // greens cts1
myObj.addColor(objColor(9181579, {tol} 15, {hue} 0.01, {sat} 2.09, colFilter)); // purples cts2
myObj.addColor(objColor(1376392, colFilter)); // rust cts0 pure
myObj.addColor(objColor(2588671, colFilter)); // orange cts0 pure
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
tpa := myObj.findCenters(searchBox);
end.Output for example 4 [/INDENT]

Example 5:
[INDENT]Now we get fancy. Add an inclusive spec and find the boxes where ALL of the colors are found within distance 35 of each other:
program example5;
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
myObj: TObjFind;
colFilter, objFilter: TObjFilterSpec;
tba: TBoxArray;
searchBox : TBox;
clW, clH: integer;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {min} 80, {max} maxInt);
objFilter.init(TObjFilterStyle.CLUSTER, {dist} 35, {min} 80, {max} maxInt);
myObj.init(objFilter);
myObj.addColor(objColor(15106394, {tol} 57, colFilter)); // blues cts0 w/ tol
myObj.addColor(objColor(3642899, {tol} 38, colorsetting(1), colFilter)); // greens cts1
myObj.addColor(objColor(9181579, {tol} 15, {hue} 0.01, {sat} 2.09, colFilter)); // purples cts2
myObj.addColor(objColor(1376392, colFilter)); // rust cts0 pure
myObj.addColor(objColor(2588671, colFilter)); // orange cts0 pure
myObj.setInclusiveSpec(objInclusiveSpec(TObjInclusiveAlg.EXACT,
{minDist} 2, {maxDist} 35));
getclientdimensions(clW, clH);
searchBox := intToBox(0, 138, clW-1, clH-1);
tba := myObj.findBoxes(searchBox);
end.Output for example 5[/INDENT]

Example 6:
[INDENT]Define two objects. One is the blue and purple colors. The second object is has the rust and orange colors. It also has an inclusive spec demanding that both rust and orange are found near each other. Then we find the rust/orange object within the blue/purple object.
program example6
{$include_once srl-6/srl.simba}
{$DEFINE TOBJ_DEBUG}
{$include_once bonsai/libobjfind.simba}
var
blueObj, rustObj: TObjFind;
colFilter, objFilter: TObjFilterSpec;
searchBox : TBox;
clW, clH: integer;
tba: TBoxArray;
begin
colFilter.init(TObjFilterStyle.CLUSTER, {dist} 2, {minCount} 10, {maxCount} maxInt);
objFilter.init(TObjFilterStyle.CLUSTER, {dist} 10, {min} 10, {max} maxInt);
blueObj.init(objFilter);
rustObj.init(objFilter);
blueObj.addColor(objColor(13387839, colFilter)); // blue
blueObj.addColor(objColor(10766755, colFilter)); // purple
rustObj.addColor(objColor(1376392, colFilter)); // red/brown
rustObj.addColor(objColor(2588671, colFilter)); // orange
rustObj.setInclusiveSpec(objInclusiveSpec(TObjInclusiveAlg.EXACT,
{minDist} 2, {maxDist} 35));
getclientdimensions(clW, clH);
searchBox := intToBox(0, 0, clW-1, clH-1);
tba := rustObj.findWithinBoxes(blueObj, searchBox);
end.Output for example 6[/INDENT]

Code Discussion
Filter Specifiers
[INDENT]The filter object data is stored in a record. There are functions provided to initialize or create filter objects. There is also a default (null) specification defined.
type TObjFilterSpec = record {INTERNAL}
const _NULL_FILTER_SPEC {A default/blank TObjFilterSpec}Filters define how a color or object gets split into subclusters. Each filter has a 'style' indicating which of the splitting functions to use during processing (TPointArray.toATAPA(), .split(), .cluster()). There is an enum defining those.
type TObjFilterStyle = (TOATPA, CLUSTER, SPLIT, NONE);They can hold width and height variables or a dist variable. This allows the internal functions to use either of those variations. Filters also have a low/high pixel count indicating how big these clusters should be in order to be retained.
Here are the TObjFilterSpec type functions to initialize filters.
procedure TObjFilterSpec.init();
procedure TObjFilterSpec.init(style: TObjFilterStyle; w: integer;
h: integer; countLow: integer; countHigh: integer); overload;
procedure TObjFilterSpec.init(style: TObjFilterStyle; distance: integer;
countLow: integer; countHigh: integer); overload;And some functions that create TObjFilterSpec objects on the fly:
function objFilterSpec(): TObjFilterSpec;
function objFilterSpec(style: TObjFilterStyle; w: integer;
h: integer; countLow: integer; countHigh: integer): TObjFilterSpec; overload;
function objFilterSpec(style: TObjFilterStyle; distance: integer;
countLow: integer; countHigh: integer): TObjFilterSpec; overload;[/INDENT]Inclusive Specifiers
[INDENT]An inclusive spec can be attached to the main object. It is also stored in a record with functions to initialize/create it. There is also a default/null instance defined for cases where no inclusive processing will be required.
type TObjInclusiveSpec = record {INTERNAL}
const _NULL_INCLUSIVE_SPEC {A default/blank TObjInclusiveSpec}The inclusive spec defines the algorithm to be used and a minimum/maximum distance between the colors for them to be considered matches. The algorithm is in an enum and the rest is loaded in the TObjInclusiveSpec.init function.
type TObjInclusiveAlg = (EXACT, RUNAWAY);
procedure TObjInclusiveSpec.init();
procedure TObjInclusiveSpec.init(alg: TObjInclusiveAlg; min: integer;
max: integer); overload;These functions will create a TObjInclusiveSpec on the fly:
function objinclusiveSpec(): TObjInclusiveSpec;
function objinclusiveSpec(alg: TObjInclusiveAlg; min: integer;
max: integer): TObjInclusiveSpec; overload;[/INDENT]Colors
[INDENT]Colors also have their own record, object functions, and functions to create them. The main object holds an array of colors.
Each color in the array can have any CTS setting and a unique filter specifier, allowing you to define processing with precision.
type TObjColor = record {INTERNAL}
type TObjColorArray = array of TObjColor;TObjColor Initializers:
{CTS0}
procedure TObjColor.init(col: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC);
{CTS0 w/ tolerance}
procedure TObjColor.init(col: integer; tol: integer;
spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload;
{CTS2}
procedure TObjColor.init(col: integer; tol: integer; hue: extended;
saturation: extended; spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload;
{CTS3}
procedure TObjColor.init(col: integer; tol: integer; sensitivity: extended;
spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload;
{any CTS}
procedure TObjColor.init(col: integer; tol: integer; cs: TcolorSettings;
spec: TObjFilterSpec = _NULL_FILTER_SPEC); overload;Matching creator functions:
function objColor(col: integer; spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor;
function objColor(col: integer; tol: integer;
spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload;
function objColor(col: integer; tol: integer; hue: extended;
saturation: extended; spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload;
function objColor(col: integer; tol: integer; sensitivity: extended;
spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload;
function objColor(col: integer; tol: integer; cs: TcolorSettings;
spec: TObjFilterSpec = _NULL_FILTER_SPEC): TObjColor; overload;[/INDENT]The main object
Like the rest, the TObjFind type is a record holding the data and a number of functions to initialize them.
This type also has the "real' processing functions that find the object data for you.
TObjFind = record {INTERNAL}Creation / query functions:
procedure TObjFind.init(colArr: TObjColorArray; spec: TObjFilterSpec = _NULL_FILTER_SPEC;
inclSpec: TobjInclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = '');
procedure TObjFind.init(spec: TObjFilterSpec = _NULL_FILTER_SPEC;
inclSpec: TobjInclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = ''); overload;
procedure TObjFind.init(col: TObjColor; spec: TObjFilterSpec = _NULL_FILTER_SPEC;
inclSpec: TobjInclusiveSpec = _NULL_INCLUSIVE_SPEC; nameStr: string = ''); overload;
procedure TObjFind.addColor(col: TObjColor);
procedure TObjFind.setColors(colArr: TObjColorArray);
function TObjFind.getColors(): TObjColorArray;
procedure TObjFind.setFilterSpec(spec: TObjFilterSpec);
function TObjFind.getFilterSpec(): TObjFilterSpec;
procedure TObjFind.setInclusiveSpec(iSpec: TObjInclusiveSpec);
function TObjFind.getInclusiveSpec(): TObjInclusiveSpec;
function TObjFind.getLastPoint(): TPoint;
function TObjFind.getLastBox(): TBox;Processing functions:
function TObjFind.findColorsATPA(searchBox: TBox = [-1,-1,-1,-1];
sortFrom: TPoint = [-1,-1]): T2DPointArray;
function TObjFind.findColors(searchBox: TBox = [-1,-1,-1,-1];
sortFrom: TPoint = [-1,-1]): TPointArray;
function TObjFind.findBoxes(searchBox: TBox = [-1,-1,-1,-1];
sortFrom: TPoint = [-1,-1]): TBoxArray;
function TObjFind.findCenters(searchBox: TBox = [-1,-1,-1,-1];
sortFrom: TPoint = [-1,-1]): TPointArray;
function TObjFind.choose(searchBox: TBox; sortFrom: TPoint; matchUptext: TStringArray;
excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray;
byBox: boolean = false; pointVariance: integer = 5): boolean;
function TObjFind.choose(searchBox: TBox; matchUptext: TStringArray;
excludeUptext: TStringArray; mouseStyle: integer; choices: TStringArray;
byBox: boolean = false; pointVariance:integer = 5): boolean; overload;
function TObjFind.findWithinATPA(enclosingObj: TObjFind;
searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): T2DPointArray;
function TObjFind.findWithinColors(enclosingObj: TObjFind;
searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TPointArray;
function TObjFind.findWithinBoxes(enclosingObj: TObjFind;
searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TBoxArray;
function TObjFind.findWithinCenters(enclosingObj: TObjFind;
searchBox: TBox = [-1,-1,-1,-1]; sortFrom: TPoint = [-1,-1]): TPointArray;
function TObjFind.chooseWithin(enclosingObj: TObjFind; searchBox: TBox;
sortFrom: TPoint; matchUptext: TStringArray; excludeUptext: TStringArray;
mouseStyle: integer; choices: TStringArray; byBox: boolean = false;
pointVariance: integer = 5): boolean;Credits:
[INDENT]Runaway's algorithm used with permission and credited in the code.[/INDENT]
Code link:
[INDENT]You probably want to remove the include libupdater.simba at the top and the initializer at the end so it won't try to use my autoupdater. The attachment below has that done already.
http://pastebin.com/raw.php?i=pGwsdnSt[/INDENT]