Number of islands on a map - image

I've got a map (a picture on a computer) and I'd like to count the islands (whatever the size).
I've thought of x and y projections before counting 1D values but it only works for small numbers and not for specific dispositions.
Is there a known and efficient algorithm to do that?
Thanks.

Updated Answer
Ok, it seems you don't really have a map, you just have a list of 16384 numbers each representing land or sea but no indication of the size of the image it corresponds to - which isn't going to help you determine the adjacency of any pieces of land!
As there are 16,384 values, let's see what the factors are that could give that size:
1x16,384
2x8,192
...
32x512
64,256
128x128
I tried each of hose by putting a simple PGM format header on the front and converting the result to a JPG but nothing looks recognisable so I can't do anything till you tell me how the file works - with width and height.
{ echo P2; echo 128 128 ; echo 65535; tr " " "\n" < ~/Desktop/image_raw.txt; } | convert PGM:- -auto-level map.jpg
Original Answer
ImageMagick will do that for you quite easily using "Connected Components Analysis". It is installed on most Linux distros and is available for OSX and Windows.
As you haven't provided a map, let's use this one of Hawaii.
Just at the command-line without any programming, you can run:
convert hawaii.png -colorspace gray -negate -threshold 10% \
-define connected-components:verbose=true \
-define connected-components:area-threshold=100 \
-connected-components 8 -auto-level output.png
Output
Objects (id: bounding-box centroid area mean-color):
0: 1303x892+0+0 633.0,443.3 1072404 srgba(0,0,0,1)
7: 273x307+956+489 1074.8,639.4 47500 srgba(255,255,255,1)
5: 163x132+825+330 898.1,395.2 12978 srgba(255,255,255,1)
3: 145x116+509+188 580.6,251.7 9895 srgba(255,255,255,1)
1: 117x96+210+79 271.3,124.5 8407 srgba(255,255,255,1)
4: 139x56+702+291 769.9,317.9 5598 srgba(255,255,255,1)
6: 71x61+752+353 787.5,381.5 3087 srgba(255,255,255,1)
2: 58x72+119+121 147.3,157.1 2407 srgba(255,255,255,1)
That tells you there are 7 islands - one line per island plus one for the whole image = the first one.
I can box them in like this:
convert hawaii.png -stroke red -fill none -strokewidth 1 \
-draw "rectangle 956,489 1229,796" \
-draw "rectangle 825,330 988,462" \
-draw "rectangle 509,188 654,304" \
-draw "rectangle 210,79 327,175" \
-draw "rectangle 702,291 841,347" \
-draw "rectangle 752,353 823,414" \
-draw "rectangle 119,121 177,193" \
boxed.png

Related

How to specify a custom channel distribution in ImageMagick?

I've got a bunch of files that are not e. g. RGB16 but essentially have a custom format.
This format has 16 bits, and the channels are divided as follows, and I want ImageMagick to import them as such and then export them to a readable, regular RGBA format (or alternatively, export each channel into a seperate grayscale file).
Bit 0-4: Red
Bit 5-9: Green
Bit 10-12: Blue
Bit 13-15: Alpha
So essentially 5-5-3-3.
Here is a zipped sample file, the wanted output (in PNG) is also included.
Does anyone know how to do that?
I succeeded in doing this in a different, manual way, but I have no idea how to do this in ImageMagick.
The first thing to do is take the expected output you kindly provided and separate that into its constituent RGBA channels and lay them out side-by-side so we can see what we are aiming for in each channel:
magick out.png -separate +smush 10 channels.png
Next thing is to check the file size. You said 512x512 pixels of 16-bit data. That means I am expecting 512x512x2, i.e. 524,288 bytes but your file has 524,290 bytes. A quick examination shows you have a 2-byte BOM at the start that we need to skip over. That means our command must treat the data as 512x512 pixels of 16-bit greyscale data with a 2-byte header to skip. We also do not want to read the file 4 times, so we put it into an MPR "Magick Persistent Register" (or "lump of named memory" is how I think of it) and recall the original data from there each time as we decode the channels.
So, in the command below, the first line reads the input image, skipping the BOM and puts it into the MPR. The second line takes a copy of the MPR and bit-twiddles the Red channel out of it. The second, third and fourth lines do the same for the Green, Blue and Alpha channels. The final line combines the 4 channels in the processing stack into the output file.
magick -size 512x512+2 -endian MSB -depth 16 gray:in.file -write MPR:orig +delete \
\( MPR:orig -evaluate and 63488 \) \
\( MPR:orig -evaluate and 1984 -evaluate leftshift 5 \) \
\( MPR:orig -evaluate and 56 -evaluate leftshift 10 \) \
\( MPR:orig -evaluate and 7 -evaluate leftshift 13 \) \
-combine result.png
Note:
63488 = 0xf800
1984 = 0x07c0
56 = 0x0038
On Windows, which I don't use and cannot test, that command will look something like this:
magick -size 512x512+2 -endian MSB -depth 16 gray:in.file -write MPR:orig +delete ^
( MPR:orig -evaluate and 63488 ) ^
( MPR:orig -evaluate and 1984 -evaluate leftshift 5 ) ^
( MPR:orig -evaluate and 56 -evaluate leftshift 10 ) ^
( MPR:orig -evaluate and 7 -evaluate leftshift 13 ) ^
-combine result.png
Just for fun, you can do the same thing in Python:
#!/usr/bin/env python3
import numpy as np
from PIL import Image
h, w = 512, 512
# Read raw 16-bit file, skipping BOM with offset=2. Reshape.
raw = np.fromfile('in.file', dtype=np.dtype('>u2'), offset=2).reshape((h,w))
# RGBA5533 packed into uint16
R = (np.bitwise_and(raw, 0xf800) >> 8).astype(np.uint8)
G = (np.bitwise_and(raw, 0x07c0) >> 3).astype(np.uint8)
B = (np.bitwise_and(raw, 0x0038) << 2).astype(np.uint8)
A = (np.bitwise_and(raw, 0x0007) << 5).astype(np.uint8)
# Stack the individual channels into RGBA
RGBA = np.dstack((R,G,B,A))
# Display or save
Image.fromarray(RGBA).show()

