Highcharts/HighcharteR - draw a polygon with rounded corners - highcharts

library(highcharter)
highchart() %>%
hc_add_series(name='Polygon',type='polygon',data=list(c(1,4),c(2,4), c(3,3), c(2,3)),
borderRadius = 10, lineColor = "red", lineWidth = 3)][1]][1]
Hello everybody. I use a polygon to display some data. I would prefer to have the borders to be round, but the borderRadius attribute does not work for the polygon.
Does anyone have an idea how to archieve a rounded look of my polygon? Documentation did not help in this case :-(. This is made the the R Highcharter package, but I would also be totally fine with an example in die native JS Library.
Thank you!

This works somewhat:
spline.poly <- function(xy, vertices, k=3, ...) {
# Assert: xy is an n by 2 matrix with n >= k.
# Wrap k vertices around each end.
n <- dim(xy)[1]
if (k >= 1) {
data <- rbind(xy[(n-k+1):n,], xy, xy[1:k, ])
} else {
data <- xy
}
# Spline the x and y coordinates.
data.spline <- spline(1:(n+2*k), data[,1], n=vertices, ...)
x <- data.spline$x
x1 <- data.spline$y
x2 <- spline(1:(n+2*k), data[,2], n=vertices, ...)$y
# Retain only the middle part.
cbind(x1, x2)[k < x & x <= n+k, ]
}
X <- matrix(c(resultdf$yAxis, resultdf$xAxis), ncol=2)
hpts <- chull(X) # Creates indices of a convex hull from a matrix
hpts <- c(hpts, hpts[1]) # connect last and first dot
hpts <- data.frame(X[hpts, ])
hpts <- data.frame(spline.poly(as.matrix(data.frame(hpts$X1, hpts$X2)), 500)) %>%
setNames(c("yAxis", "xAxis"))
the spline.poly function creates a lot of new points which connect to a more rounded shape :-)

Related

How to slice an image by table border

I have many png files like this:
I want to slice the image into 48 (=6x8) small image files for the 48 cells separated by the table borders. That is, I would like to have files img11.png, ..., img68.png, where img11.png contains the (1,1) "1.4x4x8" cell, img12.png the (1,2) "M/T" cell, img13.png the "550,000" cell, ..., img68.png the bottom right "641,500" cell.
I want to do it because I thought it would improve the performance of tesseract, which is not satisfactory because many of my image files have much poorer quality than shown above. Also, margins and sizes are diverse, and some images contain non-English characters and images.
Would there be software packages to detect the table borders and slice the image into m x n images? I am new in this area. I have read How to find table like structure in image but it's way beyond my ability. I am willing to learn, though.
Thanks for your help.
I'm using R. Bilal's suggestion (thanks) led me to the following.
Step 1: Convert the image to grayscale.
library(magick)
x <- image_read('https://i.stack.imgur.com/plBvs.png')
y <- image_convert(x, colorspace='Gray')
a <- as.integer(y[[1]])[,,1]
Step 2: Convert "dark" to 1 and "light" to 0.
w <- ifelse(a>190, 0, 1) # adjust 190
Step 3: Detect the horizontal and vertical lines.
ypos <- which(rowMeans(w) > .95) # adjust .95
xpos <- which(colMeans(w) > .95) # adjust .95
Step 4: Crop the original image (x).
xpos <- c(0,xpos, ncol(a))
ypos <- c(0,ypos, nrow(a))
outdir <- "cropped"
dir.create(outdir)
m <- 0
for (i in 1:(length(ypos)-1)) {
dy <- ypos[i+1]-ypos[i]
n <- 0
if (dy < 16) next # skip if too short
m <- m+1
for (j in 1:(length(xpos)-1)) {
dx <- xpos[j+1]-xpos[j]
if (dx < 16) next # skip if too narrow
n <- n+1
geom <- sprintf("%dx%d+%d+%d", dx, dy, xpos[j], ypos[i])
# cat(sprintf('%2d %2d: %s\n', m, n, geom))
cropped <- image_crop(x, geom)
outfile <- file.path(outdir, sprintf('%02d_%02d.png', m, n))
image_write(cropped, outfile, format="png")
}
}
The cropped (1,1) image is .

Is there a way to determine the major orientation of polygon in sf?

