Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: rename classes

...

Expand
titleStop image from continuing in evaluation

Exit image evaluation from flow:

It is possible to stop evaluation early to save time based on a specific condition e.g. a Classifier distinguishes product types and chooses different evaluation branches to meet the specific product requirements.

Info

This is useful when using Parallelism to split the flow.

See the example of different evaluation flows based on product type:

flows.png

  1. To stop the evaluation copy and paste the following code.

  2. Modify Program Settings part according to the classes you wish to filter. (In the above example list_of_exit_classes = ["Type 2"], therefore “Type 1” can pass through.)

Code Block
languagepy
def main(context):
    # List containing names of the classes that will exit the flow sooner.
    ######################## PROGRAM SETTINGS ###################################
    list_of_exit_classes = ["Type 1"]
    ######################## END PROGRAM SETTINGS ###############################
    
    for rectangle in context['detectedRectangles']:
        if rectangle['classNames'][0]['label'] in list_of_exit_classes:
            context['exit'] = True
            return

It is necessary to modify the list list_of_exit_classes based on what classes are contained in your current project that are not supposed to continue evaluating.

Expand
titleCrop image by a detected rectangle

Crop image by a detected rectangle

Useful when the image contains unnecessary elements and the goal is to focus only on a certain part.

show_crop.png

To do this:

  1. Train a Detector module (or other) to find the region of interest.

  2. Copy and paste the following code into the Code module.

  3. Modify the Program Settings part according to the class you wish to cut (or None if the rectangle does not have a class).

Code Block
languagepy
def main(context):
    ###### PROGRAM SETTINGS #########
    LABEL = "My Rectangle"
    ###### END PROGRAM SETTINGS #####
    rectangle = find_rectangle(context)
    
    x_1, x_2 = int(rectangle['x']), int(rectangle['x'] + rectangle['width'])
    y_1, y_2 = int(rectangle['y']), int(rectangle['y'] + rectangle['height'])
    
    context['image'] = context['image'][y_1:y_2, x_1:x_2]
    
def find_rectangle(context):
    for rectangle in context['detectedRectangles']:
        if LABEL is None:
            return rectangle
        if LABEL == rectangle['classNames']['label']:
            return rectangle

It is necessary to modify the LABEL variable to contain the class name (or None) of the rectangle by which you want to crop the image.

Expand
titleSave evaluated images (with or without rectangles)

Save evaluated images to folders

Useful when your goal is to inspect the evaluated images at a later time again.

Info

This code draws the found rectangles to the image and saves only the images that were evaluated as

Status
colourRed
titlengNG
evaluated images.

To do this create
  1. Create a new folder

you wish to save your images to. Then copy
  1. in Windows File Explorer where the images will be saved

  2. Copy and paste the following code into the Code module

:
  1. Modify Program Settings by specifying the folder path and drawn rectangle color

Code Block
languagepy
import cv2
import skimage
import numpy as np
from datetime import datetime
from pathlib import Path

RED = (0, 0, 255)
GREEN = (0, 255, 0)
BLUE = (255, 0, 0)
YELLOW = (0, 255, 255)
WHITE = (255, 255, 255)

#################################### STARTPROGRAM CODE SETTINGS #######################################

# Specify the path to the save folder, image formats, and the wanted color of rectangles
SAVE_FOLDER = r"C:\Users\Vox\Downloads"
ORIGINAL_IMAGE_FORMAT = '.png'
ANNOTATED_IMAGE_FORMAT = '.jpg'
RECTANGLE_COLOR = RED

################################# END CODEPROGRAM SETTINGS ####################################


def main(context):
    # Get result
    result = context['result']
    
    # When image has result TRUE, program stop.
    if result is True:
        return
    
    # Get image from context
    image = context['image']
        
    # Save Original Image
    save_image_to_disc(image, filename_prefix = 'original_', image_format = ORIGINAL_IMAGE_FORMAT)
        
    # Draw rectangles to original image
    image = draw_rectangles_to_image(image, context, BARVA_RECTANGLU)
       
    # Save Anotated Image
    save_image_to_disc(image, filename_prefix = 'anotated_', image_format = ANNOTATED_IMAGE_FORMAT)
    
def save_image_to_disc(image, filename_prefix:str = '', image_format = '.png'):
    timestamp = generate_timestamp()
    full_save_path = Path(SAVE_FOLDER).joinpath(filename_prefix + timestamp + image_format)
    cv2.imwrite(str(full_save_path), image) 

