Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions effect/copenhagen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# The Copenhagen Brush

## The Copenhagen Interpretation (The Logic)

The Copenhagen interpretation of Quantum Physics was developed by Niels Bohr and his assistant, Werner Heisenberg, between 1925 and 1927. It is based on the proposition that a measurable quantity has no reality until it is measured. In the Copenhagen interpretation, one applies the rules of Quantum Mechanics to sub-microscopic systems but not to the measuring device, which is a macroscopic object that has the property (for reasons which are not understood) of effecting wavefunction collapse from a quantum superposition to a classical probabilistic state.

It is for this reason that a system can be in a superposition of several (or even an infinite number of) states in which a given measurable quantity takes on different values. Once a measurement is performed the wavefunction collapses to what Bohr described as a classical state – i.e. one can apply classical physics to the system after measurement.

## What is the Copenhagen Brush? (High-Level Overview)

The Copenhagen Brush is a digital implementation of wave function collapse. It treats the pixels under your stroke as a system in superposition rather than a collection of fixed values.

Instead of traditional painting or smudging, the brush scans the color distribution within its radius and defines these as the possible states of the system. The act of applying the stroke (or using the Stroke Manager) serves as the measurement.

Once this "measurement" occurs, the wave function collapses. The brush selects a single outcome from all the possible color states and forces every pixel within that stroke to snap into that one specific color.

## Low-Level Description

When a stroke is created using the Copenhagen Brush, the brush collects every pixel within the brush radius along the drawn path. A quantum circuit is then constructed with ceil(log2(n)) qubits, where n is the number of sampled pixels, and a Hadamard gate is applied to each qubit, placing the circuit into superposition and simultaneously representing every pixel index as a possible outcome. The circuit is measured once on a local AerSimulator instance, collapsing the superposition to a single bitstring. That bitstring is converted to an integer index, selecting one pixel from the sampled region. Every pixel in the region is then set to that color and blended back onto the canvas. The simulator is initialized once at module load time rather than per stroke, keeping measurements fast.

The circuit has ceil(log2(n)) qubits. For n pixels in the brush region:

- 1,200 pixels → 11 qubits
- 7,800 pixels → 13 qubits
- 31,400 pixels → 15 qubits

The Hadamard gates are O(log n). The measurement is O(log n). The circuit is tiny and fast.

## The Story

There are many ways to interpret the quantum subatomic world. You might prefer the guided currents of De Broglie–Bohm’s pilot waves, the infinite branching paths of Many-Worlds, or the complexities of Jacobs theory. For me, however, the Copenhagen Interpretation has always felt the most lively.

I like to imagine it as befriending the universe and asking it to make a choice on our behalf. Every time we measure a quantum state, it is as if we are leaning in and saying, "You decide." The universe reaches out and picks a single reality for us to inhabit. I often wish the universe were just as decisive when I am trying to pick what to have for dinner. Jokes apart, the theory suggests a unique, almost poetic interaction between the observer and the observed.

I am not sure about the correctness, but I can sure say it's the most beautiful interpretation.

Hence, I created the Copenhagen Brush.

## Behaviour & Visual Effect

| The Action | The Result |
|---|---|
| Sampling | The brush scans every pixel within its radius |
| Selection | A single "outcome" is plucked from the distribution |
| Transformation | Every pixel in that stroke collapses into that one color |


## Getting Started

Let the universe be your palette. One of the creative ways to starting using this tool is to start with a chaotic, high-energy image like a fluid art piece or a crowded photograph and use the Copenhagen Brush to "draw" your piece of art. You provide the shape; the quantum randomness provides the color. With the high-energy image, you will not run out colors if you plan on using the Copenhagen Brush solely! If, you want to use combinations of the other brushes as well, then you may start with anything!

## Parameters

- **Radius** `int (0-100)` — defines the "observation field" (size of the brush)
- **Alpha** `float (0.0-1.0)` — controls the opacity of the collapsed state
- **Blur Edges** `boolean (yes/no)` — controls if you want smooth feathered or hard edges