I would like to know if there is an off-the-shelf tool or if anyone has developed a method for determining the man axis geographic orientation of spatial shapes. In general, I would like to be able to determine if a shape is oriented east-west or north-south, but ideally there will be an angle or degree measurement associated with each shape.
ArcGIS offers the 'calculate main angle tool' but it is designed for orthogonal shapes and I am working with wildfire perimeters which are blob-like or at least not very orthogonal. At first glance, the Arc tool provides very coarse measurements.
I would like to do this using an sf object, so for an example perhaps use the North Carolina data in the sf package. What is the geographic orientation of each of the 100 counties in North Carolina?
nc <- st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
Thanks for your help!
THe flightplanning-R package has a function that calculates the minimum bounding rectangle, angle of orientation, height, and width. (https://github.com/caiohamamura/flightplanning-R)
I've adjusted it slightly and used it below in another function to return an sf object with angle of orientation and a POLYGON geometry column. The angle is from 0 (east-west) to 180(also east-west), with 90 being north-south.
# Copied function getMinBBox()
# from https://github.com/caiohamamura/flightplanning-R/blob/master/R/utils.R
# credit there given to: Daniel Wollschlaeger <https://github.com/ramnathv>
library(tidyverse)
library(sf)
library(sfheaders)
nc <- st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) %>%
st_geometry() %>% st_as_sf()
getMinBBox <- function(x) {
stopifnot(is.matrix(x), is.numeric(x), nrow(x) >= 2, ncol(x) == 2)
## rotating calipers algorithm using the convex hull
H <- grDevices::chull(x) ## hull indices, vertices ordered clockwise
n <- length(H) ## number of hull vertices
hull <- x[H, ] ## hull vertices
## unit basis vectors for all subspaces spanned by the hull edges
hDir <- diff(rbind(hull, hull[1, ])) ## hull vertices are circular
hLens <- sqrt(rowSums(hDir^2)) ## length of basis vectors
huDir <- diag(1/hLens) %*% hDir ## scaled to unit length
## unit basis vectors for the orthogonal subspaces
## rotation by 90 deg -> y' = x, x' = -y
ouDir <- cbind(-huDir[ , 2], huDir[ , 1])
## project hull vertices on the subspaces spanned by the hull edges, and on
## the subspaces spanned by their orthogonal complements - in subspace coords
projMat <- rbind(huDir, ouDir) %*% t(hull)
## range of projections and corresponding width/height of bounding rectangle
rangeH <- matrix(numeric(n*2), ncol=2) ## hull edge
rangeO <- matrix(numeric(n*2), ncol=2) ## orthogonal subspace
widths <- numeric(n)
heights <- numeric(n)
for(i in seq(along=numeric(n))) {
rangeH[i, ] <- range(projMat[ i, ])
## the orthogonal subspace is in the 2nd half of the matrix
rangeO[i, ] <- range(projMat[n+i, ])
widths[i] <- abs(diff(rangeH[i, ]))
heights[i] <- abs(diff(rangeO[i, ]))
}
## extreme projections for min-area rect in subspace coordinates
## hull edge leading to minimum-area
eMin <- which.min(widths*heights)
hProj <- rbind( rangeH[eMin, ], 0)
oProj <- rbind(0, rangeO[eMin, ])
## move projections to rectangle corners
hPts <- sweep(hProj, 1, oProj[ , 1], "+")
oPts <- sweep(hProj, 1, oProj[ , 2], "+")
## corners in standard coordinates, rows = x,y, columns = corners
## in combined (4x2)-matrix: reverse point order to be usable in polygon()
## basis formed by hull edge and orthogonal subspace
basis <- cbind(huDir[eMin, ], ouDir[eMin, ])
hCorn <- basis %*% hPts
oCorn <- basis %*% oPts
pts <- t(cbind(hCorn, oCorn[ , c(2, 1)]))
## angle of longer edge pointing up
dPts <- diff(pts)
e <- dPts[which.max(rowSums(dPts^2)), ] ## one of the longer edges
eUp <- e * sign(e[2]) ## rotate upwards 180 deg if necessary
deg <- atan2(eUp[2], eUp[1])*180 / pi ## angle in degrees
return(list(pts=pts, width=heights[eMin], height=widths[eMin], angle=deg))
}
##############
## Use getMinBBox in a custom function to return an sf object
##############
min_box_sf <- function(x){
crs <- st_crs(x)
x_as_matrix <- st_coordinates(x)[,1:2]
min_box <- getMinBBox(x_as_matrix)
box <- sfheaders::sf_polygon(min_box$pts) %>%
st_set_crs(crs)
box$angle <- min_box$angle
box
}
# Testing on a county in the nc dataset with an unusual shape and orientation:
min_box_sf(nc[56,])
#> Simple feature collection with 1 feature and 2 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: -76.19819 ymin: 35.11926 xmax: -75.31058 ymax: 36.23016
#> Geodetic CRS: NAD27
#> id geometry angle
#> 1 1 POLYGON ((-76.19819 36.0092... 117.4866
#Plotting county 56 & the associated minimum bounding box
ggplot() +
geom_sf(data = nc[56,],
fill = 'red',
alpha = .2) +
geom_sf(data = min_box_sf(nc[56,]),
fill = NA)
The unusually shaped Dare County, NC has a minimum bounding box with a 'long' orientation of about 117 degrees, or north-north-west to south-south-east.
# Using the function on each row of an sf object.
# note the crs is not retained.
pmap_dfr(nc, min_box_sf)
#> Simple feature collection with 100 features and 2 fields
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: -84.32385 ymin: 33.86573 xmax: -75.31058 ymax: 36.87134
#> CRS: NA
#> First 10 features:
#> id angle geometry
#> 1 1 177.0408464 POLYGON ((-81.74847 36.2486...
#> 2 1 179.0078231 POLYGON ((-81.3505 36.36728...
#> 3 1 178.4492784 POLYGON ((-80.97202 36.2365...
#> 4 1 136.8896308 POLYGON ((-75.59489 36.2906...
#> 5 1 149.5889916 POLYGON ((-77.71197 36.8713...
#> 6 1 179.5157854 POLYGON ((-77.21774 36.2322...
#> 7 1 147.1227419 POLYGON ((-75.90195 36.2792...
#> 8 1 0.1751954 POLYGON ((-76.95329 36.2937...
#> 9 1 0.1759289 POLYGON ((-78.32017 36.1949...
#> 10 1 179.0809855 POLYGON ((-80.02092 36.5467...
Plotting all the counties minimum bounding boxes together:
pmap_dfr(nc, min_box_sf) %>%
ggplot() +
geom_sf(alpha = .2)
Created on 2021-08-20 by the reprex package (v2.0.1)