Change the RGB numbers of images using ImageMagick

I am trying to convert images to rgb: (R:242 - G:235 - B: 190), but I can't seem to find the right command for that. All I found was this command that darkens the image, but when I changed gray47 to the numbers I want the output was black:
convert \
input.jpg \
-channel red -fx 'r - gray47' \
-channel green -fx 'g - gray47' \
-channel blue -fx 'b - gray47' \
output.jpg
ImageMagick version 6.8.9-9 Q16 x86_64
Or if any other command/script does that for Linux in bulk I am open to try anything else.
I want to batch colorize all images in a folder and subfolders from
to:
In ACDsee the colorize works well with these options, but it's slow and doesn't allow subfolders (for thousands of images):
It looks like you want a constant color output image in ImageMagick. You can do that by converting your colors to percent.
R:242 - G:235 - B:190
So
R=242/255=94.9%
G=235/255=92.2%
B=190/255=74.5%
Then
convert input.jpg -fill "srgb(94.9%,92.2%,74.5%)" -colorize 100 output.jpg
If on ImageMagick 7, then change convert to magick
If I understand you correctly, you want to adjust the whitepoint to make the new white 242, 235, 190.
You can do this by multiplying the channels by 255 / 242, 255 / 235, 255 / 190, or 1.05, 1.09, 1.34. You can use recolor for this, eg.:
red_scale=1.05
green_scale=1.09
blue_scale=1.34
convert $infile -recolor "$red_scale 0 0 0 $green_scale 0 0 0 $blue_scale" $outfile
This is a 3x3 colour recombination matrix -- be careful to get all the 0s in the right place.
You could also consider libvips:
vips linear $infile $outfile "$red_scale $green_scale $blue_scale" "0 0 0"
It should be faster and use less memory.

How to generate an image with a number in it?

