How to specify a custom channel distribution in ImageMagick? - image

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()

Related

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 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

Number of islands on a map

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

Numbering/Labeling images (eg 1,2,3 or A,B,C) with ImageMagick montage

I'm using
montage *.tif output.tif
to combine several images to one. I would now like them to be numbered (sometimes 1,2,3 …; somtimes A,B,C …)
What possibilities are there to label the single images in the combined? Is there an option to put the label not underneath the picture but eg in the upper left or lower right corner?
Unfortunately I could't really figure out how to use the -label command to achieve that. Thanks for any suggestions.
If you want to invest a little more effort you can have more control. If you do it like this, you can label the images "on-the-fly" as you montage them rather than having to save them all labelled and then montage them. You can also control the width, in terms of number of images per line, more easily:
#!/bin/bash
number=0
for f in *.tif; do
convert "$f" -gravity northwest -annotate +0+0 "$number" miff:-
((number++))
done | montage -tile x3 - result.png
It takes advantage of ImageMagick miff format, which means Multiple Image File Format, to concatenate all the output images and send them into the stdin of the montage command.
Or, you can change the script like this:
#!/bin/bash
number=0
for f in *.tif; do
convert "$f" -gravity northwest -fill white -annotate +0+0 "$number" miff:-
((number++))
done | montage -tile 2x - result.png
to get
Or maybe this...
#!/bin/bash
number=0
for f in *.tif; do
convert "$f" -gravity northwest -background gray90 label:"$number" -composite miff:-
((number++))
done | montage -tile 2x - result.png
Or with letters...
#!/bin/bash
number=0
letters="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
for f in *.tif; do
label=${letters:number:1}
convert "$f" -gravity northwest -background gray90 label:"$label" -composite miff:-
((number++))
done | montage -tile 2x - result.png
What possibilities are there to label the single images in the combined?
Iterate with a for loop.
for INDEX in {A,B,C}; do
convert ${INDEX}.jpg labeled_${INDEX}.jpg
done
Is there an option to put the label not underneath the picture but eg in the upper left or lower right corner?
Try using the -annotate with -gravity
convert rose: -fill white \
-gravity NorthWest -annotate +0+0 "A" \
A.png
convert rose: -fill white \
-gravity SouthEast -annotate +0+0 "B" \
B.png
Based on the answers above, I wrote a small perl script to combine figures into labelled subfigures, which might be useful to others. It works for me on a Mac, but I didn't really test it.
Usage: pics2grid geometry output_file [-title title] [-bycols] input_files
where the geometry format is [# cols]x[# rows] and input_files accept wild cards. If your title contains spaces, the title must be quoted and the spaces escaped.
I paste it below, but it can also be downloaded from http://endress.org/progs.html#subfigs
#!/usr/bin/perl -l
use strict;
use warnings;
use autodie;
use Getopt::Long 'HelpMessage';
### Process options and arguments ###
if (#ARGV < 3){
HelpMessage(1);
}
if (($ARGV[0] !~ /^\d+x/) && ($ARGV[0] !~ /x\d+$/)){
HelpMessage(1);
}
my $geometry = shift #ARGV;
my $output_file = shift #ARGV;
my $title = "";
my $bycols = "";
GetOptions ('bycols' => \$bycols,
'title=s' => \$title,
'help' => sub { HelpMessage() },
) or HelpMessage(1);
# Wildcards are automatically expanded in #ARGV, but just make sure that they are
my #input_files = map { glob } #ARGV;
$title = "-title $title"
if ($title);
if ($bycols){
die "When option -bycols is set, a fully specified geometry is needed."
unless ($geometry =~ /^\d+x\d+$/);
}
#input_files = reorder_input_files (\#input_files, $geometry)
if ($bycols);
### Define the labels for the subfigures. If you want different figures, change it here. ###
my #labels = "a".."z";
#labels = #labels[0..$#input_files];
#labels = reorder_input_files (\#labels, $geometry)
if ($bycols);
my $pic_data;
foreach my $f (#input_files){
# Pictures are combined by rows
my $lab = shift #labels;
$pic_data .= `convert \"$f\" -pointsize 48 -font Arial-Regular -gravity northwest -annotate +20+10 \'$lab\' \\
-bordercolor black -border 1 \\
miff:-`;
}
open (OUT, "| montage -tile $geometry -geometry +0+0 -background white -pointsize 60 -font Arial-Regular $title - $output_file");
print OUT $pic_data;
close (OUT);
sub reorder_input_files {
my ($input_files, $geometry) = #_;
my ($ncols, $nrows) = split (/x/, $geometry);
my #rows = ([]) x $nrows;
foreach my $i (0..$#$input_files){
my #tmp_array = #{$rows[$i % $nrows]};
push (#tmp_array, $input_files->[$i]);
$rows[$i % $nrows] = \#tmp_array;
}
my #reordered_files = ();
map {push (#reordered_files, #$_)} #rows;
return (#reordered_files);
}
=head1 NAME
pics2grid - arrange pictures on a grid of sub-figures and number the individual pictures with letters
=head1 SYNOPSIS
pics2grid geometry output [-title title] [-bycols] inputfiles
The inputfiles argument accepts wild cards.
Geometry format: [\# cols]x[\# rows].
Unless you set the option -bycols, specifying either
the number of rows or of columns is sufficient.
Options:
--title,-t Title. If your title contains spaces, the title needs
to be quoted *and* the spaces need to be escaped.
--bycols,-b Arrange and number pictures by columns rather than rows
--geometry,-g Not yet implemented
--help,-h Print this help message
Examples:
# Create grid with 3 columns and 2 rows in which images are arranged by *rows*
pics2grid.pl 3x2 output.pdf input1.png input2.png input3.png\
input4.png input5.png input6.png
# Create grid with 2 columns and 3 rows in which images are arranged by *columns*
pics2grid.pl 2x3 output.pdf -bycols input1.png input2.png input3.png\
input4.png input5.png input6.png
# Same as above but with a title including spaces.
# Note that the title needs to be quoted *and* the space needs to be escaped
# (i.e., put \ in front of the space)
pics2grid.pl 2x3 output.pdf -bycols -title "My\ title" input1.png\
input2.png input3.png input4.png input5.png input6.png
# Create grid with 4 columns of all png files in the current directory. Images
# are arranged by *columns*.
# It will stop labeling the subfigures for more than 26 images
pics2grid.pl 4x output.pdf *.png
=head1 VERSION
0.01
=cut

Resources