<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://shawnreeves.net//wiki/index.php?action=history&amp;feed=atom&amp;title=Python_ball_pits</id>
	<title>Python ball pits - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://shawnreeves.net//wiki/index.php?action=history&amp;feed=atom&amp;title=Python_ball_pits"/>
	<link rel="alternate" type="text/html" href="https://shawnreeves.net//wiki/index.php?title=Python_ball_pits&amp;action=history"/>
	<updated>2026-04-11T13:26:30Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://shawnreeves.net//wiki/index.php?title=Python_ball_pits&amp;diff=2949&amp;oldid=prev</id>
		<title>Shawn at 04:06, 1 March 2026</title>
		<link rel="alternate" type="text/html" href="https://shawnreeves.net//wiki/index.php?title=Python_ball_pits&amp;diff=2949&amp;oldid=prev"/>
		<updated>2026-03-01T04:06:07Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 21:06, 28 February 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l10&quot;&gt;Line 10:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 10:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&amp;quot;&amp;quot;&amp;quot;Todo&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&amp;quot;&amp;quot;&amp;quot;Todo&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Display on frame notice if no cameras found.&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Display on frame notice if no cameras found.&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;Button for quitting.&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Button for going back to camera selection.&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Button for going back to camera selection.&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Convert from global variables to parameters passed to functions.&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Convert from global variables to parameters passed to functions.&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l16&quot;&gt;Line 16:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 15:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Sliders/dials for parameters&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Sliders/dials for parameters&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Smoothly add/reduce particles&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Smoothly add/reduce particles&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;KNOW THAT VARIABLES INSIDE A FUNCTION CAN BE ACCESSED BY ENCLOSED FUNCTIONS&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-added&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Shawn</name></author>
	</entry>
	<entry>
		<id>https://shawnreeves.net//wiki/index.php?title=Python_ball_pits&amp;diff=2948&amp;oldid=prev</id>
		<title>Shawn: New page with code</title>
		<link rel="alternate" type="text/html" href="https://shawnreeves.net//wiki/index.php?title=Python_ball_pits&amp;diff=2948&amp;oldid=prev"/>
		<updated>2026-03-01T04:05:31Z</updated>

		<summary type="html">&lt;p&gt;New page with code&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;I&amp;#039;d enjoyed the interactive particles on a screen with a camera I&amp;#039;ve seen at the Museum of Science and other places, and I&amp;#039;ve wanted to program them for years, so as I started to learn Python I used Devstral LLM to jumpstart a program, then added features.&lt;br /&gt;
Here&amp;#039;s the code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line copy&amp;gt;&lt;br /&gt;
from cv2_enumerate_cameras import enumerate_cameras&lt;br /&gt;
import cv2&lt;br /&gt;
import numpy as np&lt;br /&gt;
import random&lt;br /&gt;
from collections import deque&lt;br /&gt;
import time&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Todo&lt;br /&gt;
Display on frame notice if no cameras found.&lt;br /&gt;
Button for quitting.&lt;br /&gt;
Button for going back to camera selection.&lt;br /&gt;
Convert from global variables to parameters passed to functions.&lt;br /&gt;
Variable particle colors, size, mass&lt;br /&gt;
Sliders/dials for parameters&lt;br /&gt;
Smoothly add/reduce particles&lt;br /&gt;
KNOW THAT VARIABLES INSIDE A FUNCTION CAN BE ACCESSED BY ENCLOSED FUNCTIONS&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
# Global variables&lt;br /&gt;
particle_count = 80  # Initial number of particles&lt;br /&gt;
min_area = 500       # Minimum area to consider as motion&lt;br /&gt;
history = 10         # Number of frames for background subtraction&lt;br /&gt;
particles = deque(maxlen=particle_count)&lt;br /&gt;
particle_size = 10&lt;br /&gt;
particle_color = (250, 200, 50) &lt;br /&gt;
motion_objects = []  # List to store motion objects (center, direction)&lt;br /&gt;
damping = 0.9995&lt;br /&gt;
gravity = 0.09&lt;br /&gt;
MAX_MOTION_OBJECTS = 10&lt;br /&gt;
STATES = (&amp;quot;Camera selection&amp;quot;, &amp;quot;Activity&amp;quot;)&lt;br /&gt;
state = 0&lt;br /&gt;
window_size = {&amp;quot;width&amp;quot;:1024,&amp;quot;height&amp;quot;:768}&lt;br /&gt;
&lt;br /&gt;
def capture_still(camera):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Capture a still image from the specified camera on its backend&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    cap = cv2.VideoCapture(camera.index, camera.backend)&lt;br /&gt;
    time.sleep(0.6) # Wait for camera to open before getting snapshot; some short times cause error.&lt;br /&gt;
    #print(&amp;quot;Captured a still&amp;quot;)&lt;br /&gt;
    ret, frame = cap.read()&lt;br /&gt;
    cap.release()&lt;br /&gt;
    return frame&lt;br /&gt;
            &lt;br /&gt;
