diff --git a/effect/copenhagen/README.md b/effect/copenhagen/README.md new file mode 100644 index 0000000..c252e43 --- /dev/null +++ b/effect/copenhagen/README.md @@ -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. +

+ +

+ +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. +

+ +

+ +3. **Draw your Strokes** + Paint directly onto the canvas. +

+ . +

+ +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. +

+ +

+ +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. +

+ +

+ +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. +

+ +

+ +

+ +7. **Apply to Canvas** + When you find a color you like, click **Apply to Canvas** to make it permanent. +

+ +

+ +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! +

+ +

+ + +## Dependencies + +- `numpy >= 2.1.0` +- `qiskit >= 2.0.0` +- `qiskit-aer >= 0.17.0` + +## Author +[Saad Bhatti](https://github.com/saadbhattii) \ No newline at end of file diff --git a/effect/copenhagen/__init__.py b/effect/copenhagen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/effect/copenhagen/copenhagen.py b/effect/copenhagen/copenhagen.py new file mode 100644 index 0000000..25ad416 --- /dev/null +++ b/effect/copenhagen/copenhagen.py @@ -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 \ No newline at end of file diff --git a/effect/copenhagen/copenhagen_requirements.json b/effect/copenhagen/copenhagen_requirements.json new file mode 100644 index 0000000..848dfc0 --- /dev/null +++ b/effect/copenhagen/copenhagen_requirements.json @@ -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 + } +} \ No newline at end of file diff --git a/effect/copenhagen/screenshots/control_panel.png b/effect/copenhagen/screenshots/control_panel.png new file mode 100644 index 0000000..e58e6b3 Binary files /dev/null and b/effect/copenhagen/screenshots/control_panel.png differ diff --git a/effect/copenhagen/screenshots/draw.png b/effect/copenhagen/screenshots/draw.png new file mode 100644 index 0000000..6e6b30a Binary files /dev/null and b/effect/copenhagen/screenshots/draw.png differ diff --git a/effect/copenhagen/screenshots/effect_run_1.png b/effect/copenhagen/screenshots/effect_run_1.png new file mode 100644 index 0000000..744172f Binary files /dev/null and b/effect/copenhagen/screenshots/effect_run_1.png differ diff --git a/effect/copenhagen/screenshots/effect_run_2.png b/effect/copenhagen/screenshots/effect_run_2.png new file mode 100644 index 0000000..b5c655c Binary files /dev/null and b/effect/copenhagen/screenshots/effect_run_2.png differ diff --git a/effect/copenhagen/screenshots/effect_run_3.png b/effect/copenhagen/screenshots/effect_run_3.png new file mode 100644 index 0000000..a9efb9d Binary files /dev/null and b/effect/copenhagen/screenshots/effect_run_3.png differ diff --git a/effect/copenhagen/screenshots/full_screen_output.png b/effect/copenhagen/screenshots/full_screen_output.png new file mode 100644 index 0000000..11cd017 Binary files /dev/null and b/effect/copenhagen/screenshots/full_screen_output.png differ diff --git a/effect/copenhagen/screenshots/output_canvas.png b/effect/copenhagen/screenshots/output_canvas.png new file mode 100644 index 0000000..a48d17f Binary files /dev/null and b/effect/copenhagen/screenshots/output_canvas.png differ diff --git a/effect/copenhagen/screenshots/prepare_canvas.png b/effect/copenhagen/screenshots/prepare_canvas.png new file mode 100644 index 0000000..096e7ad Binary files /dev/null and b/effect/copenhagen/screenshots/prepare_canvas.png differ diff --git a/effect/copenhagen/screenshots/stroke_created.png b/effect/copenhagen/screenshots/stroke_created.png new file mode 100644 index 0000000..d896fc1 Binary files /dev/null and b/effect/copenhagen/screenshots/stroke_created.png differ