0
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import numpy as np
from PIL import Image
import requests
from io import BytesIO

img_src = 'https://unsplash.it/500/300'
response = requests.get(img_src)
img = Image.open(BytesIO(response.content))


img = np.asarray(img.convert('L'))


fig,ax = plt.subplots()
zlims = (np.percentile(img[:, :], 0), np.percentile(img[:, :], 100 - 0))
im = ax.imshow(img, aspect='auto',vmin=zlims[0], vmax=zlims[1])

threshold = 0.
axthreshold = plt.axes([0.2, 0.01, 0.65, 0.03])
sthreshold = Slider(axthreshold, 'clip', -.1, 15., 
                    valinit=threshold, valstep=None)

def update(val):
    ax.clear()
    zlims = (np.percentile(img[:, :], .1+val), np.percentile(img[:, :], 100 - (.1+val)))
    print(zlims[0],zlims[1])
    ax.imshow(img, aspect='auto',vmin=zlims[0], vmax=zlims[1])
    fig.canvas.draw_idle()
    
sthreshold.on_changed(update)
update(threshold)

this creates a image where you can limit what range the colormap covers with a slider. As "clip" increases the color map variety lessens.

Im am pretty familiar with flask. Is there any way to implement something like this into flask? I would prefer matplotlib but I'm ok with trying anything.

Thanks!

--Update--- I realized that I don't want to refresh the page every time the picture is adjusted. I tried plotly javascript approach. I was happy until I realized the res is too low.

html:

<!doctype html>
<html class="no-js" lang="en">
<head>
    <meta http-equiv="X-UA-Compatible" content="ie=edge" /> 
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        
    <script src="https://cdn.plot.ly/plotly-2.11.1.min.js"></script>
    
    <style>
    .container {
        display: flex;
        align-items: center;
        justify-content: center;
    }
    </style>    
    </head>
<body>
<div class="container" id="myDiv"></div>
<div class="container">
    <input style="width:50%" id="slide" type="range" min="0" max="5" step="0.025" value=".1" name="name_of_slider">
</div>
<div class="container" id="sliderAmount"></div>
<script>
const z1 = JSON.parse('{{ Lsort | tojson }}');
const IMG = JSON.parse('{{ IMG | tojson }}');
var size = z1.length 
var qlower = z1[Math.ceil(size * .1 / 100) - 1]
var qupper = z1[Math.ceil(size * (100-.1) / 100) - 1]
var data = [
  {
    z: IMG,
    type: 'heatmap',
    colorscale: 'Viridis',
    zauto: false,
    zmax: qupper,
    zmin: qlower,
  }
];

var layout = {
  autosize: false,
  width: 1000,
  height: 600,
};
Plotly.newPlot('myDiv', data, layout);
var slide = document.getElementById('slide'),
sliderDiv = document.getElementById("sliderAmount");
slide.onchange = function() {
    sliderDiv.innerHTML = this.value;  
    var perc = this.value    
    var qlower = z1[Math.ceil(size * perc / 100) - 1]
    var qupper = z1[Math.ceil(size * (100-perc) / 100) - 1]
     
    var data = [
      {
        z: IMG,
        type: 'heatmap',
        colorscale: 'Viridis',
        zauto: false,
        zmax: qupper,
        zmin: qlower,
      }
    ];
    
    var layout = {
      autosize: false,
      width: 1000,
      height: 600,
    };
    Plotly.newPlot('myDiv', data, layout);
}     
</script>
</body>
</html>

python:

from PIL import Image
import requests
from io import BytesIO
img_src = 'https://unsplash.it/500/300'
response = requests.get(img_src)
imgarray = Image.open(BytesIO(response.content))
imgarray = np.asarray(imgarray.convert('L'))


n = np.sort(imgarray.ravel()).tolist()
imgarray2D = np.flip(imgarray,axis=0)
imgarray2D = imgarray2D.tolist()

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def main():  
    return render_template('clip.html',Lsort = n,IMG=imgarray2D)
if __name__ == '__main__':
    app.run()
Krits
  • 53
  • 1
  • 7
  • Are you going to show us how it looks? Is loading something from `unsplash` critical to the question? – Mark Setchell May 29 '22 at 21:40
  • No, it’s not critical at all. I want to use only numpy and matpltlib. I only included pil and a online pic so anyone could give it a try – Krits May 29 '22 at 21:51

1 Answers1

0

I am no expert in Flask or Javascript, but I did something (maybe clumsy!) similar to a Matplotlib slider using an HTML range slider.