def display_cameras_and_get_selection(cameras):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Display stills from all cameras and let user select one&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    # cameras is a list of CameraInfo objects with properties we&amp;#039;ll use: CameraInfo.name and CameraInfo.index&lt;br /&gt;
    camera_ID = None&lt;br /&gt;
    camera_indices = [camera.index for camera in cameras]&lt;br /&gt;
    #print(f&amp;quot;debug: camera_indices:{camera_indices}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    def on_mouse_event_camera_selection(event, x, y, flags, param):&lt;br /&gt;
        nonlocal camera_ID&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Click event during camera selection&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if event == cv2.EVENT_LBUTTONDOWN:&lt;br /&gt;
            # Get the list of camera indices and their positions&lt;br /&gt;
            camera_indices, positions = param&lt;br /&gt;
            # Check which camera was clicked&lt;br /&gt;
            for i, (cx, cy, cw, ch) in enumerate(positions):&lt;br /&gt;
                if cx &amp;lt;= x &amp;lt;= cx + cw and cy &amp;lt;= y &amp;lt;= cy + ch:&lt;br /&gt;
                    camera_ID = camera_indices[i]&lt;br /&gt;
                    #print(f&amp;quot;on_mouse_event_camera_selection set camera_ID: {camera_ID}&amp;quot;)&lt;br /&gt;
                    return&lt;br /&gt;
        return&lt;br /&gt;
&lt;br /&gt;
    # Capture stills from all cameras&lt;br /&gt;
    stills = []&lt;br /&gt;
    for camera in cameras:&lt;br /&gt;
        stills.append(capture_still(camera))&lt;br /&gt;