## WorkFlow

1. **Prepare the Canvas**
Start by opening the image or canvas you want to work on.
<br><br>
<img src="screenshots/prepare_canvas.png" width="600" />
<br><br>

2. **Select the Tool and Configure Parameters**
Choose the Copenhagen Brush from the Control Panel and set your radius, opacity, and edge blur to whatever fits your needs.
<br><br>
<img src="screenshots/control_panel.png" width="600" />
<br><br>

3. **Draw your Strokes**
Paint directly onto the canvas.
<br><br>
<img src="screenshots/draw.png" width="600" />.
<br><br>

4. **Register the Stroke**
Once you're finished drawing, go to the Control Panel and press **Create**. The app will confirm the stroke is saved.
<br><br>
<img src="screenshots/stroke_created.png" width="600" />
<br><br>

5. **Run the Effect**
Open the **Stroke Manager** from the View tab, select your stroke from the list, and hit **Run**. The brush will then pick a single color from the area you painted and fill the entire stroke with it. The result is dictated entirely by quantum randomness.
<br><br>
<img src="screenshots/effect_run_1.png" width="600" />
<br><br>

6. **Try again**
You can hit **Run** as many times as you like. Every time you click it, the brush will pick a different color from the original pixels.
<br><br>
<img src="screenshots/effect_run_2.png" width="600" />
<br><br>
<img src="screenshots/effect_run_3.png" width="600" />
<br><br>

7. **Apply to Canvas**
When you find a color you like, click **Apply to Canvas** to make it permanent.
<br><br>
<img src="screenshots/output_canvas.png" width="600" />
<br><br>

8. **You are a wizard, Harry!**
That’s it! Your stroke is now part of the image. You may continue creating whatever you like using the power of quantum randomness. The qubit is in your court!
<br><br>
<img src="screenshots/full_screen_output.png" width="600" />
<br><br>


## Dependencies

- `numpy >= 2.1.0`
- `qiskit >= 2.0.0`
- `qiskit-aer >= 0.17.0`

