<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:media="http://search.yahoo.com/mrss/"
	
	>

<channel>
	<title>Owen's Portfolio</title>
	<link>https://owenackermanportfolio.cargo.site</link>
	<description>Owen's Portfolio</description>
	<pubDate>Tue, 16 Jun 2026 22:58:02 +0000</pubDate>
	<generator>https://owenackermanportfolio.cargo.site</generator>
	<language>en</language>
	
		
	<item>
		<title>About</title>
				
		<link>https://owenackermanportfolio.cargo.site/About-1</link>

		<pubDate>Thu, 16 Sep 2021 05:21:28 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/About-1</guid>

		<description>&#60;img width="1736" height="1570" width_o="1736" height_o="1570" data-src="https://freight.cargo.site/t/original/i/25296dfe9adc033791bae5201fd6baa001994918c08f07ac09ca6b0bdd0fad31/ProfilePhoto.jpeg" data-mid="249520421" border="0" data-scale="30" src="https://freight.cargo.site/w/1000/i/25296dfe9adc033791bae5201fd6baa001994918c08f07ac09ca6b0bdd0fad31/ProfilePhoto.jpeg" /&#62;




Hello, my name is Owen Ackerman. I’m a Creative Technologist based in NYC and Berlin.&#38;nbsp;
I graduated as a Computer Engineering student with special interests in Math, Signal Processing, and Audio. Throughout my life, the most meaningful and passion driven projects have been in the intersection of tech and art. Whether it be designing electronic instruments from the lens of a musician and engineer, or as a creative technologist designing synchronized ribbon dancing robots for a kinetic installation, I connect to the world through my love of art and technology.

 



I am a glass-half-full person and believe life is meant to be enjoyed. I try to keep things in perspective and remember life is what you put into it. I value human connection and try and ground myself in the now.
 


Feel free to contact me, all inquiries are welcome!
Personal Email: owenackerman1024@gmail.com

︎




</description>
		
	</item>
		
		
	<item>
		<title>FLOMOTION</title>
				
		<link>https://owenackermanportfolio.cargo.site/FLOMOTION-1</link>

		<pubDate>Sat, 30 May 2026 16:51:50 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/FLOMOTION-1</guid>

		<description>

.doc-wrap {
  font-family: inherit;
  max-width: 860px;
  margin: 0 auto;
  line-height: 1.6;
}

/* ── Tables ── */
.doc-wrap table {
  border-collapse: collapse !important;
  width: 100% !important;
  margin: 1.5em 0 !important;
  font-size: 0.88em !important;
  display: table !important;
  table-layout: auto !important;
  background: transparent !important;
}
.doc-wrap th,
.doc-wrap td {
  border: 1px solid #888 !important;
  padding: 7px 11px !important;
  text-align: left !important;
  vertical-align: top !important;
  background: transparent !important;
}
.doc-wrap th {
  font-weight: 600 !important;
  background: transparent !important;
}
.doc-wrap tr {
  background: transparent !important;
}

/* ── Code blocks and ASCII art ── */
.doc-wrap pre {
  font-family: 'Source Code Pro', 'Courier New', Courier, monospace !important;
  white-space: pre !important;
  overflow-x: auto !important;
  background: rgba(128, 128, 128, 0.12) !important;
  border: 1px solid #888 !important;
  padding: 1em 1.2em !important;
  border-radius: 4px !important;
  font-size: 0.82em !important;
  line-height: 1.25 !important;
  margin: 1.2em 0 !important;
  letter-spacing: 0 !important;
  word-spacing: 0 !important;
  font-variant-ligatures: none !important;
  -webkit-font-smoothing: auto !important;
}
.doc-wrap pre code {
  font-family: 'Source Code Pro', 'Courier New', Courier, monospace !important;
  background: transparent !important;
  padding: 0 !important;
  font-size: inherit !important;
  white-space: pre !important;
  line-height: inherit !important;
  letter-spacing: 0 !important;
  word-spacing: 0 !important;
}

/* ── ASCII diagram font size (adjust this value) ── */
#ascii-system-overview,
#ascii-system-overview code {
  font-size: 0.75em !important;
}

/* ── Inline code ── */
.doc-wrap p code,
.doc-wrap li code,
.doc-wrap td code,
.doc-wrap th code {
  background: transparent !important;
  padding: 2px 5px !important;
  border-radius: 3px !important;
  font-size: 0.85em !important;
  font-family: 'Source Code Pro', 'Courier New', Courier, monospace !important;
}