&lt;br /&gt;
    # Create a window to display all stills&lt;br /&gt;
    window_name = &amp;quot;Select Camera&amp;quot;&lt;br /&gt;
    cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)&lt;br /&gt;
    cv2.resizeWindow(window_name, window_size[&amp;quot;width&amp;quot;], window_size[&amp;quot;height&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
    # Calculate grid layout&lt;br /&gt;
    cols = 4&lt;br /&gt;
    rows = (len(stills) + cols - 1) // cols&lt;br /&gt;
&lt;br /&gt;
    # Prepare to store camera positions&lt;br /&gt;
    camera_positions = []&lt;br /&gt;
&lt;br /&gt;
    # Calculate image size and spacing&lt;br /&gt;
    spacing = 10&lt;br /&gt;
    img_width = (window_size[&amp;quot;width&amp;quot;] - 2*spacing)//cols&lt;br /&gt;
    img_height = img_width * window_size[&amp;quot;height&amp;quot;] // window_size[&amp;quot;width&amp;quot;]&lt;br /&gt;
    #print(img_width, &amp;quot; &amp;quot;, img_height)&lt;br /&gt;
&lt;br /&gt;
    # Create a blank canvas of 1024x768&lt;br /&gt;
    canvas = np.ones((window_size[&amp;quot;height&amp;quot;], window_size[&amp;quot;width&amp;quot;], 3), dtype=np.uint8)&lt;br /&gt;
&lt;br /&gt;
    # Display all stills in a grid on the canvas&lt;br /&gt;
    for i, still in enumerate(stills):&lt;br /&gt;
        row = i // cols&lt;br /&gt;
        col = i % cols&lt;br /&gt;
&lt;br /&gt;
        # Resize image to 320x240&lt;br /&gt;
        #print(f&amp;quot;Resizing: img_width, img_height:{img_width}, {img_height}&amp;quot;)&lt;br /&gt;
        resized = cv2.resize(still, (img_width, img_height))&lt;br /&gt;
&lt;br /&gt;
        # Calculate position in the grid&lt;br /&gt;
        x = col * (img_width + spacing)&lt;br /&gt;
        y = row * (img_height + spacing)&lt;br /&gt;
&lt;br /&gt;
        # Store camera position for click detection&lt;br /&gt;
        camera_positions.append((x, y, img_width, img_height))&lt;br /&gt;
&lt;br /&gt;
        # Place image on canvas&lt;br /&gt;
        canvas[y:y+img_height, x:x+img_width] = resized&lt;br /&gt;
&lt;br /&gt;
        # Add camera index text&lt;br /&gt;
        cv2.putText(canvas, f&amp;quot;Camera {camera_indices[i]}&amp;quot;, (x+10, y+20),&lt;br /&gt;
                   cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)&lt;br /&gt;
&lt;br /&gt;
    # Display the canvas&lt;br /&gt;
    cv2.imshow(window_name, canvas)&lt;br /&gt;
&lt;br /&gt;
    # Set mouse callback&lt;br /&gt;
    cv2.setMouseCallback(window_name, on_mouse_event_camera_selection, (camera_indices, camera_positions))&lt;br /&gt;
&lt;br /&gt;
    # Wait for user to click on an image&lt;br /&gt;
    last_time=time.time()&lt;br /&gt;
    while True:&lt;br /&gt;
        key = cv2.waitKey(1) &amp;amp; 0xFF&lt;br /&gt;
        if time.time()-last_time &amp;gt; 1.0:&lt;br /&gt;
            last_time = time.time()&lt;br /&gt;
        if key == 27:  # ESC key to exit&lt;br /&gt;
            cv2.destroyAllWindows()&lt;br /&gt;
            return None&lt;br /&gt;
        if camera_ID is not None:&lt;br /&gt;
            cv2.destroyAllWindows()&lt;br /&gt;
            return camera_ID&lt;br /&gt;
&lt;br /&gt;
def activity(camera):    &lt;br /&gt;
    global particle_count&lt;br /&gt;
    global particles&lt;br /&gt;
    class button:&lt;br /&gt;
        def __init__(self, x, y, width, height, title):&lt;br /&gt;
            self.x = x&lt;br /&gt;
            self.y = y&lt;br /&gt;
            self.width = width&lt;br /&gt;
            self.height = height&lt;br /&gt;
            self.title = title&lt;br /&gt;
            self.clicked = False&lt;br /&gt;
        &lt;br /&gt;
    reset_button = button(10,10,100,40,&amp;quot;RESET&amp;quot;)&lt;br /&gt;
    quit_button = button(120,10,100,40,&amp;quot;QUIT&amp;quot;)&lt;br /&gt;
    buttons = [reset_button, quit_button]&lt;br /&gt;
    &lt;br /&gt;
    def on_mouse_click(event, x, y, flags, param):&lt;br /&gt;
        buttons = param&lt;br /&gt;
        if event == cv2.EVENT_LBUTTONDOWN:&lt;br /&gt;
            #print(&amp;quot;mouse clicked&amp;quot;)&lt;br /&gt;
            # Check if click is within the reset button area&lt;br /&gt;
            for button in buttons:&lt;br /&gt;
                if button.x &amp;lt;= x &amp;lt;= button.x+button.width and button.y &amp;lt;= y &amp;lt;= button.y+button.width:&lt;br /&gt;
                #print(&amp;quot;reset clicked&amp;quot;)&lt;br /&gt;
                    button.clicked = True&lt;br /&gt;
        return None&lt;br /&gt;
&lt;br /&gt;
    def on_particle_count_change(val):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Callback function for particle count trackbar&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        global particle_count&lt;br /&gt;
        particle_count = val&lt;br /&gt;
    def update_particles(motion_objects):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Update particle positions and accelerate near motion objects&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        global particles&lt;br /&gt;
&lt;br /&gt;
        for i in range(len(particles)):&lt;br /&gt;
            x, y, dx, dy = particles[i]&lt;br /&gt;
            # Damp motion, more for faster particles&lt;br /&gt;
            dx *= damping/(1+(np.abs(dx)/1000))&lt;br /&gt;
            dy *= damping/(1+(np.abs(dy)/1000))&lt;br /&gt;
            dy += gravity&lt;br /&gt;
    &lt;br /&gt;
            # Check proximity to motion objects&lt;br /&gt;
            for obj in motion_objects:&lt;br /&gt;
                obj_x, obj_y, obj_dx, obj_dy = obj&lt;br /&gt;
                distance = np.sqrt((x - obj_x)**2 + (y - obj_y)**2)&lt;br /&gt;
    &lt;br /&gt;
                # If particle is near a motion object, accelerate in that direction&lt;br /&gt;
                if distance &amp;lt; 100:  # Proximity threshold&lt;br /&gt;
                    # Add motion vector to particle velocity&lt;br /&gt;
                    dx += obj_dx * 1/(distance+2)&lt;br /&gt;
                    dy += obj_dy * 1/(distance+2)&lt;br /&gt;
    &lt;br /&gt;
            # Update position&lt;br /&gt;
            x += dx&lt;br /&gt;
            y += dy&lt;br /&gt;
    &lt;br /&gt;
            # Bounce off edges&lt;br /&gt;
            frame_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)&lt;br /&gt;
            frame_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)&lt;br /&gt;
    &lt;br /&gt;
            if x &amp;lt;= 0:&lt;br /&gt;
                dx = np.abs(dx)&lt;br /&gt;
                x=0&lt;br /&gt;
            if x &amp;gt;= frame_width:&lt;br /&gt;
                dx = -1 * np.abs(dx)&lt;br /&gt;
                x = frame_width&lt;br /&gt;
            if y &amp;lt;= 0:&lt;br /&gt;
                dy = np.abs(dy)&lt;br /&gt;
                y=0&lt;br /&gt;
            if y &amp;gt;= frame_height:&lt;br /&gt;
                dy = -1 * np.abs(dy)&lt;br /&gt;
                y = frame_height&lt;br /&gt;
    &lt;br /&gt;
            particles[i] = [x, y, dx, dy]&lt;br /&gt;
