Welcome to HALCON Core Guidelines.

The HALCON Core Guidelines are a result of many years of using MVTec HALCON at http://www.heindl-solutions.com/ to develop demanding machine vision applications.

All product and company names are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.

Concatenation of tuples

Tuples are concatenated and entries are appended at the end of a tuple with the [] syntax.

* Initialize
SomeTuple := []
* ...
SomeTuple := [SomeTuple, 'someEntry']
SomeTuple := [SomeTuple, 'someOtherEntry']

Example

read_image (Images, ['fabrik','monkey'])
count_obj (Images, NumberImages)
Features := []
for Index0 := 0 to NumberImages-1 by 1
  select_obj (Images, Image, Index0+1)
  FindSomeFeatureValue (Image, Value)
  Features := [Features, Value]
endfor

Concatenation of objects

Objects are concatenated with concat_obj.

* Initialize
gen_empty_obj (ManyObjects)
* ...
concat_obj (ManyObjects, SomeOtherObject, ManyObjects)
* ...
concat_obj (ManyObjects, SomeOtherObject2, ManyObjects)

Example

gen_empty_obj (Images)
for Index := 0 to 4 by 1
  GrabSomeImage (Image)
  concat_obj (Images, Image, Images)
endfor

True/false variants

Iterate over two variants of the same code that once uses 'true' and in the second iteration uses 'false'.

IndexTrueFalse := 0
TrueFalseTuple := ['false','true'][IndexTrueFalse]

Example

for IndexTrueFalse := 0 to 1 by 1
  affine_trans_image (Image, ImageAffinTrans, HomMat2DRotate, 'constant', ['false','true'][IndexTrueFalse])
  * ...
endfor

Find matches

Use the operator tuple_find or the in-line operation find together with the check find(Tuple, ToFind) > -1 to see if if an element is contained in a container tuple.

Contains := (find(Tuple, ToFind) > -1)

Reason In using the check find(Tuple, ToFind) > -1 instead of e.g. find(Tuple, ToFind) != -1 care is taken of the case where the first argument is the empty tuple. In this case, the if branch in the following example is not taken, as expected:

Example

Files := 'blister/blister_0'+[1:6]
read_image (Images, Files)
* ImagesToIgnore := [0,2,3]
ImagesToIgnore := []

for Index0 := 0 to |Files|-1 by 1
  if (find(ImagesToIgnore, Index0) > -1)
    continue
  endif
  select_obj (Images, Image, Index0+1)
  dev_display (Image)
  * ...
endfor

Selecting entries in multiple tuples or objects using indices

Entries of a tuple selected by index via the [] operator.

Text := ['Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consetetur', 'sadipscing']
SelectMask := [0,1,5]
SelectedText := Text[SelectMask]
* SelectedText = ['Lorem', 'ipsum', 'consetetur']

Reason After e.g. sorting according to a specific criteria in one tuple, entries of this and other tuples and objects are selected via the select_mask operator (tuples) or the select_obj operator in conjunction with the select_mask operator (objects).

Example

gen_random_region (RegionRandom, 128, 128)
get_region_runs (RegionRandom, Row, ColumnBegin, ColumnEnd)

* Sort all chord variables by chord length
tuple_sort_index (ColumnEnd - ColumnBegin + 1, Indices)
RowSort := Row[Indices]
ColumnBeginSort := ColumnBegin[Indices]
ColumnEndSort := ColumnEnd[Indices]

* Get entry with maximum chord length
tuple_sort_index (ColumnEnd - ColumnBegin + 1, MaxIndex)
MaxIndex := MaxIndex[|MaxIndex|-1]
MaxRow := Row[MaxIndex]
MaxColumnBegin := ColumnBegin[MaxIndex]
MaxColumnEnd := ColumnEnd[MaxIndex]

* Get entry with minimum chord length
tuple_sort_index (ColumnEnd - ColumnBegin + 1, MinIndex)
MinIndex := MinIndex[0]
MinRow := Row[MinIndex]
MinColumnBegin := ColumnBegin[MinIndex]
MinColumnEnd := ColumnEnd[MinIndex]

* Select objects
read_image (Images, ['fabrik', 'monkey', 'alpha1'])
Indices0 := [0,2]
select_obj (Images, ImagesSelected, Indices0+1) // fabrik, alpha1