def generate_timestamp():
    timestamp = datetime.now()
    formatted_timestamp = timestamp.strftime("%Y-%m-%d_%H-%M-%S_%f")
    return formatted_timestamp

def draw_rectangles_to_image(image, context, color:tuple = (0,0,255)):
    for rect in context['detectedRectangles']:
        rect_start = (int(rect['x']),  int(rect['y']))
        rect_end = (int(rect['x'] + rect['width']),  int(rect['y'] + rect['height']))

        image = cv2.rectangle(image, rect_start, rect_end, color, 2)
        
    return image

It is necessary to modify at least SAVE_FOLDER with your path to the saving folder. Modifying ?_IMAGE_FORMAT or RECTANGLE_COLOR is not necessary.

The folder path to copy and paste is written here:

save_folder_path.png

Expand
titleSend data to S7-1200 PLC DataBlock

Send data to S7-1200 PLC

To send your custom data or data from Pekat to a PLC:

  1. Make sure that PLC has its Protection settings set up correctly via TIAportal - Same as in this example: https://snap7.sourceforge.net/snap7_client.html#1200_1500

  2. Copy and paste the code below and set correct values to all variables in Program Settings and Output Settings.

This is for sending booleans. To send other type of data:

Set up buffer to have the correct size of bits to write and update both:

bool_value and snap7.util.set_bool(buffer, 0, 0, bool_value) accordingly.

Code Block
languagepy
import snap7

#################################### START PROGRAM SETTINGS #######################################

PLC_IP = "192.100.100.1"
DATA_BLOCK_NUMBER = 1   # For example for writing to bit on datablock DB1.2 these value will be set to 1
START_BIT_OFFSET = 2    # For example for writing to bit on datablock DB1.2 these value will be set to 2

################################# END PROGRAM SETTINGS ####################################

def main(context):

    result = context['result']
    
    if result is None:
        return
        
    #################################### OUTPUT SETTINGS #######################################
    data_to_send_to_plc = result        
    ################################# START OUTPUT SETTINGS ####################################
    
    send_bool_value_to_plc_datablock(ip=PLC_IP, db_number=DATA_BLOCK_NUMBER, start_bit_offset=START_BIT_OFFSET, bool_output=data_to_send_to_plc)

def send_bool_value_to_plc_datablock(ip:str, db_number:int, start_bit_offset:int, bool_output:bool):
    plc = snap7.client.Client()
    plc.connect(ip,  0,  0)
    

        # Initialize the bytearray with one byte
        buffer = bytearray(1)
        
        # Convert the boolean value to an integer (1 for True,  0 for False)
        bool_value = int(bool_output)
        
        # Set the boolean value to the bytearray
        snap7.util.set_bool(buffer,  0,  0, bool_value)
        
        # Write the bytearray to the PLC
        plc.db_write(DATA_BLOCK_NUMBER, start, buffer)
    
    plc.disconnect()
Expand
titleUse Heatmap as a mask

Use Heatmap as a mask

It is possible to use previously detected heatmap (from anomaly or surface) as a masking tool to hide specific objects or features.

before.pngafter.png
  1. The project contains a trained anomaly/surface module producing a heatmap

  2. Copy and paste the following code

Code Block
languagepy
def main(context):
    # Make a copy of the image
    context['original_image'] = context['image'].copy()
    
    # Mask out the heatmap region
    heatmap = context['heatmaps'][0][0][:, :, 0]
    context['image'][:, :, 0][heatmap == 255] = 0
    context['image'][:, :, 1][heatmap == 255] = 0
    context['image'][:, :, 2][heatmap == 255] = 0
    
    context['heatmaps'] = []
    context['detectedRectangles'] = []

To get back the original image (delete the mask), copy and paste the following code into a different code module:

Code Block
languagepy
def main(context):
    context['image'] = context['original_image']

See image:

Surface detector finds the heatmap of an object → Code module masks out the image → Further detections are done → Code module unmasks back to the original.

masking flow.png

Expand
titleMeasure the distance between rectangles

Measure the distance between rectangles

It is possible to calculate the distance between detected objects for further evaluation.

image-20240425-090229.png
  1. The project needs to contain a module detecting two distinct rectangles

  2. Copy and paste the following code

  3. Modify Program Settings according to the class names in your project

This code:

  • writes the calculated distance converted to mm to the top left part of the image

and
  • draws

the
  • a line between the centers of said rectangles

.
Code Block
languagepy
import cv2
import math

################ PROGRAM SETTINGS ####################
label_1 = "Class 1"
label_2 = "Class 2"
pixel_to_mm_conversion = 0.078  # Example conversion factor (adjust as needed)
############### END PROGRAM SETTINGS #################