This command from ImageMagic creates an image writing the value of variable var in its center:
convert -size 512x512 xc:none -font "Free-Monospaced-Bold" -pointsize 180 -gravity center -draw 'text 0,0 '"$var"' ' "$var".png
While it works well for strings, it fails when the value of var is a number:
convert-im6.q16: non-conforming drawing primitive definition `text' # error/draw.c/DrawImage/3265.
Since variables in bash are untyped, there's no way to transform it into a string (command printf will leave it as it is, a number), so the point must be to change the text argument in the command.
How to generate the image when the variable is numeric (positive, negative and real numbers)?
While the documentation for -draw's text operator only says that the string to render needs to be quoted if it has spaces, it appears it also needs to be quoted if it's numeric. So...
-draw "text 0,0 '$var' "
You can also put a number in an image in Imagemagick using -annotate in place of -draw.
var=7
convert -size 512x512 xc:none -font "Free-Monospaced-Bold" -pointsize 180 -gravity center -annotate +0+0 "$var" "$var".png
Or
var=7
convert -size 512x512 xc:none -font "Free-Monospaced-Bold" -pointsize 180 -gravity center -annotate +0+0 $var $var.png
But safest to use double quotes

How to remove all lines from an image? (horizontal, vertical, diagonal)

I need to remove the lines in an image, which is a table eventually. I have found a way to remove the horizontal and vertical lines:
convert 1.jpg -type Grayscale -negate -define morphology:compose=darken -morphology Thinning 'Rectangle:1x80+0+0<' -negate out.jpg
The following image:
Was converted to the following one:
As one can see the diagonal line is still there. I tried to rotate the image for 45 degrees and then try to remove it, but was also unsuccessful. How it can be done? Any suggestions are appreciated. I opted for imagemagick, but any other options are welcome
You can try using cv2.HoughLinesP() to detect the diagonal line then use a mask to fill in the contour
import cv2
import numpy as np
image = cv2.imread('1.jpg')
mask = np.zeros(image.shape, np.uint8)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(gray,100,200)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
close = cv2.morphologyEx(canny, cv2.MORPH_CLOSE, kernel)
minLineLength = 10
maxLineGap = 350
lines = cv2.HoughLinesP(close,1,np.pi/180,100,minLineLength,maxLineGap)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(mask,(x1,y1),(x2,y2),(255,255,255),3)
mask = cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY)
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(image, [c], -1, (255,255,255), -1)
cv2.imshow('mask', mask)
cv2.imshow('image', image)
cv2.imwrite('image.png', image)
cv2.waitKey()
Here is another approach. I use Imagemagick, since I am not proficient with OpenCV. Basically, I binarize the image. Then do connected components processing to isolate the largest contiguous black region, which will be the black lines you want to exclude. Then use that as a mask to fill in white over the lines. This is Unix syntax with Imagemagick.
Note that some text characters will be lost, if they touch the black lines.
Input:
Get the id number of the largest black region:
id=`convert Arkey.jpg -threshold 50% -type bilevel \
-define connected-components:verbose=true \
-define connected-components:mean-color=true \
-connected-components 4 null: |\
grep "gray(0)" | head -n 1 | sed -n 's/^ *\(.*\):.*$/\1/p'`
Isolate the black lines and dilate them
convert Arkey.jpg -threshold 50% -type bilevel \
-define connected-components:mean-color=true \
-define connected-components:keep=$id \
-connected-components 4 \
-alpha extract \
-morphology dilate octagon:2 \
mask.png
Fill white over the lines in the image using the mask for control:
convert Arkey.jpg \( -clone 0 -fill white -colorize 100 \) mask.png -compose over -composite result.png
See -connected-components at https://imagemagick.org/script/connected-components.php for details how it works.

imagmagick: selectively fill pixels based on r/g/b condition?

Is there a way to selectively replace colors, based on an arithmetic expression using a pixel's R or G or B value?
Example: say I have an RGB image "foobar.png" and I want to change all pixels whose Red channel is <100 into white.
In pseudocode:
for (all pixels in image) { if (pixel.red < 100) then pixel = 0xffffff; }
Is there a way to pull this off with ImageMagick?
This is similar but slightly different from emcconville's excellent solution. This is in Unix syntax.
#1 compute the 100 out of 255 threshold in percent
#2 read the input
#3 clone the input and make it completely white
#4 clone the input and separate the red channel, threshold and negate so that the white part represents values less than 100 out of 255
#5 use the threshold image as a mask in a composite to select between the original and the white images
#6 write the output
thresh=`convert xc: -format "%[fx:100*100/255]" info:`
convert image.png \
\( -clone 0 -fill white -colorize 100 \) \
\( -clone 0 -channel r -separate +channel -threshold $thresh% -negate \) \
-compose over -composite \
result.png
You can use FX expressions.
Say I create a test image...
convert -size 400x400 gradient:red-blue input.png
Replace any pixel with a red value < 100 (assuming max-value is a 8bit quantum of 255), can be expressed by..
convert input.png -fx 'r < (100/255) ? #FFFFFF : u' output.png
Update
FX is powerful, but slow. It'll also draw harsh edges. Another approach is to separate the RED channel, convert it to a mask, and composite over the other channels. This can be done with -evaluate-sequance MAX, or set the alpha channel & compose over a white background.
Create an example input image.
convert -size 400x400 xc:white \
-sparse-color shepards '0 0 red 400 0 blue 400 400 green 0 400 yellow ' \
input.png
convert -size 400x400 xc:white \
\( input.png \
\( +clone -separate -delete 1,2 \
-negate -level 39% -negate \
\) \
-compose CopyOpacity -composite \
\) -compose Atop -composite output.png

Resources