Selecting entries in multiple tuples or objects using a mask

Entries of a tuple are selected by a mask via the select_mask operator or the select_obj operator in conjunction with the select_mask operator.

Text := ['Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consetetur', 'sadipscing']
SelectMask := [1,1,0,0,0,1,0]
SelectedText := select_mask(Text,SelectMask)
* SelectedText = ['Lorem', 'ipsum', 'consetetur']

Reason After selecting according to a specific criteria in one tuple, entries of this and other tuples and objects are selected via the select_mask operator or the select_obj operator in conjunction with the select_mask operator.

Example

Filenames := ['monkey', 'mreut', 'fabrik', 'alpha1', 'alpha2']
read_image (Images, Filenames)
threshold (Images, Region, 128, 255)
area_center (Region, Area, __, __)
Mask := Area [>] 200000
* Mask := [0,1,0,1,1]
FilenamesBigArea := select_mask(Filenames,Mask)
SelectedFilenames := select_mask(Filenames,Mask)
* SelectedFilenames = ['mreut', 'alpha1', 'alpha2']
select_obj (Images, ObjectSelected, select_mask([1:|Mask|],Mask))
* or use HALCON's predefined procedure select_mask_obj

Always use 0-based indices

Many operators on objects use 1-based indices, in contrast to tuples and vectors which use 0-based indices. Nevertheless, 0-based indices should be used until one of these operators is invoked. The most prominent operators that use 1-based indices are select_obj, copy_obj, access_channel.

select_obj (Image, ObjectSelected, Indices0+1)

Reason It is much easier to reason about the indexing if the same index base is used everywhere in the HALCON script. As most other languages use 0-based indices, use them in HALCON code for tuples and objects. To be compatible with 1-based HALCON object indexing, add 1 in the corresponding operator call (only).

Example

list_image_files ('bin_switch', 'default', [], ImageFiles)
read_image (Images, ImageFiles)

* Train with 60% of the images
TrainIndices0 := [0:int(0.60*|ImageFiles|)]
TrainFiles := ImageFiles[TrainIndices0]
select_obj (Images, TrainImages, TrainIndices0+1)

* Test with 20% of the images
TestIndices0 := [int(0.60*|ImageFiles|)+1:0.80*|ImageFiles|]
TestFiles := ImageFiles[TestIndices0]
select_obj (Images, TestImages, TestIndices0+1)

* Cross validate with another 20% of the images
CvIndices0 := [int(0.80*|ImageFiles|)+1:|ImageFiles|-1]
CvFiles := ImageFiles[CvIndices0]
select_obj (Images, CvImages, CvIndices0+1)

for Index0 := 0 to |TestFiles|-1 by 1
  Filename := TestFiles[Index0]
  select_obj (TestImages, Image, Index0+1)
  * ...
endfor

How to generate alternating sequences

Alternating sequences come in handy to start with an algorithm in the middle and then making your way towards the lower and upper bound.

* Generate an alternating sequence e.g. [2, 3, 1, 4, 0] for NumberImages = 5
tuple_gen_sequence (1, NumberImages, 1, Sequence)
AltSeq := int(pow(-1,Sequence)) * (Sequence/2) + ((NumberImages-1)/2)
* AltSeq = [2,3,1,4,0]

* Also works for 0
NumberImages := 0
tuple_gen_sequence (1, NumberImages, 1, Sequence)
AltSeq := int(pow(-1,Sequence)) * (Sequence/2) + ((NumberImages-1)/2)
* AltSeq = []

Example

Filenames := 'ampoules/ampoules_' + [1:8]$'.2' + '.png'
read_image (Image, Filenames)
tuple_gen_sequence (1, |Filenames|, 1, Sequence)
AltSeq := int(pow(-1,Sequence)) * (Sequence/2) + ((|Filenames|-1)/2)
* Starting from the middle, get the match with a (dummy) distance 0
BestIndex0 := []
for Index0 := 0 to |AltSeq|-1 by 1
  CurrentIndex0 := AltSeq[Index0]
  get_distance (CurrentIndex0, Distance) // output: Distance
  if (Distance = 0)
    BestIndex0 := CurrentIndex0
    break
  endif
endfor

How to stay in procedure during debugging

