I have a Python script set up in the Client Events section of the Vision module, where I would like to make use of the math.sin(), math.cos() and math.atan2() functions to process some vector data stored in a database into a client dataset memory tag and display it to a page. I have been encountering an error “NameError: global name ‘math’ is not defined” where the client is unable to run the script despite having “import math” in the first line. This leads me to believe that clients are unable to use the default Python modules.

Is it possible to install Python’s included Math module to the gateway such that all clients are able to use these functions? The Project Library seems like a lead but I am unsure how to go about this. I have seen a few other threads that mention the Apache Math functions, however these are only for the Jython/Java environment, correct?

I don’t know for sure, but I imagine the response you’re going to get is;

… consider a re-think of your design approach. Let the server (gateway) do the number crunching, pulling the information from the DB, processing, and then serving the information to the relevant client(s)…

Yeah, that code in the Script Console works fine for me too. I can run my script with no errors by pasting it into the Console in the Designer, but as soon as it’s in the Client Events area, I get the issue.

There’s a bit to it but I’ve attached a stripped-down version of the script with the relevant sections. You should be able to put an arbitrary array of angles in degrees into the function provided here.

import math
def averageAngles(angle_array):
"""
Takes array of angles in degrees and finds the average. Each angle is used to create a unit vector with x and y
components using cosine and sine definitions. Components are then summed and divided by the number of angles to
produce x' and y'. Average coordinates are then used to calculate the average angle using the atan2() method, a
version of arctan / inverse tangent which preserves angle sign across all four quadrants.
Inputs:
angle_array (array): One-dimensional array containing angles (in degrees) to be averaged
Returns:
angle_prime (float): Average angle calculated from array. Angles are given between 0 and 360 degrees, with a
value of -1 indicating a "None" angle in the situation where the net vector is null
"""
angle_count = 0
x_prime = 0
y_prime = 0
for angle in angle_array:
if angle != "None" or angle != -1:
# Convert to radians, find x and y components for an angle defined from North heading
theta = math.radians(angle)
x = math.cos(theta)
y = math.sin(theta)
# Add new x and y to x' and y' accumulators
x_prime += x
y_prime += y
# Increase angle count
angle_count += 1
# Check if angle_array is empty or angle is null condition
if angle_count != 0 and (x_prime != 0 and y_prime != 0):
# Divide accumulators by number of angles to find average
x_prime = x_prime / angle_count
y_prime = y_prime / angle_count
# Calculate inverse angle from average x and y positions
theta_prime = math.atan2(y_prime, x_prime)
angle_prime = math.degrees(theta_prime)
# Bound angle to positive conditions between 0 and 360
if angle_prime < 0:
angle_prime += 360
# Return calculated angle
return angle_prime
else:
# Null condition
return -1
#
# date = system.tag.read("[Client]date_tagpath").value
# Populate day[] with shift timestamps from date
#
day_angles = []
for shift in day: # Day is array of relevant timestamps
#
# Code to fetch dataset from database here. Create shift_data array of angles in degrees eg. [45, 90, 270, 340]
#
shift_angles = averageAngles(shift_data)
day_angles.append(shift_angles)
#
# Create new dataset from day_angles
# system.tag.write("[Client]dataset_tagpath", dataset)
#

What Jordan is hinting at is that event scripts have a legacy scope behavior for top-level names that prevents imports at the top level from being available within further-nested function scopes. (A backward compatibility mess that I wish IA had broken at v8.0…)

You should treat any def buried in an event script to be a “There Be Dragons” warning flare. Unless you have a very specific reason (closures, perhaps), you should put such functions in project scripts. And call them from the event script. Project scripts have modern top-level scope rules.