<jupyter_start><jupyter_text>Generate Interactive 2D Point CloudUse this notebook to generate an interactive 2D point clouds. You need to provide a folder with a couple of PNG files containing white points on black background. The script will take these images and create random codes that will allow you to show each of the images when entering it.Check out an example containing 6 hidden images from Brazil here: [https://haltakov.net/point-cloud-brazil/](https://haltakov.net/point-cloud-brazil/). Use the following codes to see the first 3 images, but the rest you will have to find yourself :)- 8198399457- 6674004952- 0856561979  Project InputsSet these variables for your specific project. If you are not sure about something leave it to the default settings.<jupyter_code>points_images_folder = "images"         # Folder containing the PNG images to be encoded in the point cloud
project_name = "Point Cloud Brazil"     # Project name
output_folder = "output"                # Folder where the point cloud will be saved
single_file = False                     # Set to true if everything should be compiled in a signle file

points_to_add = 10000                   # Number of points to try to add to the point cloud
min_distance_between_points = 5         # Minimum distance (in pixels) of a new point to an existing one
code_length = 10                        # Secret code length<jupyter_output><empty_output><jupyter_text>SetupThis section sets up the environment.<jupyter_code>import os
import cv2
import random
import jinja2
import numpy as np
from PIL import Image
from pathlib import Path
from shutil import copyfile
import plotly.graph_objects as go

common_layout = go.Layout(
    autosize=False,
    width=500,
    height=500,
    margin=go.layout.Margin(l=10, r=10, b=10, t=10),
    xaxis=dict(
        visible=False,
        showgrid=False
    ),
    yaxis=dict(
        visible=False,
        showgrid=False
    )
)<jupyter_output><empty_output><jupyter_text>Read and prepare the dataLoad all the images from the folder and process all white pixels as points.<jupyter_code># Find all the PNG images in the folder
points_image_files = list(Path(points_images_folder).glob("*.png"))
print("Images found:", len(points_image_files))

points = []
classes_count = len(points_image_files)

for id, filename in enumerate(points_image_files):
    print("Processing:", str(filename))

    # Load the points image
    points_image = cv2.imread(str(filename), cv2.IMREAD_GRAYSCALE)
    height, width = points_image.shape

    # Find each non black pixel and store its coordinates as a point
    for y in range(0, height):
        for x in range(0, width):
            if points_image[y][x] > 0:
                points.append((x, height-y, id))

points = np.array(points)

print("Total points found:", len(points))<jupyter_output><empty_output><jupyter_text>Generate random points in the point cloudThis part fills up the point clound random points in order to "hide" the images.<jupyter_code># Compute the center of the image
center = np.array([height/2, width/2])
max_distance_center = width/2

for i in range(0, points_to_add):
    # Create a random point
    p = np.random.randint(height, size=(1, 2))

    # Find the distance to the closest existing point
    dist = np.min(np.linalg.norm(points[:,0:2] - p, axis=1))

    # Check if the point is not too close to another point and in the circle entered at the middle of the image
    if dist >= min_distance_between_points and np.linalg.norm(center - p) < max_distance_center:
        points = np.append(points, [(p[0][0], p[0][1], 255)], axis=0)<jupyter_output><empty_output><jupyter_text>Display the point cloud using Plotly<jupyter_code>data = go.Scatter(x=points[:,0], y=points[:,1], mode='markers')
go.Figure(data=data, layout=common_layout).show()<jupyter_output><empty_output><jupyter_text>Generate the features and the weightsPerform MSE minimization to find the best features that can be classified with randomly generated weights<jupyter_code># Important parameters for the optimization
features_count = code_length
learning_rate = 0.001
iterations = 1000<jupyter_output><empty_output><jupyter_text>Create random weights of the model that will be used to create the secret keys<jupyter_code># Create random codes
W = 2*np.random.rand(features_count, classes_count) - 1

# Transform the weights to be mappable to an int between 0 and 9 (a signle digit)
W = (10 + 10*W)/2
W = (2*W.astype(np.int32) - 10) / 10.0

# Show the weights as digits
W_digits = (10+10*W.T)/2

for weight in W_digits:
    print("".join(map(str, weight.astype(np.int8))))
# W_strings = np.array2string(W_digits.T.astype(np.int8),  separator='')
# W_digits<jupyter_output><empty_output><jupyter_text>Optimize the features so that only the points of image corresponding to the provided code (weights) are mapped to 1 and all others are mapped to 0.<jupyter_code># Initialize the features at random
points_count = points.shape[0]
X = 2*np.random.rand(points_count, features_count)-1

# One hot encoding of the labels
labels = np.zeros((points_count, classes_count))
for c in range(0, classes_count):
    labels[:, c] = (points[:,2] == c)
# Do the optimization
for i in range(0, iterations):
    # Compute the predictions based on the current features and weights
    predictions = X @ W

    # Compute the loss (MSE)
    diffs = labels - predictions
    loss = np.sum(diffs ** 2) / (classes_count * points_count)

    # Compute the gradients
    grad_X = np.zeros((points_count, features_count))
    for c in range(0, classes_count):
        grad_X -= 2 * np.expand_dims(diffs[:,c], 1) * np.expand_dims(W[:,c], 0)

    # Update the deatures
    X -= learning_rate * grad_X

    if i % 10 == 0:
        print(f"Iteration {i}\tLoss = {loss}")<jupyter_output><empty_output><jupyter_text>Display ResultsIn this part you can test displaying the point cloud with a specific code. You can play around changing the `class_to_show` to see the different images.<jupyter_code># Change this to the ID of the image you want to see
class_to_show = 0

# Compute the score for every point and take only the ones with a high score
predicted = X @ W[:, class_to_show]
predicted_points = points[predicted > 0.5, :]

# Plot the selected points
data = go.Scatter(x=predicted_points[:,0], y=predicted_points[:,1], mode='markers')
go.Figure(data=data, layout=common_layout).show()<jupyter_output><empty_output><jupyter_text>Create the Point Cloud web pageWrite the points to a JS file<jupyter_code># Create a data frame and shuffle the points
data = np.concatenate((points[:, 0:2], X),1)
np.random.shuffle(data)

# Write the points to a JS file
with open(Path(output_folder, "p.js"), "w") as pout:
    pout.write("points = [")
    for point in data:
        pout.write(f"[{','.join(point.astype('str'))}], \n")
    pout.write("];")<jupyter_output><empty_output><jupyter_text>Create the final point cloud page from the template<jupyter_code># Copy the template files
copyfile("template/index.html", Path(output_folder, "index.html"))
copyfile("template/points.js", Path(output_folder, "points.js"))
copyfile("template/points.css", Path(output_folder, "points.css"))
copyfile("template/plotly-basic.min.js", Path(output_folder, "plotly-basic.min.js"))

# Setup the jinja environment
file_loader = jinja2.FileSystemLoader(output_folder)
env = jinja2.Environment(loader=file_loader)

# Renter the template
template = env.get_template("index.html")
html = template.render(project_name=project_name, single_file=single_file)

# Save the HTML file
with open(Path(output_folder, "index.html"), "w") as out:
    out.write(html)

# If single file, delete the JS and CSS files
if single_file:
    Path(output_folder, "p.js").unlink()
    Path(output_folder, "points.js").unlink()
    Path(output_folder, "points.css").unlink()
    Path(output_folder, "plotly-basic.min.js").unlink()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10329587069074378, 'fraction_numerical': 0.01780527844424801, 'mean_word_length': 4.4809688581314875, 'pattern_counts': {'":': 0, '<': 46, '<?xml version=': 0, '>': 48, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '643890', 'license': 'MIT', 'n_tokens_mistral': 2564, 'n_tokens_neox': 2453, 'n_words': 813, 'path': 'generate_2d_point_cloud.ipynb', 'repo_name': 'haltakov/point-cloud-art', 'size': '7919'}