donkirkby / live-py-plugin

Live coding in Python with PyCharm, Emacs, Sublime Text, or even a browser
https://donkirkby.github.io/live-py-plugin
MIT License
290 stars 57 forks source link

Avoid Message Limit Exceeded Error in Turtle Mode #546

Closed mtkalms closed 9 months ago

mtkalms commented 1 year ago

What I did

While developing the pillow tutorial I encountered the live coding message limit exceeded error. Image filters usually iterate over each pixel position. Since the plugin will list the result of every iteration in one line, the message limit will very likely be exceeded, even for smaller images.

Here is a simple example:

from PIL import Image

source = Image.open('hopper.jpg')

result_data = []
for pixel in source.getdata():
    if pixel[0] < 120:
        result_data.append(255)  # Live Coding will show the value of result_data here
    else:
        result_data.append(0)

result = Image.new('1', source.size)
result.putdata(result_data)
result.show()

What happened

In Live Mode the error message will only be displayed at the end of the message line. In Turtle Mode however it will only display the stack trace and not the final image (if the error occurs before show() is called).

This will display both the error message and the result image:

from PIL import Image

source = Image.open('hopper.jpg')
source.show()

result = []
for d in range(20480):
    result.append(d)

This will only show the error message:

from PIL import Image

source = Image.open('hopper.jpg')

result = []
for d in range(20480):
    result.append(d)

source.show()

What I wanted to happen

Since the message line is not even displayed in Turtle Mode it would be better to just ignore the message limit violation.

Another issue that occurs in Live Mode is that several long lines seem to create performance issues. My suggestion would be an annotation for functions or a comment tag for single lines to mark regions of the code that should not be displayed or even processed by the plugin.

from PIL import Image

source = Image.open('hopper.jpg')

result_data = []
for pixel in source.getdata():
    if pixel[0] < 120:
        # livepy:mute
        result_data.append(255)
    else:
        # livepy:mute
        result_data.append(0)

result = Image.new('1', source.size)
result.putdata(result_data)
result.show()
from PIL import Image

@livepy(mute=True)
def pixel_filter(image: Image) -> Image:
    result_data = []
    for pixel in image.getdata():
        if pixel[0] < 120:
            result_data.append(255)
        else:
            result_data.append(0)
    result = Image.new('1', source.size)
    result.putdata(result_data)
    return result

source = Image.open('hopper.jpg')
result = pixel_filter(source)
result.show()

(These are usage examples to illustrate my idea, not a well formed naming or implementation suggestion)

My environment

IntelliJ IDEA 2023.1 (Community Edition) Build #IC-231.8109.175, built on March 28, 2023 Runtime version: 17.0.6+10-b829.5 aarch64 macOS 13.3.1

Live Coding Plugin 4.10.0

Other feedback

There might be even more graceful ways to handle message limit violations especially for loops.

donkirkby commented 1 year ago

Yes, I've also run into this problem. I've been wondering if the canvas option should disable the message builder so it can ignore the message limit. I'll think about whether that would cause any problems. One nice thing about the current system is that you can quickly flip back and forth between canvas and program state without having to rerun it. However, I don't do that very often.

donkirkby commented 1 year ago

The reason I originally added the message limit was to deal with infinite loops. I guess I could use a timer instead for canvas mode.

donkirkby commented 9 months ago

I've switched the canvas mode to use a 10s timer instead of the message limit, but your example still takes more than 10 seconds. I don't really want to make the time limit longer, because the browser is still running single threaded, and it's unusable with a long delay. Hopefully, the 10s time limit is an improvement over the arbitrary message limit.

For your specific example, using numpy is much faster:

import numpy as np
from PIL import Image

source = Image.open('hopper.jpg')
pixels = np.asarray(source)
dark_pixels = pixels[:, :, 0] >= 120

result = Image.fromarray(dark_pixels)
result.show()

I haven't deployed the timer code yet, but it's available in a GitHub codespace until I deploy version 4.11. See the contributing file for instructions on the new codespace setup.