Pixels regions comparision

I'm trying to write a python script for GIMP, who's aim is to slice a picture into a tileset (identify each unique 16x16 tiles in a picture).
So far, I'm able to read tiles (in fact a 16x16 pixels region) and write it somewhere.
But all my attempts at comparing tiles failed.
Did I miss Something ?
My script is as follow:
#!/usr/bin/env python
from gimpfu import *
# compare 2 tiles,
# return 1 if identical, false otherwise
def tileCompare(tile1, tile2):
if(tile1 == tile2):
return 1
return 0
# return tile at (x, y) coordinates
def readTile(layer, x, y):
pr = layer.get_pixel_rgn(x,y,16,16)
return pr[x:x+16, y:y+16]
# write tile at (x, y) coordinates on given layer
def writeTile(layer, x, y, tile):
pr = layer.get_pixel_rgn(x,y,16,16)
pr[x:x+16, y:y+16] = tile
def TilesSlicer(sourceLayer, targetLayer):
# Actual plug-in code will go here
# iterate tiles (result in tileSource)
for x in range(0, sourceLayer.width, 16):
for y in range(0, sourceLayer.height, 16):
tileSource = readTile(sourceLayer, x, y)
found = 0
# iterate tiles again (result in tileIterator)
for a in range(0, sourceLayer.width, 16):
for b in range(0, sourceLayer.height, 16):
tileIterator = readTile(sourceLayer, x, y)
# compare tiles
# if identical and not yet found
# write it in the target layer
if (tileCompare(tileSource, tileIterator) == 1):
if(found == 0):
writeTile(tileIterator, a, b, tileSource)
found = 1
register(
"TilesSlicer",
"Tiles slicer",
"Slice a picture into tiles",
"Fabrice Lambert",
"Fabrice Lambert",
"April 2019",
"Tiles slicer...",
"RGB*",
[
(PF_DRAWABLE, "sourceLayer", "Source Layer: ", None),
(PF_DRAWABLE, "targetLayer", "Target Layer: ", None),
],
[],
TilesSlicer,
menu="<Image>/Filters/My Scripts")
main()
Thanks for your suggestions.
Nvm,
I found the problem:
tileIterator = readTile(sourceLayer, a, b)
instead of:
tileIterator = readTile(sourceLayer, x, y)
Alright,
After refining a bit, the script is as follow:
- Added tiles width and height to handle any tile size.
- Removed target layer parameter, the script now create it.
- Added real time display to give feedbacks to the user (sadly, progress bar doesn't work).
- Improved speed.
#!/usr/bin/env python
from gimpfu import *
# compare 2 tiles,
# return 1 if identical, 0 otherwise
def tileCompare(tile1, tile2):
if(tile1 == tile2):
return 1
return 0
# return tile at (x, y) coordinates
def readTile(layer, x, y, width, height):
pr = layer.get_pixel_rgn(x, y, width, height)
return pr[x:x+width, y:y+height]
# write tile at (x, y) coordinates on given layer
def writeTile(layer, x, y, width, height, tile):
pr = layer.get_pixel_rgn(x, y, width, height)
pr[x:x+width, y:y+height] = tile
layer.update(x, y, width, height)
gimp.displays_flush()
def TilesSlicer(sourceLayer, tileWidth, tileHeight):
# Actual plug-in code will go here
if((sourceLayer.width % tileWidth) != 0):
gimp.message("The layer width is not multiple of " + str(tileWidth))
gimp.quit()
if((sourceLayer.height % tileWidth) != 0):
gimp.message("The layer height is not multiple of " + str(tileHeight))
gimp.quit()
totalTiles = (sourceLayer.width / tileWidth) * (sourceLayer.height / tileHeight)
tilesProcessed = 0
gimp.progress_init("Processing...")
gimp.progress_update(0.0)
sourceImage = sourceLayer.image
targetLayer = pdb.gimp_layer_new(sourceImage, sourceLayer.width, sourceLayer.height, sourceImage.base_type, "Target", 100.0, sourceLayer.mode)
targetLayer.add_alpha()
targetLayer.fill(TRANSPARENT_FILL)
sourceImage.add_layer(targetLayer, 0)
# iterate tiles (result in tileSource)
for x in range(0, sourceLayer.width, tileWidth):
for y in range(0, sourceLayer.height, tileHeight):
tileSource = readTile(sourceLayer, x, y, tileWidth, tileHeight)
found = 0
# iterate tiles again (result in tileIterator)
for a in range(0, sourceLayer.width, tileWidth):
for b in range(0, sourceLayer.height, tileHeight):
tileIterator = readTile(sourceLayer, a, b, tileWidth, tileHeight)
# compare tiles
# if identical and not yet found
# write it in the target layer
# and abort iteration (for speed purpose)
if (tileCompare(tileSource, tileIterator) == 1):
if(found == 0):
writeTile(targetLayer, a, b, tileWidth, tileHeight, tileIterator)
found = 1
break
if(found == 1):
break
tilesProcessed = tilesProcessed + 1
gimp.progress_update(tilesProcessed / totalTiles)
gimp.displays_flush()
register(
"TilesSlicer",
"Tiles slicer",
"Slice a picture into tiles",
"Fabrice Lambert",
"Fabrice Lambert",
"April 2019",
"Tiles slicer...",
"RGB*",
[
(PF_DRAWABLE, "sourceLayer", "Source Layer: ", None),
(PF_INT8, "tileWidth", "Tile width: ", 16),
(PF_INT8, "tileHeight", "Tile height: ", 16),
],
[],
TilesSlicer,
menu="<Image>/Filters/My Scripts")
main()
It can probably be refined better, and if someone have anything to deal with the progress bar, let me know.
I'm open to suggestions.

Convert raster to a matrix

I can read an image, raster, limit the values from 10-100. What I can't do is convert the limitation to a matrix where I could sum all values.
library(raster)
DEM <- raster("img.JPG")
image(DEM, zlim=c(10,100))
I'd like to convert the result of
image(DEM, zlim=c(10,60))
into a matrix where I can perform calculations.
image(DEM, zlim=c(10,60)) would result in
Target is to only sum the Red Circle.
library(raster)
d <- raster("img.JPG")
dd <- reclassify(d, rbind(c(-Inf, 10, NA), c(60, Inf, NA)))
# or: d[d< 10 | d > 60] <- NA
plot(dd)
hist(dd)
Or, if you really want a matrix
m <- matrix(d)
m[m<10] <- NA

How to make ImageTransformation produce an anamorphic version of image

I'm experimenting with the ImageTransformation function to try to make anamorphic versions of images, but with limited progress so far. I'm aiming for the results you get using the image reflected in a cylindrical mirror, where the image curves around the central mirror for about 270 degrees. The wikipedia article has a couple of neat examples (and I borrowed Holbein's skull from them too).
i = Import["../Desktop/Holbein_Skull.jpg"];
i = ImageResize[i, 120]
f[x_, y_] := {(2 (y - 0.3) Cos [1.5 x]), (2 (y - 0.3) Sin [1.5 x])};
ImageTransformation[i, f[#[[1]], #[[2]]] &, Padding -> White]
But I can't persuade Mathematica to show me the entire image, or to bend it correctly. The anamorphic image should wrap right round the mirror placed "inside" the centre of the image, but it won't. I found suitable values for constants by putting it inside a manipulate (and turning the resolution down :). I'm using the formula:
x1 = a(y + b) cos(kx)
y1 = a(y + b) sin(kx)
Any help producing a better result would be greatly appreciated!
In ImageTransformation[f,img], the function f is such that a point {x,y} in the resulting image corresponds to f[{x,y}] in img. Since the resulting image is basically the polar transformation of img, f should be the inverse polar transformation, so you could do something like
anamorphic[img_, angle_: 270 Degree] :=
Module[{dim = ImageDimensions[img], rInner = 1, rOuter},
rOuter = rInner (1 + angle dim[[2]]/dim[[1]]);
ImageTransformation[img,
Function[{pt}, {ArcTan[-#2, #1] & ## pt, Norm[pt]}],
DataRange -> {{-angle/2, angle/2}, {rInner, rOuter}},
PlotRange -> {{-rOuter, rOuter}, {-rOuter, rOuter}},
Padding -> White
]
]
The resulting image looks something like
anamorphic[ExampleData[{"TestImage", "Lena"}]]
Note that you can a similar result with ParametricPlot and TextureCoordinateFunction, e.g.
anamorphic2[img_Image, angle_: 270 Degree] :=
Module[{rInner = 1,rOuter},
rOuter = rInner (1 + angle #2/#1 & ## ImageDimensions[img]);
ParametricPlot[{r Sin[t], -r Cos[t]}, {t, -angle/2, angle/2},
{r, rInner, rOuter},
TextureCoordinateFunction -> ({#3, #4} &),
PlotStyle -> {Opacity[1], Texture[img]},
Mesh -> None, Axes -> False,
BoundaryStyle -> None,
Frame -> False
]
]
anamorphic2[ExampleData[{"TestImage", "Lena"}]]
Edit
In answer to Mr.Wizard's question, if you don't have access to ImageTransformation or Texture you could transform the image data by hand by doing something like
anamorph3[img_, angle_: 270 Degree, imgWidth_: 512] :=
Module[{data, f, matrix, dim, rOuter, rInner = 1.},
dim = ImageDimensions[img];
rOuter = rInner (1 + angle #2/#1 & ## dim);
data = Table[
ListInterpolation[#[[All, All, i]],
{{rOuter, rInner}, {-angle/2, angle/2}}], {i, 3}] &#ImageData[img];
f[i_, j_] := If[Abs[j] <= angle/2 && rInner <= i <= rOuter,
Through[data[i, j]], {1., 1., 1.}];
Image#Table[f[Sqrt[i^2 + j^2], ArcTan[i, -j]],
{i, -rOuter, rOuter, 2 rOuter/(imgWidth - 1)},
{j, -rOuter, rOuter, 2 rOuter/(imgWidth - 1)}]]
Note that this assumes that img has three channels. If the image has fewer or more channels, you need to adapt the code.

Resources