## Author
[Saad Bhatti](https://github.com/saadbhattii)
Empty file added effect/copenhagen/__init__.py
Empty file.
130 changes: 130 additions & 0 deletions effect/copenhagen/copenhagen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import numpy as np
import importlib.util
import math

# This is the same pattern used by all brushes in this project.
spec = importlib.util.spec_from_file_location("utils", "effect/utils.py")
utils = importlib.util.module_from_spec(spec)
spec.loader.exec_module(utils)

# Import quantum libraries
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator

# Initialize the simulator once at module load time.
# This avoids spinning up a new AerSimulator instance on every stroke.
simulator = AerSimulator()


def quantum_random_index(n):
"""
Uses a quantum circuit to randomly select an index from 0 to n-1.

This is the heart of the Copenhagen brush. It prepares qubits
in superposition using Hadamard gates and then measure them.
The measurement outcome selects which color the brush collapses to.

Args:
n (int): Number of possible outcomes (pixels to choose from)

Returns:
int: A quantum-randomly selected index between 0 and n-1
"""

# Calculate how many qubits are needed to represent n possible outcomes.
num_qubits = math.ceil(math.log2(n)) if n > 1 else 1

# Build the quantum circuit with num_qubits quantum bits and num_qubits classical bits
qc = QuantumCircuit(num_qubits, num_qubits)

# Apply a Hadamard gate to every qubit.
qc.h(range(num_qubits))

# Measure all qubits into classical bits.
qc.measure(range(num_qubits), range(num_qubits))

# Run the circuit on the shared AerSimulator instance.
job = simulator.run(qc, shots=1)
result = job.result()
counts = result.get_counts()

# Extract the measured bitstring and convert to integer index.
bitstring = list(counts.keys())[0]
index = int(bitstring, 2) % n # Modulo n ensures the result stays within bounds even if 2^num_qubits > n

return index


def run(params):
"""
The Copenhagen brush: a direct visualization of wave function collapse,
powered by a quantum circuit.

The pixels under the brush exist in superposition: each one is a different
color, a different possibility. A quantum circuit is constructed, qubits
are placed into superposition, and a measurement is made. The quantum
outcome selects one color. Everything collapses to it.

Run the same stroke again and the quantum circuit produces a different
outcome.

Args:
params (dict): Contains stroke_input (image, path) and user_input (Radius, Alpha, Blur Edges)

Returns:
numpy.ndarray: The modified RGBA image array
"""

# Extract image
image = params["stroke_input"]["image_rgba"]
assert image.shape[-1] == 4, "Image must be RGBA format"

height = image.shape[0]
width = image.shape[1]

# Extract user parameters
# Radius:
radius = params["user_input"]["Radius"]
assert radius > 0, "Radius must be greater than 0"

# Alpha:
alpha = params["user_input"]["Alpha"]
assert 0 <= alpha <= 1, "Alpha must be between 0 and 1"

# Blur Edges:
blur = params["user_input"]["Blur Edges"]

# Path: the list of coordinates the user drew on the canvas
path = params["stroke_input"]["path"]

# Find all pixels in the brush region
region, distance = utils.points_within_radius(
path, radius, border=(height, width), return_distance=True
)

# If the brush region is empty, return image unchanged
if len(region) == 0:
return image

# Collect all pixel colors currently under the brush.
pixels_in_region = image[region[:, 0], region[:, 1]]

# The quantum measurement
random_index = quantum_random_index(len(pixels_in_region))
collapsed_color = pixels_in_region[random_index].astype(np.float32) / 255

# Build the patch: every pixel in the region gets the collapsed color.
patch = pixels_in_region.astype(np.float32) / 255

# Set all RGB channels to the single collapsed color
patch[:, :3] = collapsed_color[:3]

# Set opacity from the Alpha parameter
patch[:, 3] = alpha

# Apply to image
image[region[:, 0], region[:, 1]] = utils.apply_patch_to_image(
image[region[:, 0], region[:, 1]], patch, blur=blur, distance=distance
)

return image
37 changes: 37 additions & 0 deletions effect/copenhagen/copenhagen_requirements.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "Copenhagen",
"id": "copenhagen",
"author": "Saad Bhatti",
"version": "1.0.0",
"description": "The Copenhagen Brush collapses all pixels within the stroke region into a single, randomly chosen color—an effect directly inspired by the Copenhagen interpretation of quantum mechanics.\n\n Just as a quantum system in superposition collapses to one definite state upon measurement, this brush observes the pixels beneath it, puts them in superposition and collapses them all to one selected color from that specific region.\n\nThe effect is controlled by:\n 🔹 Radius → determines the size of the brush\n 🔹 Alpha → controls the opacity of the collapse\n 🔹 Blur Edges → ON = smooth, feathered edges; OFF = hard edges\n\nUsage:\n 🔸 Click and drag on the canvas. Every measurement collapses to a different color within the stroke region.",
"dependencies": {
"numpy": ">=2.1.0",
"qiskit": ">=2.0.0",
"qiskit_aer": ">=0.17.0"
},
"user_input": {
"Radius": {
"type": "int",
"min": 0,
"max": 100,
"default": 20
},
"Alpha": {
"type": "float",
"min": 0.0,
"max": 1.0,
"default": 1.0
},
"Blur Edges": {
"type": "bool",
"default": false
}
},
"stroke_input": {
"image_rgba": "array",
"path": "array"
},
"flags": {
"smooth_path": true
}
}
Binary file added effect/copenhagen/screenshots/control_panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added effect/copenhagen/screenshots/draw.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added effect/copenhagen/screenshots/effect_run_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added effect/copenhagen/screenshots/effect_run_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added effect/copenhagen/screenshots/effect_run_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added effect/copenhagen/screenshots/output_canvas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added effect/copenhagen/screenshots/prepare_canvas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added effect/copenhagen/screenshots/stroke_created.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.