/* ── Syntax highlighted code (Pandoc spans) ── */
.doc-wrap code.sourceCode { white-space: pre !important; }
.doc-wrap .kw { color: #569cd6 !important; }
.doc-wrap .st { color: #ce9178 !important; }
.doc-wrap .co { color: #6a9955 !important; }
.doc-wrap .op { color: inherit !important; }
.doc-wrap .fl, .doc-wrap .dv { color: #b5cea8 !important; }
.doc-wrap .va { color: #9cdcfe !important; }
.doc-wrap .bu { color: #dcdcaa !important; }

/* ── Image grid — tall cells, fixed 2-col ── */
.doc-wrap .img-grid {
  border: none !important;
  width: 100% !important;
  table-layout: fixed !important;
}
.doc-wrap .img-grid th {
  border: none !important;
  text-align: center !important;
  font-weight: 600 !important;
  padding: 4px 6px !important;
}
.doc-wrap .img-grid td {
  border: none !important;
  padding: 6px !important;
  text-align: center !important;
  vertical-align: top !important;
  width: 50% !important;
}
.doc-wrap .img-grid .img-square {
  width: 100% !important;
  overflow: hidden !important;
  display: block !important;
  position: relative !important;
}
.doc-wrap .img-grid .img-square img,
.doc-wrap .img-grid .img-square figure,
.doc-wrap .img-grid .img-square &#62; * {
  width: 100% !important;
  height: auto !important;
  object-fit: cover !important;
  display: block !important;
  margin: 0 !important;
}

/* ── Blockquotes ── */
.doc-wrap blockquote {
  border-left: 3px solid #555 !important;
  margin: 1em 0 !important;
  padding: 0.5em 1em !important;
  color: #aaa !important;
}

/* ── Headings ── */
.doc-wrap h1 { font-size: 1.9em; margin-top: 0; }
.doc-wrap h2 { font-size: 1.4em; margin-top: 2em; border-bottom: 1px solid #333; padding-bottom: 0.3em; }
.doc-wrap h3 { font-size: 1.1em; margin-top: 1.6em; }
.doc-wrap h4 { font-size: 1em !important; margin-top: 1.4em !important; color: inherit !important; }

/* ── Scrollable wide tables ── */
.doc-wrap .table-scroll {
  display: block !important;
  overflow-x: auto !important;
  -webkit-overflow-scrolling: touch !important;
  width: 100% !important;
}

/* ── Misc ── */
.doc-wrap hr { border: none; border-top: 1px solid #333; margin: 2em 0; }
.doc-wrap img { max-width: 100%; height: auto; }
.doc-wrap ul, .doc-wrap ol { padding-left: 1.5em; }
.doc-wrap li { margin-bottom: 0.3em; }




Robotic Ribbons — FLOWMOTION
A kinetic installation of 6 robotic arms, each animating a ribbon
through coordinated motion and synchronized lighting. Commissioned by
Florian Goerlitz for the Light Art Festival in Heidenheim, Germany.
Designed, engineered, and built at MotionLab Berlin.

Table of Contents

Project Overview
Motion Design &#38;amp; Concept
Mechanical Design
Electronics Architecture
Software Architecture
Show Design: Motion &#38;amp; Lighting Timecode
Transportation &#38;amp; Setup
Lessons Learned &#38;amp; Next Steps
Credits



1. Project Overview
The piece was inspired by synchronized ribbon dancers — the way a
group of performers can move as a single organism, each ribbon tracing
its own path through space while contributing to a shared visual field.
FLOWMOTION translates that choreographic logic into a machine: six
robotic arms, each carrying a ribbon, moving in spherical coordinates
under a unified control system.



FLOMOTION in a linear configuration in a castle at the Light Art Festival in Heidenheim, Germany.



FLOMOTION in a circular configuration in a theatre at the Aux Actes Citoyens in Tomblaine, France.






2. Motion Design &#38;amp; Concept
2.1 Starting Point — What Motion Do We Want?
We started with understanding the types of circular and rhythmic
motions people have used to control ribbons. How does the human arm work
in coordination with a ribbon's natural movement?
The design process began with an analysis of how humans dance with
ribbons. We started to understand the relationship between how a human
arm navigates a ribbon which then naturally flows through the air. We
wanted the movement to be continuous, three-dimensional sweeps. The goal
was to translate that quality into motorized motion: organic, wave-like,
but also powerful enough to make sharp changes.

2.2 Identifying the Right Degrees of Freedom
Early explorations considered several mechanical configurations, but
a spherical two-motor design was chosen because it directly maps to the
mathematics of smooth orbital motion and flow patterns. It also allowed
for a robust configuration of powerful motors for controlled motion.
The following animations were made in Desmos 3D to test what kind of
motion was possible along a unit sphere using this spherical coordinate
motor system. All of these animations were used by programming different
functions onto the theta and phi angles.





Rotation
&#60;img width="1420" height="1288" width_o="1420" height_o="1288" data-src="https://freight.cargo.site/t/original/i/3f8da39a0f6444826793695dc5f8d604c257a9af9c7d52e3ae9b79b39fc342cf/SphericalAnimationRotationGIF.gif" data-mid="249351203" border="0"  src="https://freight.cargo.site/w/1000/i/3f8da39a0f6444826793695dc5f8d604c257a9af9c7d52e3ae9b79b39fc342cf/SphericalAnimationRotationGIF.gif" /&#62;


Figure 8
&#60;img width="1276" height="1116" width_o="1276" height_o="1116" data-src="https://freight.cargo.site/t/original/i/8c027e497d5357d2eaa0481a5209cfc9d96d4e3db3a19af9f342c7f52b4b018c/SphericalAnimationFigure8GIF.gif" data-mid="249351201" border="0" data-scale="23" src="https://freight.cargo.site/w/1000/i/8c027e497d5357d2eaa0481a5209cfc9d96d4e3db3a19af9f342c7f52b4b018c/SphericalAnimationFigure8GIF.gif" /&#62;




High Frequency
&#60;img width="1370" height="1258" width_o="1370" height_o="1258" data-src="https://freight.cargo.site/t/original/i/d02bf926ca102d32ca0f7d19c8cff9b18208f72bc49a58f6fc7f5830c70b4501/SphericalAnimationHighFreqGIF.gif" data-mid="249351202" border="0"  src="https://freight.cargo.site/w/1000/i/d02bf926ca102d32ca0f7d19c8cff9b18208f72bc49a58f6fc7f5830c70b4501/SphericalAnimationHighFreqGIF.gif" /&#62;


Complex Motion
&#60;img width="1370" height="1298" width_o="1370" height_o="1298" data-src="https://freight.cargo.site/t/original/i/6ba33bee230f0568771ef3cd7f9a0e8ec043240604e9155abafd933f2a9f75f4/SphericalAnimationCrazyGIF.gif" data-mid="249351200" border="0"  src="https://freight.cargo.site/w/1000/i/6ba33bee230f0568771ef3cd7f9a0e8ec043240604e9155abafd933f2a9f75f4/SphericalAnimationCrazyGIF.gif" /&#62;





2.3 Spherical Coordinate Model
Each robot arm's position is defined by two angles:

Theta (θ) — azimuthal rotation in the XZ plane,
around the Y axis. Range: 0–360°, continuous and wrapping. Controlled by
velocity (rotations per second), driving a stepper
motor at the corresponding pulse rate.
Phi (φ) — elevation from Y+ (straight up). Range:
−135° to +135°. Controlled by direct angle, sent as a
PWM command to the servo.

Y+ = phi 0°         (arm pointing straight up)
XZ plane = phi 90°  (arm horizontal)
Y- = phi ±135°      (servo hardware limit)

Theta: azimuth in XZ plane, 0–360°, wraps continuously

Cartesian mapping:
  y = cos(phi)
  x = sin(phi) * sin(theta)
  z = sin(phi) * cos(theta)
  theta = atan2(x, z)
The choice of velocity control for theta (rather than position) was
deliberate: it allows the stepper to spin continuously without needing
to track absolute position, and makes wave behaviors — where robots
rotate at slightly different speeds or with phase offsets — natural to
express.
&#60;img width="1322" height="1279" width_o="1322" height_o="1279" data-src="https://freight.cargo.site/t/original/i/43515a1f0e61f16e344ffccecbf33e55ae3dd4fecbf70ae3e44893221c9a458f/RobotArmCoordinateSystem.jpg" data-mid="249351195" border="0" data-scale="51" src="https://freight.cargo.site/w/1000/i/43515a1f0e61f16e344ffccecbf33e55ae3dd4fecbf70ae3e44893221c9a458f/RobotArmCoordinateSystem.jpg" /&#62;
Spherical coordinate system diagram


2.4 TouchDesigner Motion Controller Preview
Exploration of TouchDesigner UI for motion controller. Constant velocity on theta motor, sine wave placed on phi motor with variable parameters. In the following video I explore the different patterns that can emerge from altering the sine wave parameters for each of the robots.





3. Mechanical Design
3.1 Robot Arm Structure
Each robot consists of:

A stepper motor (theta axis) — continuous azimuthal
rotation, driven by a DM860 driver at 800 pulses/revolution
A servo motor (phi axis) — 270° range, 25.2V
supply, direct angle control
A rigid arm of length 1 m — 12mm wide hollow aluminum tubing
A ribbon attachment point at the tip with bearing for natural
rotation during flight
A proximity sensor to establish a known reference position (theta =
315°) on startup — sensor signal voltage-divided from 25.2V to ~4.2V via
a 1MΩ / 200kΩ divider for safe Arduino input

Note on ribbon behavior: at low speed near phi ≈ 0°
(arm pointing straight up), the ribbon can become caught around the arm.
This is a known mechanical constraint that shaped how behaviors approach
the vertical.


  &#60;img width="959" height="1286" width_o="959" height_o="1286" data-src="https://freight.cargo.site/t/original/i/ed6d37f0f9203c95ab0154a4ddc86d1ffe81fdccdb36d3be4df63a56796c847a/RobotDesignSketchCrop.jpg" data-mid="249471966" border="0" data-scale="98" src="https://freight.cargo.site/w/959/i/ed6d37f0f9203c95ab0154a4ddc86d1ffe81fdccdb36d3be4df63a56796c847a/RobotDesignSketchCrop.jpg" /&#62;Design Sketch
  &#60;img width="602" height="936" width_o="602" height_o="936" data-src="https://freight.cargo.site/t/original/i/27abd11cc89793eb5acb11d551054fb890d2b5bdb3024c3cd40b0b18694c17e3/CutRobotImage2.PNG" data-mid="249353164" border="0" data-scale="85" src="https://freight.cargo.site/w/602/i/27abd11cc89793eb5acb11d551054fb890d2b5bdb3024c3cd40b0b18694c17e3/CutRobotImage2.PNG" /&#62;Mechanism


  &#60;img width="3503" height="4668" width_o="3503" height_o="4668" data-src="https://freight.cargo.site/t/original/i/49a352d0887b9cce781f7d9d4fb6755e2e3d4f544e25fb5df0c75482323c8953/IMG_0200.jpeg" data-mid="249471077" border="0" data-scale="97" src="https://freight.cargo.site/w/1000/i/49a352d0887b9cce781f7d9d4fb6755e2e3d4f544e25fb5df0c75482323c8953/IMG_0200.jpeg" /&#62;Mechanism Innards
  &#60;img width="4284" height="5712" width_o="4284" height_o="5712" data-src="https://freight.cargo.site/t/original/i/51452f86048a37fe9b3428f7c8582fca12c6f4af79596a4d91355840b8e86cc9/RobotGroupPhoto.jpeg" data-mid="249351197" border="0" data-scale="97" src="https://freight.cargo.site/w/1000/i/51452f86048a37fe9b3428f7c8582fca12c6f4af79596a4d91355840b8e86cc9/RobotGroupPhoto.jpeg" /&#62;Full Length Robots


3.2 Physical Parts List (Per Robot)



Part
Specification
Notes




Stepper motor
NEMA 34, 8.5 Nm holding torque
Theta axis


Stepper driver
DM860
800 ppr DIP switch setting required


Servo motor
270° servo, 25.2V supply
Phi axis, −135° to +135°


Arm
1 m aluminum rod, 12 mm diameter, 2 mm wall thickness



Homing sensor
Metal proximity sensor
25.2V supply, signal divided to ~4.2V


Power module
60V stepper supply + 25.2V DC-DC buck
One per robot


Ribbon
75 mm width, 4 m length



Mounting hardware
M6 rods and nuts





3.4 Design Files
[TODO: Link CAD files, 3D print files, and laser cut files.]


4. Electronics System Architecture
4.1 System Overview
The system starts with TouchDesigner running on a MiniPC as the
central brain where the ribbon motion is designed and sequenced. TD
sends out live motor values via serial to an Arduino which connects to
each robot's power box. This power box holds a voltage transformer and a
motor driver which powers each robot. Each robot also sends back sensor
data to the Arduino to monitor its position.

&#60;img width="2316" height="1138" width_o="2316" height_o="1138" data-src="https://freight.cargo.site/t/original/i/8119c22f2a86d83f2f4ef3f5eeacbff5be593ed71ff281cc42cef4876640a7a3/RobotWiringDiagram.jpg" data-mid="249351199" border="0" data-scale="68" src="https://freight.cargo.site/w/1000/i/8119c22f2a86d83f2f4ef3f5eeacbff5be593ed71ff281cc42cef4876640a7a3/RobotWiringDiagram.jpg" /&#62;
Hardware and electronics overview diagram

The electronics form a two-tier hierarchy: a central
control computer runs all show logic and communicates
to a single Arduino, which fans out to six
robot nodes via individual Ethernet links. Lighting is
handled by a dedicated ESP32 DMX controller on the same
network.
&#60;img width="1198" height="476" width_o="1198" height_o="476" data-src="https://freight.cargo.site/t/original/i/213054e07d500f1f2447656d9a1f10ddff3874fd9e197ee9377977111eb9fd4f/image.png" data-mid="249472061" border="0" data-scale="69" src="https://freight.cargo.site/w/1000/i/213054e07d500f1f2447656d9a1f10ddff3874fd9e197ee9377977111eb9fd4f/image.png" /&#62;

4.2 Control Computer → Arduino (Serial)
TouchDesigner communicates with the Arduino over USB serial. Per-robot data sent each frame:



Data
Encoding
Notes




Theta velocity
(-theta_velocity × 800) + 100000
100000 = stopped; &#38;gt;100000 = forward; &#38;lt;100000 = reverse


Phi angle
Degrees, range −135 to +125
Direct servo angle


LED color
R, G, B values
Per-robot RGB fixture



The offset encoding for theta means the Arduino receives a single
unsigned integer that encodes both direction and speed — values above
100000 are forward rotation, values below are reverse, and the magnitude
of the offset is the pulse rate.
The Arduino sends 'HOMED' over serial when the homing sequence completes.

4.3 Arduino → Robot Nodes (Ethernet)
Each of the six robots has its own dedicated Ethernet
connection from the Arduino, allowing independent simultaneous
command delivery. The Ethernet carries two data lines to the stepper
motor, one to the servo, and one back from the homing sensor. Ethernet
was chosen for its twisted-pair wiring, which rejects noise and keeps
signal integrity over the cable runs between the central box and each
robot.

4.4 Motor Control
Stepper Motor — Theta Axis (DM860 Driver)
The DM860 accepts standard PUL / DIR / ENA signals with built-in optocouplers.

Critical: the Arduino ground must be fully isolated from the 60V system to protect the DM860 optocouplers.



SignalNotes


PULPulse input — rate sets velocity
DIRDirection
ENAEnable (active low)
Resolution800 pulses/revolution (DM860 DIP switch)
Supply voltage60V



Servo Motor — Phi Axis


ParameterValue


ControlStandard PWM
Supply voltage25.2V (DC-DC buck, non-isolated from 60V rail)
Range−135° to +135°



Homing Sensor


ParameterValue


Supply voltage25.2V
Signal conditioningVoltage divider: 1MΩ + 200kΩ → ~4.2V at Arduino input
Homing referencetheta = 315°



4.5 Power Architecture
Each robot has its own dedicated power module, isolating load and simplifying fault isolation.


RailVoltagePowersNotes


Main supply60VDM860 stepper driver
DC-DC buck25.2VServo motor, homing sensorNon-isolated from 60V rail
Logic supply5V isolatedArduino Mega 2560Must be fully isolated from 60V



4.6 Lighting — ESP32 DMX Controller
Each robot has one RGB DMX fixture (3 channels: R, G, B). A dedicated
ESP32 receives Art-Net UDP from
TouchDesigner and outputs DMX512 via a MAX485 RS-485 transceiver.


ComponentDetail


MicrocontrollerESP32
LibraryDmx_ESP32
Protocol inArt-Net (UDP)
Protocol outDMX512 via MAX485 transceiver
Channels used18 total (6 robots × 3 RGB channels)


The Dmx_ESP32 library was adopted after resolving
persistent DMX break-timing issues with the initial
implementation, which caused fixture flicker and dropout. Correct break
timing is handled automatically by the library.


5. Software Architecture
5.1 Stack Overview


LayerTool / Language


Show control &#38;amp; orchestrationTouchDesigner (Python extensions)
Motion mathPython (spherical coordinates, phase accumulators)
Serial communicationTouchDesigner Serial DAT
Microcontroller firmwareArduino C++ (Mega 2560)
Lighting outputArt-Net DAT → ESP32 → DMX512
Inter-machine communication for hand controlOSC over direct Ethernet



5.2 TouchDesigner Node Structure
/project1
    /controller_comp    Base COMP — RobotControllerEXT
    /sequencer_comp     Base COMP — ShowSequencerEXT
    /recorder_comp      Base COMP — MotionRecorderEXT
    /Robot1 .../Robot6  Base COMP — RobotEXT
        const_robot     Constant CHOP — motor output values for each robot
The Execute DAT calls all three extensions in order every frame:
op('sequencer_comp').ext.ShowSequencerEXT.Update()    # sets params first
op('controller_comp').ext.RobotControllerEXT.Update() # computes motion
op('recorder_comp').ext.MotionRecorderEXT.Update()    # captures state last

5.3 RobotEXT — Per-Robot State Container
RobotEXT is a pure data container — one instance per
robot arm. It tracks current position, velocity, and hardware limits,
and writes motor commands to a Constant CHOP each frame.

State


AttributeUnitDefault


thetadegrees0.0 (range 0–360°)
phidegrees90.0 (range −135° to +135°)
theta_velocityrps0.0
phi_velocitydeg/s0.0



Hardware Limits


LimitDefaultNotes


pulses_per_revolution800Must match DM860 DIP switch
max_theta_velocity1.5 rps
max_phi_velocity540.0 deg/sEquivalent to 1.5 rps
max_theta_acceleration2.3 rps/s0 = unlimited
max_phi_acceleration1.0 deg/s²0 = unlimited



CHOP Output
Each frame, PushToCHOP() writes three values to the robot's Constant CHOP:
const0value = theta (deg)
const1value = phi (deg)
const2value = (-theta_velocity × 800) + 100000
              100000 = stopped
              &#38;gt; 100000 = forward rotation
              &#38;lt; 100000 = reverse rotation

Key Methods
SetState(theta, phi, ledMatrix, dt=None)
    # If dt &#38;gt; 0: derives velocity from delta/dt, applies accel clamping.
    # If dt is None or 0: snaps to position, zeroes velocity.

Halt()
    # Zeros velocity and pushes to CHOP.
    # Always use this from outside — never assign r.theta_velocity directly
    # (TD extension promotion does not intercept attribute assignment)

5.4 RobotControllerEXT — Motion Orchestration
RobotControllerEXT owns all motion logic: behavior
evaluation, weighted blending, smooth transitions, stop/resume ramps,
and the homing sequence.

Update Loop (Every Frame)
1. Skip if dt &#38;lt; 0.001  (double-cook guard)
2. Read rotation axis from CHOP if quaternion behavior active
3. Advance homing state machine if running
4. If paused: return early
5. Scale effective_dt by stop/resume ramp factor
6. Accumulate internal show time
7. Exponential-lag smooth all behavior parameters (τ ≈ 1/6 s)
8. Evaluate all active behaviors; blend by normalized weight
9. Apply blended states to each RobotEXT via SetState + PushToCHOP

Behavior System
Multiple behaviors can run simultaneously. Each has an independent
weight and parameters that are
exponentially smoothed each frame before evaluation:
alpha = 1 - exp(-6.0 * dt)
smoothed = smoothed + (target - smoothed) * alpha
Blending is normalized weighted: if sine has weight 0.7 and wave has weight 0.3,
the output is a 70/30 blend of what each behavior would produce. This enables smooth crossfades with no discontinuities.

Built-in Behaviors


BehaviorDescription


sineTheta at constant velocity; phi oscillates sinusoidally with per-robot phase offsets
waveBoth theta and phi oscillate sinusoidally from absolute show time
noiseSame as wave at higher frequency, smaller amplitude — good for subtle organic perturbation
circularAll robots trace circles in a tilted plane using per-robot angle accumulators
quaternionRotates arm vectors around an arbitrary 3D axis (Rodrigues' formula); supports live CHOP input for the rotation axis
circle_zyTraces a circle on the unit sphere in a plane offset from the ZY axis
figure8Lissajous figure-8: theta at frequency f, phi at 2f
figure9Inverse Lissajous: theta at 2f, phi at f
flipBinary phi toggle — all robots snap to ±135°
stepper_speed_controlTheta spins continuously; phi set directly with cascading delay ripple between robots
stepper_direct_controlDirect position control for both axes with independent cascading delays


Behaviors involving continuous accumulation (sine, circular, figure8, etc.)
maintain per-robot phase accumulators so parameter changes — e.g. changing frequency mid-show —
are seamless rather than causing a position jump.

Fade System
ctrl.fadeTo('wave', duration=2.0)
Transitions use cubic ease-in-out interpolation. For sine and circular,
the endpoint is predicted duration seconds ahead, so the incoming behavior lands without a visible seam.
On completion, all other weights are zeroed and the target is set to 1.0.

Stop / Resume
ctrl.stop()        # Smooth decel over decel_duration (default 0.5s)
ctrl.resume()      # Smooth accel back up
ctrl.hardStop()    # Instant — zeroes all velocities immediately
Stop and resume work by scaling effective_dt over the ramp window,
so velocity reaches zero naturally through the same acceleration clamping used during normal motion.

5.5 Homing Sequence
On startup (or whenever the installation needs to re-reference),
homing runs through a four-state machine:
None → 'decelling' → 'waiting' → 'resuming' → None

All robots decelerate to a stop
Controller pulses a configurable trigger op to activate the Arduino homing routine
Each robot drives toward its homing sensor; on contact, theta is zeroed and the reference is set to 315°
Arduino sends 'HOMED' over serial when all robots are referenced
Controller resumes motion, restoring each robot's saved phi value

ctrl.startHoming(
    trigger_op  = 'button_home',
    trigger_par = 'Pulse',
    serial_dat  = 'serial1',
    done_string = 'HOMED'
)

5.6 MotionRecorderEXT — Live Motion Capture
The recorder captures live robot states into named clips at a configurable sample rate (default 30fps),
then registers each clip as a dynamic behavior on the controller — meaning recorded motion
can be weighted, blended, and faded identically to any built-in behavior.
rec.startRecording('take_1')          # begin capture
rec.stopRecording()                   # finalize and register on controller

ctrl.fadeTo('take_1', 2.0)            # crossfade into recorded motion
rec.setClipSpeed('take_1', 0.5)       # half speed playback
rec.setClipLoop('take_1', False)      # one-shot (holds last frame)
rec.saveClips('show/clips.json')      # persist to disk
rec.loadClips('show/clips.json')      # restore and re-register on load
This was particularly useful during choreography development —
interesting moments discovered through live manipulation could be captured immediately and played back precisely.

5.7 ShowSequencerEXT — Timecoded Show Playback
The show is authored as a Table DAT — one row per segment — which the sequencer reads
to drive the controller over time. The sequencer must update before the controller each frame
so parameters land in the same frame they're intended for.

Table Format




time_start
duration
behavior
blend_start
blend_end
params_start
params_end




0.010.0sine
theta_velocity=0.1 bias_phi=-90
theta_velocity=0.3 bias_phi=-45


10.04.0sine/wave0.01.0
theta_velocity=0.2


14.08.0figure8
frequency=0.25 amplitude_theta=60
frequency=0.5 amplitude_theta=30





behavior — single name (sine) or crossfade pair (sine/wave);
in crossfade mode, blend_start/blend_end control the weight of the second behavior across the segment duration
params_start / params_end — space-separated key=value pairs;
the sequencer interpolates linearly between them over duration seconds
The sequencer fully owns weight state during playback — it zeros all behavior weights every frame
Segments are sorted by time_start automatically on load


Playback Controls
seq.loadFromDAT('sequencer_table')
seq.play()           # start / resume
seq.pause()          # freeze; controller holds current state
seq.seek(30.0)       # jump to 30s; immediately applies that segment's state
seq.setLoop(True)
seq.seq_time         # current playhead position in seconds (read-only)

5.8 Key TouchDesigner Implementation Notes
These patterns were non-obvious and important to get right:

Frame-persistent state — scriptOp.store() / fetch() used for values
that must survive TD cook cycles; instance variables alone are not reliable
Absolute time — absTime.seconds used for show timing;
me.time.seconds resets on component resets and is unsuitable for a running installation
Extension promotion — methods on RobotEXT are promoted to the operator level
(r.Halt() works), but attribute assignment (r.theta_velocity = x)
does not go through the extension — always use setter methods
Double-cook guard — TD fires Execute DATs multiple times per frame with dt≈0;
the guard if dt &#38;lt; 0.001: return prevents the second cook from zeroing velocities


5.9 Source Files


FileRole


DATS/RobotEXT.pyPer-robot state container and CHOP output
DATS/RobotControllerEXT.pyAll motion logic: behaviors, blending, fading, stop/resume, homing
DATS/MotionRecorderEXT.pyRecords robot motion into named clips; registers clips as behaviors
DATS/ShowSequencerEXT.pyTimecoded show: Table DAT-driven parameter sequences




6. Show Design: Motion &#38;amp; Lighting Timecode
6.1 Approach
Both motion and lighting are driven from a single timecoded Table DAT sequence in TouchDesigner,
read by ShowSequencerEXT. The sequencer owns a single internal clock and drives
RobotControllerEXT with interpolated behavior parameters each frame — including both motor targets
and LED colors. Because everything shares one clock and one update loop, motion and lighting are structurally
guaranteed to stay in sync across playback, seek, pause, and loop operations.

6.2 Motion Authoring
Motion is authored by writing segments into the sequencer_table Table DAT.
Each segment specifies which behavior is active, its parameters at the start and end of the segment
(with linear interpolation between), and optionally a crossfade to a second behavior.




time_start
duration
behavior
params_start
params_end




0.08.0sine
theta_velocity=0.1 bias_phi=-90
theta_velocity=0.2 bias_phi=-60


8.04.0sine/wave
theta_velocity=0.2



12.010.0wave
frequency_phi=0.5 amplitude_phi=30
frequency_phi=1.5 amplitude_phi=60


22.06.0figure8
frequency=0.25 amplitude_theta=60
frequency=0.5 amplitude_theta=30




Certain moments in the show were developed through live performance — moving the robots in real time
through parameter manipulation — and captured using MotionRecorderEXT. These clips were then
incorporated into the sequence as named behaviors alongside the procedural ones.

6.3 Lighting
Each robot has one RGB DMX fixture, addressed as 3 consecutive DMX channels. Lighting is controlled by
the same Table DAT / ShowSequencer system as motion — there is no separate lighting timeline.
Every behavior function returns a led: [r, g, b] value per robot alongside
theta and phi. These values flow through the same normalized weighted blend
as the motion state, so lighting crossfades automatically whenever behaviors crossfade.
Lighting cues are authored directly in the sequencer_table using the same
params_start / params_end syntax:




time_start
duration
behavior
params_start
params_end




0.08.0sine
led_r=0 led_g=50 led_b=255
led_r=0 led_g=100 led_b=255


8.04.0wave
led_r=255 led_g=80 led_b=0
led_r=255 led_g=255 led_b=0





6.4 Show Sections
The show was built through a sequence of timecoded commands. In this short example, we first start
the show by cutting the lights and homing all of the motors. Then the music starts and robot one shoots
to life along with its lights, then after 5 seconds, robot 2 joins. We did this for about 300 different
timecodes along a 7 minute song to create a choreographed light-controlled show.


TimeSectionMotion BehaviorLightingNotes


0:00Startup / HomingAll robots home at theta=315°DarkSilent calibration
0:05Song beginsOnly Robot 1 movesOnly Robot 1 lights up
0:10Robot 2 joinsRobot 2 light joins
…~300 timecoded cues over 7 minutes




7. Transportation &#38;amp; Setup

The installation was transported in custom shipping containers.&#38;nbsp;&#60;img width="4284" height="5712" width_o="4284" height_o="5712" data-src="https://freight.cargo.site/t/original/i/e404c859bf87cee0e544082c9e3200b1eb640c63d38423e3168a5fe11a42d7f6/RobotsInPackaging.jpeg" data-mid="249351198" border="0" data-scale="36" src="https://freight.cargo.site/w/1000/i/e404c859bf87cee0e544082c9e3200b1eb640c63d38423e3168a5fe11a42d7f6/RobotsInPackaging.jpeg" /&#62;&#60;img width="4284" height="5712" width_o="4284" height_o="5712" data-src="https://freight.cargo.site/t/original/i/6d7ca17e5bb9ac62fd1e58ad464859dbba61a4cead9ddc93e26bec25831d40be/IMG_1426.jpeg" data-mid="249474186" border="0" data-scale="36" src="https://freight.cargo.site/w/1000/i/6d7ca17e5bb9ac62fd1e58ad464859dbba61a4cead9ddc93e26bec25831d40be/IMG_1426.jpeg" /&#62;&#60;img width="5712" height="4284" width_o="5712" height_o="4284" data-src="https://freight.cargo.site/t/original/i/b1f2aab5ed8144bf5e02e2427185edda4061c7f50f365cc335e21333a58e1b28/IMG_1430.jpeg" data-mid="249474185" border="0" data-scale="58" src="https://freight.cargo.site/w/1000/i/b1f2aab5ed8144bf5e02e2427185edda4061c7f50f365cc335e21333a58e1b28/IMG_1430.jpeg" /&#62;


8. Lessons Learned &#38;amp; Next Steps
This project was a full end-to-end build under real installation pressure — hardware, electronics, firmware,
and software all developed in parallel against a hard ship date. Below are the post-mortems from each layer.

8.1 Hardware
Vulnerability: stepper-to-servo coupling screw
The most consequential mechanical issue was the M_ screw connecting the 12mm stepper motor axle into
the servo motor flange mount. Under repeated rotation, this screw would occasionally snap. When it did,
the servo motor wires became the only thing supporting the rotational load — causing them to twist, short,
and in several cases blow out digital pins on the Arduino.
The root cause is mechanical: without a tight interference fit between the axle and the flange bore,
all rotational strain concentrates on the fastener. The correct fix is proper milling of both the
flange and the axle to achieve a tight press or keyed fit that transfers torque through the metal
geometry rather than the screw. This was identified during the build and during the installation we drilled through and expanded the holes, filling it with a larger bolt. This came after failures that resulted in several hours of repair time.
Next time: prioritize machined fits for any joint that sees continuous cyclic load. A screw should
be a safety fastener, not the primary torque path.
Wiring to the servo motor
Because the servo wires travel a very short, constrained distance, they were extremely difficult to repair
after the coupling screw broke — the failure cascade (screw snaps → wires take load → wires rip or short)
often happened faster than it could be caught, and repair required significant disassembly.
Next time: use breakaway connectors — connectors designed to release under tension
— on the servo wiring. If the coupling fails, the connector separates cleanly before the wires are damaged
or a short reaches the electronics. In addition, if the first hardware fastener issue was fixed, this issue wouldn't be a problem.
Takeaway: beware of cascading failures.

8.2 Electronics
No per-robot isolation
The current architecture has all six robots sharing data lines back to a single Arduino with no electrical
isolation between them. When a robot node fails — especially where a mechanical failure causes a short or
voltage spike — the fault can propagate up the data line and damage the Arduino, potentially taking down
the entire installation.
Next time: add optocouplers on every data line between the Arduino and the robot nodes.
An optocoupler breaks the electrical path entirely — a short or voltage spike on the robot side can't reach
the Arduino. The failure mode becomes "that robot stops working" rather than "the whole system goes down."
Takeaway: beware of cascading failures.

8.3 Software
Developing inside a larger TD network without full context
The Python extension classes (RobotEXT, RobotControllerEXT,
ShowSequencerEXT, MotionRecorderEXT) were developed using Claude Code,
which worked well for the self-contained logic. The difficulty was that a significant amount of the
TouchDesigner network — the wiring, the CHOPs that feed into the extensions, the DATs that read out of them
— existed outside the codebase and therefore outside Claude's context. Claude Code couldn't see or
reason about the full system, which meant bugs at the boundary between the Python extensions and the TD
network were harder to catch and took longer to debug.
Next time: have Claude Code implement unit tests for every extension class that
simulate the TD environment — mocking absTime, scriptOp.store/fetch, CHOP reads
and writes — and verify that each public method produces the exact expected output. This would have caught
boundary bugs much earlier, shortened the debugging cycle considerably, and produced a more robust system
overall. Closing the loop on AI-assisted development requires giving the AI a way to verify its own work
against the real integration surface.


9. Credits


RoleName


Concept, artistic direction, design, fabricationFlorian Goerlitz
Artistic direction, design, engineering, fabricationOwen Ackerman
Development studioMotionLab Berlin


Exhibitions:
Light Art Festival, Heidenheim, Germany — 4/24–5/2/2026
Aux Actes Citoyens Theatre Festival, Tomblaine, France — 5/9–16/2026

Documentation last updated: 6/15/2026

</description>
		
	</item>
		
		
	<item>
		<title>Interactive Ribbons</title>
				
		<link>https://owenackermanportfolio.cargo.site/Interactive-Ribbons-1</link>

		<pubDate>Tue, 16 Jun 2026 18:01:07 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/Interactive-Ribbons-1</guid>

		<description>Interactive Ribbons

In this project, I made a large spiraling ribbon driven by a large joystick. As you push and pull the joystick it drives a motor clockwise or counter-clockwise depending on the joystick position. This effect gives you the illusion that you’re controlling this spiraling ribbon with your hand. Controlling the intensity of wind pushing into your face.&#38;nbsp;
In this video I give a simple explanation of the project. An accelerometer is inside the joystick which is read by an esp32 which sends information to a motor driver. The x axis of the accelerometer determines the position and speed of the motor which drives the ribbon.&#38;nbsp;



 




	
    

    
	
     


</description>
		
	</item>
		
		
	<item>
		<title>Circular Ribbons</title>
				
		<link>https://owenackermanportfolio.cargo.site/Circular-Ribbons-1</link>

		<pubDate>Tue, 16 Jun 2026 22:58:02 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/Circular-Ribbons-1</guid>

		<description>Circular Ribbons
This installation consists of 6 ribbons aranged in a circle driven by synchronized stepper motors. In addition they were projection mapped onto for the Light Art Festival in Germany.


	
    
    
	
    
    


	
    
    
	
    
    




</description>
		
	</item>
		
		
	<item>
		<title>Project Soundscape</title>
				
		<link>https://owenackermanportfolio.cargo.site/Project-Soundscape-1</link>

		<pubDate>Wed, 07 May 2025 17:47:52 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/Project-Soundscape-1</guid>

		<description>Project Soundscape was a year long research project to create a novel spatial audio system aimed at optimizing virtual source localization for a single user. After I convinced Bose to sponsor the project, I found 4 other engineers to help out with the software integration of the project while I worked on the Acoustic/DSP algorithm.
Below You can find the overview project explanation in the poster paper below as well as the final written report for this project. Additionally I created a binaural video explanation of the project recorded using a Neumann KU 100 dummy head microphone. &#38;nbsp;





&#60;img width="1936" height="1284" width_o="1936" height_o="1284" data-src="https://freight.cargo.site/t/original/i/cb70b9de32f5023df7e578ffd6aae3a70f9709f2658551495f58dfbec0479c50/Screenshot-2025-05-07-134205.png" data-mid="232908540" border="0"  src="https://freight.cargo.site/w/1000/i/cb70b9de32f5023df7e578ffd6aae3a70f9709f2658551495f58dfbec0479c50/Screenshot-2025-05-07-134205.png" /&#62;
Final Report Link
</description>
		
	</item>
		
		
	<item>
		<title>Machine Learning Transfer Function Prediction</title>
				
		<link>https://owenackermanportfolio.cargo.site/Machine-Learning-Transfer-Function-Prediction-1</link>

		<pubDate>Wed, 07 May 2025 03:18:08 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/Machine-Learning-Transfer-Function-Prediction-1</guid>

		<description>&#60;img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdMSWEXtwQXWXiFaAvEdkLjz8BFihFCU2f8mUbCyP8cofRQyqtne7kHu-lIZpxdjbxn4gno3kXGoAbIVIKP6-p691NjqDOF_xBSub5lKTr0j21EEbbj0Qy66_p9D8VS8lpEqhYLvg?key=k_qz4XlqE9162ZgcYais4w" width="624" height="851" style="width: 624px; height: 851px;"&#62;


</description>
		
	</item>
		
		
	<item>
		<title>Sound Field Synthesis Exploration</title>
				
		<link>https://owenackermanportfolio.cargo.site/Sound-Field-Synthesis-Exploration</link>

		<pubDate>Tue, 19 Nov 2024 01:37:48 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/Sound-Field-Synthesis-Exploration</guid>

		<description>A mathematical exploration into Sound Field Synthesis.
I created a set of notes from my personal exploration of SFS and created a python script that generates WFS animations.
Here are some of the animations, the python script can be downloaded here. My math derivations and notes can be found below the animations.



	&#60;img width="600" height="570" width_o="600" height_o="570" data-src="https://freight.cargo.site/t/original/i/2fecaf0695642bd6aded5503c260a53414a322eefc20a7f590b31b5578a5d2c2/WFSdoubleSource2-ezgif.com-video-to-gif-converter.gif" data-mid="222012595" border="0" data-scale="100" src="https://freight.cargo.site/w/600/i/2fecaf0695642bd6aded5503c260a53414a322eefc20a7f590b31b5578a5d2c2/WFSdoubleSource2-ezgif.com-video-to-gif-converter.gif" /&#62;
&#60;img width="600" height="573" width_o="600" height_o="573" data-src="https://freight.cargo.site/t/original/i/c6f2e5600085e0acb5d1a049056cb2db149f119d8c2c004fe2bbdb138ac0dcd4/Figure12024-11-1820-16-44-ezgif.com-video-to-gif-converter.gif" data-mid="222012598" border="0" data-scale="100" src="https://freight.cargo.site/w/600/i/c6f2e5600085e0acb5d1a049056cb2db149f119d8c2c004fe2bbdb138ac0dcd4/Figure12024-11-1820-16-44-ezgif.com-video-to-gif-converter.gif" /&#62;
	
	&#60;img width="600" height="578" width_o="600" height_o="578" data-src="https://freight.cargo.site/t/original/i/bb90cb72e720f39eec80ea6cd1c455d98e3d8c91ef4328eaf88ab9d0bcdcb2eb/WFSdoulbeSource1-ezgif.com-video-to-gif-converter.gif" data-mid="222012596" border="0" data-scale="98" src="https://freight.cargo.site/w/600/i/bb90cb72e720f39eec80ea6cd1c455d98e3d8c91ef4328eaf88ab9d0bcdcb2eb/WFSdoulbeSource1-ezgif.com-video-to-gif-converter.gif" /&#62;&#60;img width="600" height="577" width_o="600" height_o="577" data-src="https://freight.cargo.site/t/original/i/c40c108d6326a952f20857ab35968c980faed12b2b0c0704e8f621d7e5626a52/ezgif.com-video-to-gif-converter.gif" data-mid="222012683" border="0" data-scale="100" src="https://freight.cargo.site/w/600/i/c40c108d6326a952f20857ab35968c980faed12b2b0c0704e8f621d7e5626a52/ezgif.com-video-to-gif-converter.gif" /&#62;
    

Notes:

&#60;img width="1668" height="2154" width_o="1668" height_o="2154" data-src="https://freight.cargo.site/t/original/i/c9506eab7df7436eb6610d29c4be8ac9d0a17ab0765f545e1ad6baca01b54ec1/Wave-Field-Synthesis-3.jpg" data-mid="222012584" border="0"  src="https://freight.cargo.site/w/1000/i/c9506eab7df7436eb6610d29c4be8ac9d0a17ab0765f545e1ad6baca01b54ec1/Wave-Field-Synthesis-3.jpg" /&#62;&#60;img width="1668" height="2154" width_o="1668" height_o="2154" data-src="https://freight.cargo.site/t/original/i/4684e9e5fe5fb47d3f759154e92918c1dd8b1cb742ad98f5b75024662df6f07c/Wave-Field-Synthesis-4.jpg" data-mid="222012583" border="0"  src="https://freight.cargo.site/w/1000/i/4684e9e5fe5fb47d3f759154e92918c1dd8b1cb742ad98f5b75024662df6f07c/Wave-Field-Synthesis-4.jpg" /&#62;
&#60;img width="1668" height="2154" width_o="1668" height_o="2154" data-src="https://freight.cargo.site/t/original/i/915af9e16add95ad4405519ac4fe47f9a8e9bf13a66191210e109a6e01accef4/Wave-Field-Synthesis-5.jpg" data-mid="222012585" border="0"  src="https://freight.cargo.site/w/1000/i/915af9e16add95ad4405519ac4fe47f9a8e9bf13a66191210e109a6e01accef4/Wave-Field-Synthesis-5.jpg" /&#62;&#60;img width="1668" height="2154" width_o="1668" height_o="2154" data-src="https://freight.cargo.site/t/original/i/1e56a398a6fb9c67dc9873b61dc5137205ea74ab9406b5216d01af85a19874de/Wave-Field-Synthesis-6.jpg" data-mid="222012582" border="0"  src="https://freight.cargo.site/w/1000/i/1e56a398a6fb9c67dc9873b61dc5137205ea74ab9406b5216d01af85a19874de/Wave-Field-Synthesis-6.jpg" /&#62;</description>
		
	</item>
		
		
	<item>
		<title>Chimillian</title>
				
		<link>https://owenackermanportfolio.cargo.site/Chimillian-1</link>

		<pubDate>Wed, 14 Aug 2024 15:26:49 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/Chimillian-1</guid>

		<description>&#60;img width="1460" height="1845" width_o="1460" height_o="1845" data-src="https://freight.cargo.site/t/original/i/4afc54d423aee76f692c25e0b5dd9b00994f5ce73ebd8b08656e04fc1ea66eea/ChimillianDocumentation.png" data-mid="216355517" border="0" data-scale="36" src="https://freight.cargo.site/w/1000/i/4afc54d423aee76f692c25e0b5dd9b00994f5ce73ebd8b08656e04fc1ea66eea/ChimillianDocumentation.png" /&#62;
</description>
		
	</item>
		
		
	<item>
		<title>Embedded Medical Devices</title>
				
		<link>https://owenackermanportfolio.cargo.site/Embedded-Medical-Devices-1</link>

		<pubDate>Sun, 24 Sep 2023 23:12:19 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/Embedded-Medical-Devices-1</guid>

		<description>
This past summer I worked in tandem with professor Joel Grodstein to design a new class at Tufts: Embedded Medical Devices. The main topics consist of programming in an embedded real time operating system ​(RTOS), and signal processing methods specifically for medical signals. A driving motivation of the course was to educate students on how an RTOS allows for reliable, precisely timed multi-tasking embedded devices. An RTOS provides the framework to schedule many tasks running at varying frequencies. This is helpful as many medical applications require lots of things to happen “concurrently” while typically only having a single core to work with. 

Over the summer, utilizing FreeRTOS I designed a heartbeat monitor. I implemented several types of real time filters such as differentiators, integrators, signal peak detectors, cascading biquads for highpass, lowpass and bandpass filters, and decision tree algorithms to classify a QRS wave. Simultaneously a buzzer is triggered on the heartbeat and a display is periodically updated to display the measured heart rate in BPM. 

In the course we used the many pitfalls I came up against in the summer project to create an RTOS “debug derby”, a style of lab where the students must debug a broken piece of code to better understand how RTOS’s work. We also teach basic real time signal processing, and PC based signal processing analysis. First we teach students how to design basic filters using python on their laptops so that they can play with different parameters and view the effects in the frequency domain in a nice GUI. After students have designed their filters, we teach them how to filter given real time constraints and explore the pitfalls that come outside the idealized world of python.&#38;nbsp; We also teach more advanced signal processing ideas like wavelets which produce time localized frequency information, great for analyzing dynamic biological signals. Wavelets are one of my favorite signal processing topics and I was allowed to give a lecture on them! 

You can find the course webpage here. 

You can read more about the heartbeat detector designed using FREE RTOS and our signal processing labs here.
Here is a live demo of the heartbeat monitor correctly classifying my heartbeat. My heart rate is shown on the display of the device below (Green circuit board).
&#60;img width="4032" height="3024" width_o="4032" height_o="3024" data-src="https://freight.cargo.site/t/original/i/4c3e7c6a6350f8399755f1e7be02f4ef67b49e9f24a4149c53f2d2882bbd3ec0/IMG_5858.jpg" data-mid="191945687" border="0"  src="https://freight.cargo.site/w/1000/i/4c3e7c6a6350f8399755f1e7be02f4ef67b49e9f24a4149c53f2d2882bbd3ec0/IMG_5858.jpg" /&#62;
Here is an image of a scaleogram: a time, frequency, amplidude plot used to analyze dynamically changing signals. It’s the output of the continuous wavelet transform!

&#60;img width="1985" height="989" width_o="1985" height_o="989" data-src="https://freight.cargo.site/t/original/i/2f8c06720eecb62aadf628e45bc806e36364c3276b91e877eba0785d72de7db3/CWTpicture.png" data-mid="191945777" border="0"  src="https://freight.cargo.site/w/1000/i/2f8c06720eecb62aadf628e45bc806e36364c3276b91e877eba0785d72de7db3/CWTpicture.png" /&#62;


</description>
		
	</item>
		
		
	<item>
		<title>Computer Vision</title>
				
		<link>https://owenackermanportfolio.cargo.site/Computer-Vision-1</link>

		<pubDate>Wed, 13 Sep 2023 22:35:09 +0000</pubDate>

		<dc:creator>Owen's Portfolio</dc:creator>

		<guid isPermaLink="true">https://owenackermanportfolio.cargo.site/Computer-Vision-1</guid>

		<description>
Here is a quick outline of some of the ideas that have influenced projects I’ve completed in the field of computer vision and machine learning. A full explanation and documentation of each project can be found on github here:&#38;nbsp;GITHUB

Feature matching, image warping and panoramas

Feature matching is how we relate two images to each other. Through identifying several key features, like a corner or a dot, we can overlay two separate images of overlapping scenes by relating their key features. We can match features using SIFT, the scale invariant feature transform algorithm. First we can define a key feature as one with a large “contrast”, this means a large difference in the values of pixels, such as a border. We can filter out “bad” features as those which don’t have enough contrast, (using EigenValues to determine if there is enough contrast in enough directions, read harris corner detection). We then find the orientation of the feature so that we can compare it to other features. Once we have the key points of two images, we can filter for key points that are only shared between the 2 images.
 &#38;nbsp;
&#60;img width="1162" height="427" width_o="1162" height_o="427" data-src="https://freight.cargo.site/t/original/i/79824eb30a6434b74b0fa645aae244cea8d4a5fe22fd8e6ee282337c5a92d973/FeatureMatching.png" data-mid="191519964" border="0"  src="https://freight.cargo.site/w/1000/i/79824eb30a6434b74b0fa645aae244cea8d4a5fe22fd8e6ee282337c5a92d973/FeatureMatching.png" /&#62;
Now comes image warping. Usually when we take 2 pictures of the same scene, they’re at different angles, this is especially true for panoramic images. So in order to map 2 images onto each other, we define a homography, a 3d warping transformation of the image. Now, we solve for the optimal warping of one image to line up with the second. We do this with a least squares algorithm that optimizes for the number of overlapping key points between the two images. The result looks as follows. 

&#38;nbsp;
&#60;img width="407" height="590" width_o="407" height_o="590" data-src="https://freight.cargo.site/t/original/i/40526bfd9f224ef9065b96e442745176bc79b552b3734ee69bbbbafb5b33dc6a/WarpingMatch.png" data-mid="191519965" border="0" data-scale="100" src="https://freight.cargo.site/w/407/i/40526bfd9f224ef9065b96e442745176bc79b552b3734ee69bbbbafb5b33dc6a/WarpingMatch.png" /&#62;
We can do this as an iterative process and stitch multiple images together! 

Here’s the result.


&#60;img width="1432" height="321" width_o="1432" height_o="321" data-src="https://freight.cargo.site/t/original/i/cbba3be1585681c5737108bbf30112493ba125a5ae64470b6096adeab5a45c0d/Panorama.png" data-mid="191519946" border="0"  src="https://freight.cargo.site/w/1000/i/cbba3be1585681c5737108bbf30112493ba125a5ae64470b6096adeab5a45c0d/Panorama.png" /&#62;
TODO: Explanation of Facial recognition with Machine learning



</description>
		
	</item>
		
	</channel>
</rss>