So, you put one of those sliders (with a class="watch" because you're watching it) and an HTML img in your web-page's HTML so you will have at least a slider in the index.html:

<input name="XXX" id="YYY" style="width:100%" class="watch" type="range" step="1" min="0" max="255" value="0">  

and also an img:

<img src="{{url_for('static', filename='default.png')}}" id="image" class="img-fluid">

Then attach an onChange() function to the slider like this so it gets called whenever the slider is moved:

$(document).ready(function() {
  $(".watch").on( "change input", fChanged);
  fChanged();   // do one initial update before user changes anything
});

You'll also need the jQuery CDN stuff. I used:

<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

And the function fChanged() gathers up the values of a number of sliders on the HTML page into a JSON to send to Flask endpoint called /_refresh like this:

{# https://flask.palletsprojects.com/en/2.0.x/patterns/jquery/ #}
$SCRIPT_ROOT = {{ request.script_root|tojson|safe }};

function fChanged(eObj) {
   $.getJSON($SCRIPT_ROOT + '/_refresh', {
        Hmin: $('#Hmin').val(),
        Hmax: $('#Hmax').val(),
        Smin: $('#Smin').val(),
        Smax: $('#Smax').val(),
        Vmin: $('#Vmin').val(),
        Vmax: $('#Vmax').val(),
        mask: jQuery("input[type='radio']").filter(":checked").attr("value")
      }, function(data) {
        $("#code").text(data.code);
        $("#image").attr("src", "data:image/png;base64,"+data.image);
      });
      return false;
}

You can see I had 6 sliders, a min and a max for each of Hue, Saturation and Value.

The Flask endpoint returned another JSON containing some code I wanted to display on the webpage and a base64-encoded PNG-format result image that I then used to update an HTML img by writing its src attribute.

The Python code for the _refresh endpoint looks like this - it does some other stuff with Hue, Saturation and Value that isn't interesting to you, but it shows how I processed the image and encoded it for returning as a JSON to the webpage:

@app.route('/_refresh')
def refresh():
    # DEBUG print(request.args)
    Hmin = request.args.get('Hmin')
    Hmax = request.args.get('Hmax')
    Smin = request.args.get('Smin')
    Smax = request.args.get('Smax') 
    Vmin = request.args.get('Vmin')
    Vmax = request.args.get('Vmax')
    mask = request.args.get('mask')
    # Do the image processing
    if 'localfilename' in session:
       im, HSV = loadImage(session['localfilename'])
    else:
       im, HSV = loadImage('default')
    lo = np.array([Hmin,Smin,Vmin], dtype=np.uint8)
    hi = np.array([Hmax,Smax,Vmax], dtype=np.uint8)
    mask = cv2.inRange(HSV, lo, hi)
    alpha = np.zeros((im.shape[0],im.shape[1]), dtype=np.uint8) + mask
    res  = np.dstack((im, alpha))
    # DEBUG 
    # cv2.imwrite('result.png', res)
    _, PNG = cv2.imencode(".png", res)
    b64img = encodebytes(PNG.tobytes()).decode('ascii')
    response =  { 
        'Status' : 'Success', 
        'image': b64img,
:        'code': render_template('code.py', Hmin=Hmin, Hmax=Hmax, Smin=Smin, Smax=Smax, Vmin=Vmin, Vmax=Vmax)
    }
    return jsonify(response) # send the result to client

You are not obliged to use OpenCV for the processing or base64-encoding, you can equally do all that with PIL/Pillow if you prefer - just ask, or look at any of my previous PIL/Pillow answers.

It might all be clumsy and misguided but it worked for me. I'll be glad too if someone else shows a better way!


By the way, if anyone is using macOS and trying to run the code in your question, the GUI with the image and slider pops up briefly and disappears. You can then spend ages reading about Matplotlib backends, how to list them, how to see what's available, how to change them, that you need a "framework" Python installation, or maybe don't and after around an hour, you discover that all you need is to add an extra line as follows:

...
ax.imshow(img, aspect='auto',vmin=zlims[0], vmax=zlims[1])
fig.canvas.draw_idle()
plt.show()                 # add this line if plot flashes and disappears
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • excited to give this a try. where do I find encodebytes and loadImage? – Krits May 31 '22 at 02:46
  • Sorry. You need `from base64 import encodebytes` at the top. – Mark Setchell May 31 '22 at 07:34
  • I loaded a default image from the webserver disk at startup and cached it in the Flask session and allowed the website user to upload their own to replace that default. So I made `loadImage()` to get that from the session. Underneath it is just exactly `im = cv2.imread('SOMEIMAGE.PNG')` So you need `import cv2` at the top also. – Mark Setchell May 31 '22 at 07:37
  • I tired working your answer with my set up, i ran into some issues I think i could figure out. Does you code load the browser every-time the html slider is adjusted? I wanted to avoid that but I should have made that more clear in my question . – Krits Jun 01 '22 at 22:47
  • It loads the default image from disk once at startup and then holds it as a Numpy array in memory. That gets overwritten with a new Numpy array if the user uploads a new image. Each time the slider is moved the server-side image is reprocessed and sent to the browser. – Mark Setchell Jun 02 '22 at 02:02
  • You can do image processing in the browser with OpenCVJS if that helps... https://www.digitalocean.com/community/tutorials/introduction-to-computer-vision-in-javascript-using-opencvjs – Mark Setchell Jun 02 '22 at 02:04