&lt;br /&gt;
    def generate_particles(count=particle_count):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Generate particles at random positions&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        global particles&lt;br /&gt;
        nonlocal cap&lt;br /&gt;
        particles.clear()&lt;br /&gt;
        frame_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)&lt;br /&gt;
        frame_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)&lt;br /&gt;
    &lt;br /&gt;
        for _ in range(count):&lt;br /&gt;
            x = random.randint(0, int(frame_width))&lt;br /&gt;
            y = random.randint(0, int(frame_height))&lt;br /&gt;
            dx = random.uniform(-1, 1)  # Random initial velocity&lt;br /&gt;
            dy = random.uniform(-1, 1)&lt;br /&gt;
            particles.append([x, y, dx, dy])&lt;br /&gt;
    &lt;br /&gt;
&lt;br /&gt;
    # Initialize video capture with selected camera&lt;br /&gt;
    try:&lt;br /&gt;
        cap = cv2.VideoCapture(camera.index, camera.backend)&lt;br /&gt;
        if not cap.isOpened():&lt;br /&gt;
            pass&lt;br /&gt;
            #print(f&amp;quot;camera {camera_index} not available anymore.&amp;quot;)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        pass&lt;br /&gt;
        #print(f&amp;quot;camera {camera_index} not available anymore. error {ValueError}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    # Create window and trackbar&lt;br /&gt;
    activityWindowName=&amp;#039;Motion Detection with Particles&amp;#039;&lt;br /&gt;
    cv2.namedWindow(activityWindowName)&lt;br /&gt;
    cv2.createTrackbar(&amp;#039;Particles&amp;#039;, activityWindowName, particle_count, 100, on_particle_count_change)&lt;br /&gt;
    &lt;br /&gt;
    # Initialize background subtractor&lt;br /&gt;
    backSub = cv2.createBackgroundSubtractorMOG2(history, 16, True)&lt;br /&gt;
    &lt;br /&gt;
    # Generate initial particles&lt;br /&gt;
    generate_particles()&lt;br /&gt;
    cv2.setMouseCallback(activityWindowName, on_mouse_click, buttons)&lt;br /&gt;
&lt;br /&gt;
    while True:&lt;br /&gt;
        ret, frame = cap.read()&lt;br /&gt;
        if not ret:&lt;br /&gt;
            continue&lt;br /&gt;
    &lt;br /&gt;
        # Flip the frame horizontally&lt;br /&gt;
        frame = cv2.flip(frame, 1)&lt;br /&gt;
    &lt;br /&gt;
        # Get current particle count from trackbar&lt;br /&gt;
        current_particle_count = cv2.getTrackbarPos(&amp;#039;Particles&amp;#039;, activityWindowName)&lt;br /&gt;
        if current_particle_count != particle_count:&lt;br /&gt;
            particle_count = current_particle_count&lt;br /&gt;
            particles = deque(maxlen=particle_count)&lt;br /&gt;
            generate_particles(particle_count)&lt;br /&gt;
            &lt;br /&gt;
        # Check if reset button was clicked&lt;br /&gt;
        if reset_button.clicked:&lt;br /&gt;
            #print(&amp;quot;reset clicked&amp;quot;)&lt;br /&gt;
            reset_button.clicked = False&lt;br /&gt;
            generate_particles(particle_count)&lt;br /&gt;
        if quit_button.clicked:&lt;br /&gt;
            break&lt;br /&gt;
    &lt;br /&gt;
        # Convert to grayscale and apply background subtraction&lt;br /&gt;
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)&lt;br /&gt;
        fgMask = backSub.apply(gray)&lt;br /&gt;
    &lt;br /&gt;
        # Find contours in the motion mask&lt;br /&gt;
        contours, _ = cv2.findContours(fgMask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)&lt;br /&gt;
    &lt;br /&gt;
        motion_objects = []  # Reset motion objects each frame&lt;br /&gt;
    &lt;br /&gt;
        for cnt in contours:&lt;br /&gt;
            if cv2.contourArea(cnt) &amp;gt; min_area and len(motion_objects) &amp;lt; MAX_MOTION_OBJECTS:&lt;br /&gt;
                # Get bounding rectangle&lt;br /&gt;
                x, y, w, h = cv2.boundingRect(cnt)&lt;br /&gt;
    &lt;br /&gt;
                # Calculate center of motion&lt;br /&gt;
                center_x = x + w // 2&lt;br /&gt;
                center_y = y + h // 2&lt;br /&gt;
    &lt;br /&gt;
                # Calculate direction from center to motion area&lt;br /&gt;
                frame_center_x = frame.shape[1] // 2&lt;br /&gt;
                frame_center_y = frame.shape[0] // 2&lt;br /&gt;
    &lt;br /&gt;
                dx = center_x - frame_center_x&lt;br /&gt;
                dy = center_y - frame_center_y&lt;br /&gt;
    &lt;br /&gt;
                motion_objects.append((center_x, center_y, dx, dy))&lt;br /&gt;
                #cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)&lt;br /&gt;
                cv2.circle(frame, (center_x, center_y), 20, (255, 0, 255), -1)&lt;br /&gt;
                starsize=20&lt;br /&gt;
                cv2.line(frame, (center_x, center_y), (center_x+starsize, center_y+starsize), (0, 255, 0), 2)&lt;br /&gt;
                cv2.line(frame, (center_x, center_y), (center_x-starsize, center_y+starsize), (0, 255, 0), 2)&lt;br /&gt;
                cv2.line(frame, (center_x, center_y), (center_x+starsize, center_y-starsize), (0, 255, 0), 2)&lt;br /&gt;
                cv2.line(frame, (center_x, center_y), (center_x-starsize, center_y-starsize), (0, 255, 0), 2)&lt;br /&gt;
    &lt;br /&gt;
        # Update and draw particles&lt;br /&gt;
        update_particles(motion_objects)&lt;br /&gt;
        for particle in particles:&lt;br /&gt;
            x, y, _, _ = particle&lt;br /&gt;
            cv2.circle(frame, (int(x), int(y)), particle_size, particle_color, -1)&lt;br /&gt;
        for button in buttons:&lt;br /&gt;
            cv2.rectangle(frame, (button.x, button.y), (button.width, button.height), (255, 255, 255), -1)&lt;br /&gt;
            cv2.putText(frame, button.title, (button.x + 10, button.y + button.height - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2)&lt;br /&gt;
&lt;br /&gt;
        # Display the frame&lt;br /&gt;
        cv2.imshow(activityWindowName, frame)&lt;br /&gt;
    &lt;br /&gt;
        # Exit on &amp;#039;q&amp;#039; key&lt;br /&gt;
        if cv2.waitKey(30) &amp;amp; 0xFF == ord(&amp;#039;q&amp;#039;):&lt;br /&gt;
            break&lt;br /&gt;
    &lt;br /&gt;
    # Release resources&lt;br /&gt;
    cap.release()&lt;br /&gt;
    cv2.destroyAllWindows()&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
def main():&lt;br /&gt;
    # Get available cameras&lt;br /&gt;
    #cameras = get_available_cameras&lt;br /&gt;
    cameras = enumerate_cameras()&lt;br /&gt;
    if not cameras:&lt;br /&gt;
        #print(&amp;quot;No cameras found!&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    if 0 == len(cameras):&lt;br /&gt;
        #print(&amp;quot;No cameras found!&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
&lt;br /&gt;
    # Display stills and get user selection&lt;br /&gt;
    selected_camera = display_cameras_and_get_selection(cameras)&lt;br /&gt;
    # print(f&amp;quot;selected_camera:{selected_camera}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    if selected_camera is not None:&lt;br /&gt;
        #print(f&amp;quot;Starting activity with selected camera: {selected_camera}&amp;quot;)&lt;br /&gt;
        # You can now use this camera index for further processing&lt;br /&gt;
        camera = next((camera for camera in cameras if camera.index == selected_camera), None)&lt;br /&gt;
&lt;br /&gt;
        if camera:&lt;br /&gt;
            # print(f&amp;quot;Found camera: {camera.index}, {camera.backend}&amp;quot;)&lt;br /&gt;
            pass&lt;br /&gt;
        else:&lt;br /&gt;
            # print(&amp;quot;Camera not found&amp;quot;)&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
        activity(camera)&lt;br /&gt;
    else:&lt;br /&gt;
        #print(&amp;quot;No camera selected or operation cancelled&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    main()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Computers]]&lt;/div&gt;</summary>
		<author><name>Shawn</name></author>
	</entry>
</feed>