Showing cropped image in bokeh - javascript

I am showing a picture in a figure in bokeh and am using the BoxSelectTool in order to draw a rectangle.
box_select = BoxSelectTool(callback=callback)
p2 = figure(x_range=(0,700), y_range=(0,500),plot_width=1100,plot_height=1100,tools=[box_select])
p2.image_url( url='url',
x=1, y=1, w=700, h=500, anchor="bottom_left",source=im_src)
rect_source = ColumnDataSource(data=dict(x=[], y=[], width=[], height=[]))
callback = CustomJS(args=dict(rect_source=rect_source), code="""
// get data source from Callback args
var data = rect_source.data;
/// get BoxSelectTool dimensions from cb_data parameter of Callback
var geometry = cb_data['geometry'];
/// calculate Rect attributes
var width = geometry['x1'] - geometry['x0'];
var height = geometry['y1'] - geometry['y0'];
var x = geometry['x0'] + width/2;
var y = geometry['y0'] + height/2;
/// update data source with new Rect attributes
data['x'].push(x);
data['y'].push(y);
data['width'].push(width);
data['height'].push(height);
rect_source.data = data;
rect_source.change.emit();
'''
Now I want to show that image region as cropped in a different, smaller figure, after the rectangle is drawn, without clicking a button or anything:
d2 = figure(x_range=(0,200), y_range=(0,100),plot_width=200,plot_height=100)
d2.image( image='image',
x=1, y=1, dw=100, dh=100, source=img)
img = ColumnDataSource( data=dict(image=[]))
So I need something like this in JS:
tmp_im = cv2.imread('static/' + str(im_nr) + '.jpg')
tmp_im = tmp_im[geometry['y0']:geometry['y1'],geometry['x0']:geometry['x1']]
tmp_im = cv2.cvtColor(tmp_im, cv2.COLOR_BGR2GRAY)
img.data = dict(image=[tmp_im])
How can I do that in JS + bokeh?

I suggest to use the module holoviews (part of the pyviz ecosystem) for this task, which provides a high-level access to bokeh.
Holoviews provides so called streams, which can be used together with DynamicMaps to generate dynamic figures based on the (changing) values of the stream.
The module panel (also part of the pyviz ecosystem) can be used to define layouts for visualization.
import numpy as np
import holoviews as hv
from holoviews import opts
from holoviews.streams import BoundsXY
import panel as pn
pn.extension() # loading the panel extension for use with notebook
opts.defaults(opts.Image(tools=['box_select'])) # making sure, that box_select is available
minval, maxval = 0, 200
# x-y data
ls = np.linspace(minval, 10, maxval)
xx, yy = np.meshgrid(ls, ls)
# z-data, e.g. intensity
zz = xx*yy
# min and max, later used to recalibrate the colormapping
zzmin = zz.min()
zzmax = zz.max()
bounds=(0,0, 1,1) # bounds used for the image
im = hv.Image(zz, bounds=bounds)
# stream, xy-data are provided by the box_select-tool
# As start values the same bounds as for the image are used.
box = BoundsXY(bounds=bounds)
# The box-stream is used to draw a rectangle dynamically
# based on the current selection using the box_select-tool.
rect = hv.DynamicMap(
lambda bounds: hv.Bounds(bounds),
streams=[box])
# The box-stream is used to draw an image dynamically
# based on the current selection using the box_select-tool.
im_select = hv.DynamicMap(
lambda bounds: im[bounds[0]:bounds[2],bounds[1]:bounds[3]],
streams=[box])
# Arranging the layout.
# With redim.range we make sure the colormapping uses the original min- and max-values as in 'im',
# and not the min- and max-values from 'im_select'.
layout = pn.Row(im * rect \
+\
im_select.redim.range(z=(zzmin, zzmax)))
layout.app()

Related

How to move the mouse with webshot function inside a dygraph widget

I'm stuck because I can not achive what I want with a dygraph widget created in R.
I have this time series.
#library(xts)
dts <- xts::xts(c(10508,8465,3175,3816,4532,2735,2534,9541,8253,9962),
order.by = seq(from = as.Date("2022-02-01"), to = as.Date("2022-11-01"), by = "month"))
I can plot it in a dygraph widget
#library(dygraph)
object <- dygraph((dts),
ylab = "") %>%
dyRangeSelector(height = 20, strokeColor = "") %>%
dyOptions(fillGraph = FALSE, stackedGraph = FALSE, drawGrid = FALSE, strokeWidth = 3) %>%
dyHighlight(highlightSeriesOpts = list(strokeWidth = 3,highlightCircleSize = 10))
and save it in a html page.
#library(htmlwidgets)
saveWidget(object, "temp.html", selfcontained = FALSE)
Now, I would like to take a screenshot of the plot. I know I can do it with
webshot::webshot(url = "images/temp.html", file = "screenshot.png")
No problem with that. But I should hover the mouse near the last data point before the screenshot, because I would like the chart to show the information of the last point in the legend.
I have tried with
webshot::webshot(url = "images/temp.html", file = "screenshot.png"),
eval = "this.mouse.click(1781, 109);")
I got the X,Y coordinates injecting the following code in the inspection frame of the page.
<html onclick="display(event)">
function display(event) {
let X = event.clientX;
let X = event.clientX;
alert = (X + Y);
}
But it does not seem to work.
I get
but I would like something like this
Actually, I would like to hover the last point regardless the time series.
I know it could be done with ggplot and save me a lot of steps but the dygraph is mandatory and I would like to get the static plot from it.
I know also that it is maybe more of a javascript or casperjs question. If so I wouldn't mind repeat the question in the appropriate forum.
In advance, thank you very much for your help.

opencv.js issue with findTransformECC criteria

I'm attempting to use opencv.js to align images to a baseline image. I'm following some basic python guidance that i've seen work (example: https://alexanderpacha.com/2018/01/29/aligning-images-an-engineers-solution/)
but i'm getting tripped up with an error that I don't quite understand. The error is "opencv.js:30 Uncaught TypeError: Cannot use 'in' operator to search for 'type' in 1e-10" and it seems to be caused by the "criteria" variable passed to "cv.findTransformECC();" see here.
any guidance as to what I'm doing wrong here?
function Align_img(){
let image_baseline = cv.imread(imgElement_Baseline);
let image = cv.imread('imageChangeup');
let im1_gray = new cv.Mat();
let im2_gray = new cv.Mat();
let im2_aligned = new cv.Mat();
//get size of baseline image
width1 = image_baseline.cols;
height1 = image_baseline.rows;
//resize image to baseline image
let dim1 = new cv.Size(width1, height1);
cv.resize(image, image, dim1, cv.INTER_AREA);
// Convert images to grayscale
cv.cvtColor(image_baseline, im1_gray, cv.COLOR_BGR2GRAY);
cv.cvtColor(image, im2_gray, cv.COLOR_BGR2GRAY);
// Find size of image1
let dsize = new cv.Size(image_baseline.rows, image_baseline.cols);
// Define the motion model
warp_mode = cv.MOTION_HOMOGRAPHY;
// Define 3x3 matrix and initialize the matrix to identity
let warp_matrix = cv.Mat.eye(3, 3, cv.CV_8U);
// Specify the number of iterations.
number_of_iterations = 5000;
// Specify the threshold of the increment in the correlation coefficient between two iterations
termination_eps = 0.0000000001; //1e-10;
// Define termination criteria
criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, number_of_iterations, termination_eps);
//Run the ECC algorithm. The results are stored in warp_matrix.
cv.findTransformECC(im1_gray, im2_gray, warp_matrix, warp_mode, criteria, null, 5);
// Use warpPerspective for Homography
cv.warpPerspective (image, im2_aligned, warp_matrix, dsize, cv.INTER_LINEAR + cv.WARP_INVERSE_MAP);
cv.imshow('imageChangeup', im2_aligned);
im1_gray.delete();
im2_gray.delete();
im2_aligned.delete();
};
UPDATE: 2 things. 1. Found easy fix to error (code below) and 2. looks like a bug in the findTransformECC opencv.js API causing this method not to work. Here is current code.
The API has 2 optional parameters (inputMask and gaussFiltSize) but if you don't include them you get an error ("function findTransformECC called with 5 arguments, expected 7 args!").
The issue is what to use for inputMask - "null" does not work, there doesn't seem to be support for 'cv.noArray()' and I can't find a mask that doesn't lead to a 'uncaught exception' error.
I'll update again once I find a workaround. Let me know if anyone sees a work around.
function Align_img(){
let image_baseline = cv.imread(imgElement_Baseline);
let image = cv.imread('imageChangeup');
let im1_gray = new cv.Mat();
let im2_gray = new cv.Mat();
let im2_aligned = new cv.Mat();
//get size of baseline image
var width1 = image_baseline.cols;
var height1 = image_baseline.rows;
//resize image to baseline image
let dim1 = new cv.Size(width1, height1);
cv.resize(image, image, dim1, cv.INTER_AREA);
// Convert images to grayscale
cv.cvtColor(image_baseline, im1_gray, cv.COLOR_BGR2GRAY);
cv.cvtColor(image, im2_gray, cv.COLOR_BGR2GRAY);
// Find size of image1
let dsize = new cv.Size(image_baseline.rows, image_baseline.cols);
// Define the motion model
const warp_mode = cv.MOTION_HOMOGRAPHY;
// Define 3x3 matrix and initialize the matrix to identity
let warp_matrix = cv.Mat.eye(3, 3, cv.CV_8U);
// Specify the number of iterations.
const number_of_iterations = 5000;
// Specify the threshold of the increment in the correlation coefficient between two iterations
const termination_eps = 0.0000000001; //1e-10;
// Define termination criteria
//const criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, number_of_iterations, termination_eps);
let criteria = new cv.TermCriteria(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, number_of_iterations, termination_eps);
//Run the ECC algorithm. The results are stored in warp_matrix.
//let inputMask = new cv.Mat.ones(im1_gray.size(), cv.CV_8U); //uint8
cv.findTransformECC(im1_gray, im2_gray, warp_matrix, warp_mode, criteria, null, 5);
// Use warpPerspective for Homography
cv.warpPerspective (image, im2_aligned, warp_matrix, dsize, cv.INTER_LINEAR + cv.WARP_INVERSE_MAP);
getMatStats(im2_aligned, 1); //0 = baseline (srcMat), 1 = image (srcMat_compare)
cv.imshow('imageChangeup', im2_aligned);
im1_gray.delete();
im2_gray.delete();
im2_aligned.delete();
};
UPDATE 2 I verified code works fine in Python. code below. The issue at hand now is simply, how do you this in Javascript: "inputMask=None"
Python:
# Read the images to be aligned
im1 = cv2.imread(r"C:\temp\tcoin\69.jpg");
im2 = cv2.imread(r"C:\temp\tcoin\pic96_crop.jpg");
#resize image to compare
width1 = int(im1.shape[1])
height1 = int(im1.shape[0])
dim1 = (width1, height1)
im2 = cv2.resize(im2, dim1, interpolation = cv2.INTER_AREA)
# Convert images to grayscale
im1_gray = cv2.cvtColor(im1,cv2.COLOR_BGR2GRAY)
im2_gray = cv2.cvtColor(im2,cv2.COLOR_BGR2GRAY)
# Find size of image1
sz = im1.shape
# Define the motion model
warp_mode = cv2.MOTION_HOMOGRAPHY
# Define 2x3 or 3x3 matrices and initialize the matrix to identity
warp_matrix = np.eye(3, 3, dtype=np.float32)
# Specify the number of iterations.
number_of_iterations = 5000;
# Specify the threshold of the increment
# in the correlation coefficient between two iterations
termination_eps = 1e-10;
# Define termination criteria
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, number_of_iterations, termination_eps)
# Run the ECC algorithm. The results are stored in warp_matrix.
(cc, warp_matrix) = cv2.findTransformECC (im1_gray,im2_gray,warp_matrix, warp_mode, criteria, inputMask=None, gaussFiltSize=1)
# Use warpPerspective for Homography
im2_aligned = cv2.warpPerspective (im2, warp_matrix, (sz[1],sz[0]), flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP)
# Show final results
cv2.imshow("Aligned Image 2", im2_aligned)
cv2.imwrite(r"c:\temp\tcoin\output\pic96_cropB.jpg", im2_aligned)
cv2.waitKey(0)

How can I pass a geometry to the Map function in GEE?

I am trying to use the Map function in Google Earth Engine to clip an ImageCollection to a geometry. I have multiple areas of interest (AOIs) and thus would like to apply a generic clip function multiple times (for each AOI). However, when I create a function with two parameters (the image and the geometry) to map over, I get the error image.clip is not a function. When using the function with just one parameter (the image), it works just fine, but then I need to write two (or possible more) functions to do exactly the same task (i.e. clipping an image to a certain geometry).
I have tried the solutions in this post, but they do not work.
Any ideas on how to solve this issue?
Code:
// Get NTL data
var ntl = ee.ImageCollection("NOAA/VIIRS/DNB/MONTHLY_V1/VCMSLCFG");
// Define start and end year
var startYear = 2015;
var endYear = 2018;
// Filter montly and select 'avg_rad' band
var ntlMonthly = ntl.filter(ee.Filter.calendarRange(startYear, endYear, 'year'))
.filter(ee.Filter.calendarRange(1,12,'month'))
.select(['avg_rad']);
// Create geometries of AOIs
// -- Create a geometry of Venezuela
var geomVenezuela = ee.Geometry.Rectangle([-73.341258, 13.363291, -59.637555, -0.372893]);
// -- Create a geometry of Caracas (Venezuela's capital)
var geomCaracas = ee.Geometry.Rectangle([-67.062383, 10.558489, -66.667078, 10.364908]);
// Functions to crop to Venezuela (nationwide) and Caracas (local) resp.
var clipImageToGeometry = function(image, geom) {
return image.clip(geom);
}
// Apply crop function to the ImageCollection
var ntlMonthly_Venezuela = ntlMonthly.map(clipImageToGeometry.bind(null, geomVenezuela));
var ntlMonthly_Caracas = ntlMonthly.map(clipImageToCaracas.bind(null, geomCaracas));
// Convert ImageCollection to single Image (for exporting to Drive)
var ntlMonthly_Venezuela_image = ntlMonthly_Venezuela.toBands();
var ntlMonthly_Caracas_image = ntlMonthly_Caracas.toBands();
// Check geometry in map
Map.addLayer(geomCaracas, {color: 'red'}, 'planar polygon');
Map.addLayer(ntlMonthly_Caracas_image);
// Store scale (m. per pixel) in variable
var VenezuelaScale = 1000;
var CaracasScale = 100;
// Export the image, specifying scale and region.
Export.image.toDrive({
image: ntlMonthly_Caracas_image,
description: 'ntlMonthly_Caracas_'.concat(startYear, "_to_", endYear),
folder: 'GeoScripting',
scale: CaracasScale,
fileFormat: 'GeoTIFF',
maxPixels: 1e9
});
If I understood your question correctly:
If you want to crop each image in the ImageCollection to a geometry, you could just do
var ntlMonthly_Venezuela = ntlMonthly.map(function(image){return ee.Image(image).clip(geomVenezuela)})
And just repeat for other AOIs.
If you wan to cast it into a function:
var clipImageCollection = function(ic, geom){
return ic.map(function(image){return ee.Image(image).clip(geom)})
}
// Apply crop function to the ImageCollection
var ntlMonthly_Venezuela = clipImageCollection(ntlMonthly, geomVenezuela);
var ntlMonthly_Caracas = clipImageCollection(ntlMonthly, geomCaracas);

Python Bokeh CustomJS: Debugging a JavaScript callback for the Taping-Tool

I am working with Python 3.6.2 and Bokeh 1.0.4 to create a custom JavaScript callback in my plot.
By tapping on one of the points in the plot, I'd like all points sharing the same attribute in the id-column to be highlighted.
Iterating over all datapoints with JavaScript and manipulating the respective 'selected'-attribute in the ColumnDataSource-object should do the trick.
Unfortunately I can not figure out how to correct this code.
# Import packages
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS, HoverTool, TapTool
# Create the data for the points
x = [0, 1, 2, 3]
y = [0, 1, 0, 1]
ids = ['A','B','A','B']
data = {'x':x, 'y':y, 'id':ids}
source = ColumnDataSource(data)
# Add tools to the plot
tap = TapTool()
hover = HoverTool(tooltips=[("X", "#x"),
("Y", "#y"),
("ID", "#id")])
# Create a plotting figure
p = figure(plot_width=400, plot_height=400, tools=[tap,hover])
# Code for the callback
code = """
// Set column name to select similar glyphs
var column = 'id';
// Get data from ColumnDataSource
var data = source.data;
// Get indices array of all selected items
var selected = source.selected.indices;
// Array to store glyph-indices to highlight
var select_inds = [];
// Check if only a single glyph is selected
if(selected.length==1){
// Get the value of the column to find similar attributes/glyphs
attribute_value = data[column][selected[0]];
// Iterate over all entries in the ColumnDataSource
for (var i=0; i<data[column].length; ++i){
// Check if items have the same attribute
if(data[column][i]==attribute_value){
// Add index to selected list
select_inds.push(i);
}
}
}
// Set selected glyphs in ColumnDataSource
source.selected.indices = select_inds;
// Save changes to ColumnDataSource
source.change.emit();
"""
# Create a CustomJS callback with the code and the data
callback = CustomJS(args={'source':source}, code=code)
# Add the callback to the ColumnDataSource
source.callback=callback
# Plots circles
p.circle('x', 'y', source=source, size=25, color='blue', alpha=1, hover_color='black', hover_alpha=1)
# Show plot
show(p)
An older version of this problem with Bokeh 0.13.0 could not solve my problem.
You were almost there! The callback has to be added to the TapTool instead of the ColumnDataSource.
# Import packages
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS, HoverTool, TapTool
# Create the data for the points
x = [0, 1, 2, 3]
y = [0, 1, 0, 1]
ids = ['A','B','A','B']
# Generate data source for the visualization
data = {'x':x, 'y':y, 'id':ids}
source = ColumnDataSource(data)
# Add tools to the plot
tap = TapTool()
hover = HoverTool(tooltips=[("X", "#x"),
("Y", "#y"),
("ID", "#id")])
# Create a plotting figure
p = figure(plot_width=400, plot_height=400, tools=[tap,hover])
# Code for the callback
code = """
// Set column name to select similar glyphs
var column = 'id';
// Get data from ColumnDataSource
var data = source.data;
// Get indices array of all selected items
var selected = source.selected.indices;
// Array to store glyph-indices to highlight
var select_inds = [];
// Check if only a single glyph is selected
if(selected.length==1){
// Get the value of the column to find similar attributes/glyphs
var attribute_value = data[column][selected[0]];
// Iterate over all entries in the ColumnDataSource
for (var i=0; i<data[column].length; ++i){
// Check if items have the same attribute
if(data[column][i]==attribute_value){
// Add index to selected list
select_inds.push(i);
}
}
}
// Set selected glyphs in ColumnDataSource
source.selected.indices = select_inds;
// Save changes to ColumnDataSource
source.change.emit();
"""
# Create a CustomJS callback with the code and the data
callback = CustomJS(args={'source':source}, code=code)
# Add the callback to the taptool
tap.callback=callback
# Plots circles
p.circle('x', 'y', source=source, size=25, color='blue', alpha=1, hover_color='black', hover_alpha=1)
# Show plot
show(p)

How to connect objects with Bezier-like curves using Paper.js

I have a web app prototype where nodes similar to Blender shader editor are connected to each other. I am using Paper.js framework
I want them to be connected using those smooth Bezier-like curves. So I have 2 shapes and I can connect them by making a straight line, but now I want to have handles at the endpoints that smooth these objects out, kinda like this:
So 2 handles on endpoints, pointing horizontally for half the bounding box of the path.
The problem is I can't figure out how to add and edit those handles using Paper.js
The code I have is this:
function makeRectangle(topLeft, size, cornerSize, colour){
var rectangle = new Rectangle(topLeft, size);
var cornerSize = cornerSize;
var path = new Path.RoundRectangle(rectangle, cornerSize);
path.fillColor = colour;
return path;
}
var xy1 = new Point(50,50); //Position of 1st rectangle.
var size = new Size(100, 80); //Size
var c = new Size(8,8); //Corner radius
var col = "#167ee5"; //Colour
var r1 = makeRectangle(xy1, size, c, col); //Make first rectangle
var xy2 = new Point(467,310); //Position of second rectangle
var size2 = new Size(115, 70); //Size of second rectangle
var r2 = makeRectangle(xy2, size2, c, col); //Make secont rectangle
var r1cent = r1.bounds.center; //Get the center points, they will be used as endpoints for the curve.
var r2cent = r2.bounds.center;
var connector = new Path(r1cent, r2cent); //Ok so I made this path... Now what? How do access and edit the handlers at endpoints like in the image?
connector.strokeColor = 'black'; //Give it some colour so we can see it.
You can paste all this code here without any setup, it's a good way to test the framework.
You can use Segment objects when defining the connector rather than using Points (or you can set the handleIn and handleOut properties after creating the path).
The doc is here: Segment
And here is a sketch showing how to use handleIn and handleOut with your code:
sketch.paperjs.org solution

Categories

Resources