def main(context):
    rectangle_1 = find_rectangle(context, label_1)
    rectangle_2 = find_rectangle(context, label_2)
    
    if rectangle_1 is not None and rectangle_2 is not None:
        x1, y1, w1, h1 = rectangle_1['x'], rectangle_1['y'], rectangle_1['width'], rectangle_1['height']
        x2, y2, w2, h2 = rectangle_2['x'], rectangle_2['y'], rectangle_2['width'], rectangle_2['height']
        
        center_x1 = x1 + w1 / 2
        center_y1 = y1 + h1 / 2
        center_x2 = x2 + w2 / 2
        center_y2 = y2 + h2 / 2
        
        distance_pixels = math.sqrt((center_x2 - center_x1)**2 + (center_y2 - center_y1)**2)
        print("Distance between {} and {} in pixels: {:.2f}".format(label_1, label_2, distance_pixels))
        
        # Convert distance from pixels to millimeters
        distance_mm = distance_pixels * pixel_to_mm_conversion
        print("Distance between {} and {} in millimeters: {:.2f}".format(label_1, label_2, distance_mm))
        
        # Draw line between centers of rectangles on the original image in context
        draw_line_on_image(context, (int(center_x1), int(center_y1)), (int(center_x2), int(center_y2)))
        
        # Display distance on the original image in context
        cv2.putText(context['image'], "Distance: {:.2f} mm".format(distance_mm), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
        
    else:
        print("One or both rectangles not found.")

def find_rectangle(context, label):
    for rectangle in context['detectedRectangles']:
        for class_name_item in rectangle['classNames']:
            if label == class_name_item['label']:
                return rectangle
    return None

def draw_line_on_image(context, start_point, end_point):
    image = context['image']
    # Convert image to BGR format (if not already in BGR)
    if len(image.shape) == 2:
        image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
        
    # Draw line on the image
    cv2.line(image, start_point, end_point, (0, 0, 255), 2)
    
    # Update the image in the context
    context['image'] = image

Expand
titleColor a grayscale depthmap image

Color a grayscale depth map image

The grayscale depth map can be colored based on a specified threshold and color.

Useful when using a 3D scanner (like Photoneo) that returns a depth map.

The extreme pixels (white and black) in the image are ignored, other pixels are linearly interpolated and assigned a color.

depthmap.pngImage Addedcolormap.pngImage Added
  1. Copy and paste the following code

  2. Modify the Program Settings color if necessary to change the default color

Code Block
languagepy
import cv2

RED = cv2.COLORMAP_HOT
BLUE = cv2.COLORMAP_OCEAN
GREEN = cv2.COLORMAP_DEEPGREEN
 
####################### PROGRAM SETTINGS ######################
color = RED
###################### END PROGRAM SETTINGS ###################
                       
def main(context):
    image = context["image"]
    
    colormap = colormaps_available[color]
    colored_image = grayscale_to_color(image, colormap)

    context["image"] = colored_image


def grayscale_to_color(image, colormap=cv2.COLORMAP_HOT):
    """
    Convert a grayscale image to a gradient color scale based on pixel values.

    Args:
        image (numpy.ndarray): Grayscale image.

    Returns:
        numpy.ndarray: Colorized image.
    """
    # Normalize the pixel values to the range [0, 1]
    normalized_image = image / 255.0

    # Apply a colormap to the normalized image
    colorized_image = cv2.applyColorMap((normalized_image * 255).astype(np.uint8), colormap)

    return colorized_image
Expand
titleRename existing model classes

Rename existing model classes

It is possible to change the names of classes that were used in training. Useful when you made a mistake during naming or only made numeric names like “Type 1” - see images

before rename.pngImage Addedafter rename.pngImage Added
  1. Copy and paste the following code into the Code module

  2. Modify Program Settings specifying a new name for each existing class

Classes not mentioned in the dictionary will not be modified.

Code Block
languagepy

################### PROGRAM SETTINGS #######################
# Modify and or add new class names to be used based on the following pattern
# LEFT - current name : RIGHT - new name
DICTIONARY = {
    "Type 1": "Screw",
    "Type 2": "Spring",
    "Type 3": "Nut",
}
################# END PROGRAM SETTINGS #####################

def main(context):
    for rectangle in context['detectedRectangles']:
        for idx, class_name in enumerate(rectangle['classNames']):
            current_name = class_name['label']
            new_name = DICTIONARY.get(current_name, current_name)
            rectangle['classNames'][idx]['label'] = new_name