To be able to inspect intermediary results it is useful to stay with the HDevelop debugging in a procedure. A breakpoint might not be sufficient as it might easily be skipped when pressing F5 too often. Continue by setting the program counter (PC) to the return statement manually by clicking with the mouse on the program line number.

while (true)
  stop ()
endwhile

Example


* complicated_procedure (: : : Result)
Debug := false
* Set Debug to true only when in HDevelop (not in HDevEngine nor in exported code)
SystemInformations := []
dev_get_system ('engine_environment', SystemInformations)
if (SystemInformations = 'HDevelop')
  Debug := true
endif
* ...
complicated_algorithm1 (IntermedResult1)
if (Debug)
  * dev_display (...)
  stop ()
endif

complicated_algorithm2 (IntermedResult2)
if (Debug)
  * dev_display (...)
  stop ()
endif

complicated_algorithm3 (IntermedResult3)
if (Debug)
  * dev_display (...)
  stop ()
endif

* Inspect IntermedResult1, or IntermedResult2, or IntermedResult3
if (Debug)
    while (true)
        stop ()
    endwhile
endif
Result := IntermedResult1 + IntermedResult2 + IntermedResult3 
return ()

How to generate left-hand zeros (e.g. 003)

Convert numbers to strings with left-hand zeros where needed using the $'.DIGITS' syntax (where DIGITS has to be replaced by the target number of digits)

Indices := [1,2,3,10,999]
IndicesStr := Indices$'.2'
* IndicesStr = ['01','02','03','10','999']

Example

Filenames := 'ampoules/ampoules_' + [1:22]$'.2' + '.png'
* Filenames = ['ampoules/ampoules_01.png', 'ampoules/ampoules_02.png', ...]
read_image (Image, Filenames)

Clear output parameters of procedures

Always clear the output parameters in HDevelop/HDevEngine procedures. Without clearing, the output parameters might behave differently in scripted HDevelop code and in exported (C++, C#, ...) code.

procedure TestWithClear (: : : MyTuple, MyVector)
  MyTuple := []
  MyVector.clear()
  * ... 

Reason HDevelop (tested with 12.0.2) clears output tuples and output vectors on first write access. If there is no write access, the original value of the output variable will not be modified. If there is a write operation, in HDevelop the tuple/vector is cleared before the first write operation, while in exported code the referenced output tuple/vector is used *without* clearing. So the behavior of exported and non-exported code is different. To avoid confusion (and hard to find bugs), always clear your output tuples/vectors in the first lines of your procedure.

Example

# Local procedures 
procedure TestWithoutClear (: : : MyTuple, MyVector)
  MyTuple[0] := 'zero'
  MyTuple[1] := 'one'
  * 
  MyVector.at(0) := 'zero'
  MyVector.at(1) := 'one'
  return ()


procedure TestWithClear (: : : MyTuple, MyVector)
  MyTuple := []
  MyVector.clear()
  * 
  MyTuple[0] := 'zero'
  MyTuple[1] := 'one'
  * 
  MyVector.at(0) := 'zero'
  MyVector.at(1) := 'one'
  MyVector.at(0) := 'zero'
  MyVector.at(1) := 'one'
  return ()


# Main procedure 

  MyTuple0 := [0,1,2]
  MyVector0 := {0,1,2}
  TestWithoutClear (MyTuple0, MyVector0)
  # std::cout << "after TestWithoutClear, MyTuple0=" << hv_MyTuple0.ToString().Text() << std::endl;
  # std::cout << "after TestWithoutClear, MyVector0=" << hvec_MyVector0.ToString().Text() << std::endl;
  * MyVector0 in HDevelop: {['zero'],['one']}
  * MyVector0 in C++: {['zero'],['one'], [2]} !!
  * 
  MyTuple1 := [0,1,2]
  MyVector1 := {0,1,2}
  TestWithClear (MyTuple1, MyVector1)
  # std::cout << "after TestWithClear, MyTuple1=" << hv_MyTuple1.ToString().Text() << std::endl;
  # std::cout << "after TestWithClear, MyVector1=" << hvec_MyVector1.ToString().Text() << std::endl;
  * MyVector1 in HDevelop: {['zero'],['one']}
  * MyVector1 in C++: {['zero'],['one']}