How to solve this exclusion/inclusion problem with z3py? - z3py

Thanks to community help I have come up with this code:
from z3 import *
Color, (Red, Green, Blue) = EnumSort('Color', ('Red', 'Green', 'Blue'))
Size, (Big, Medium, Small) = EnumSort('Size', ('Big', 'Medium', 'Small'))
h1c, h2c, h3c = Consts('h1c h2c h3c', Color)
h1s, h2s, h3s = Consts('h1s h2s h3s', Size)
s = Solver()
myvars = [h1c, h2c, h3c, h1s, h2s, h3s]
s.add(Distinct([h1c, h2c, h3c]))
s.add(Distinct([h1s, h2s, h3s]))
s.add(h3s == Medium)
s.add(h3c == Red)
res = s.check()
n = 1
while (res == sat):
print("%d. " % n),
m = s.model()
block = []
for var in myvars:
v = m.evaluate(var, model_completion=True)
print("%s = %-5s " % (var, v)),
block.append(var != v)
s.add(Or(block))
n = n + 1
res = s.check()
This solves the problem, where only one house can be, for example, Medium size and Red color. And other combinations stay as variations.
However what I also want is a condition, that the House why is, for Example, Green is Small. Not pointing initially to particular house. This would exclude all variations where Green or Small is not combined (Green can not be Medium, and Small can not be Red, etc.)... But also keep the distinct so, for example, only one house can be Green and Small. So later if I say house 1 is Green or Small, then for house 1 is this one variation and no other houses (variations) can be Green or Small.
Example after 1st condition (Green is Small):
h1 = Green + Small
h2 = Green + Small
h3 = Green + Small
h1 = Red + Medium
h1 = Red + Big
h2 = Red + Medium
h2 = Red + Big
h3 = Red + Medium
h3 = Red + Big
h1 = Blue + Medium
h1 = Blue + Big
h2 = Blue + Medium
h2 = Blue + Big
h3 = Blue + Medium
h3 = Blue + Big ( I might missed something)
Example after 2nd condition (House 1 is Small/Green):
h1 = Green + Small
h2 = Red + Medium
h2 = Red + Big
h3 = Red + Medium
h3 = Red + Big
h2 = Blue + Medium
h2 = Blue + Big
h3 = Blue + Medium
h3 = Blue + Big ( I might missed something)
I have been looking into Functions and children variable, but see no way how to compare Any variable in stack. I think the code needs to be totally reorganized?

#JohanC's answer is just fine, but I do agree with the OP that these sorts of constraints can get really out of hand and impossible to manage if you don't approach them in a systematic way. I found that creating dictionaries and your own abstractions can really help. Note that this isn't really z3/z3py specific, but in general for programming. For instance, here's how I would code your problem:
from z3 import *
Color, (Red, Green, Blue) = EnumSort('Color', ('Red', 'Green', 'Blue'))
Size, (Big, Medium, Small) = EnumSort('Size', ('Big', 'Medium', 'Small'))
# Create a house and store properties in a dictionary
def mkHouse(name):
return { 'name' : name
, 'color': Const(name + "_color", Color)
, 'size' : Const(name + "_size", Size)
}
allHouses = [mkHouse(n) for n in ["house1", "house2", "house3"]]
s = Solver ()
# Assert sizes and colors are different
s.add(Distinct([h['color'] for h in allHouses]))
s.add(Distinct([h['size'] for h in allHouses]))
def forallHouses(pred):
cond = True
for house in allHouses:
cond = And(cond, pred(house))
s.add(cond)
# Assert that Green house is small. Note the implication.
forallHouses(lambda h: Implies(h['color'] == Green, h['size'] == Small))
# Assert that If a house is Red, then it cannot be Medium
forallHouses(lambda h: Implies(h['color'] == Red, h['size'] != Medium))
# Collect the solutions:
res = s.check()
n = 1
while (res == sat):
print("Solution %d: " % n)
m = s.model()
block = []
for house in allHouses:
hcolor = m.evaluate(house['color'], model_completion=True)
hsize = m.evaluate(house['size'], model_completion=True)
print(" %-5s = %-5s %-5s" % (house['name'], hcolor, hsize))
block.append(Or(house['color'] != hcolor, house['size'] != hsize))
s.add(Or(block))
n = n + 1
res = s.check()
Note the use of a dictionary to keep track of the name, size, and color of the house. You can add new properties as you wish, and everything stays local for easy manipulation and extraction later on. In particular, the function forallHouses captures the essence of what you're trying to say: You want to say something about each individual house, and it captures this via a lambda-function.
In the above example, I asserted the Green house is Small and Red house is not Medium. (This implies the Red house must be big, something z3 discovers for us.) When I run it, I get:
Solution 1:
house1 = Blue Medium
house2 = Green Small
house3 = Red Big
Solution 2:
house1 = Green Small
house2 = Red Big
house3 = Blue Medium
Solution 3:
house1 = Green Small
house2 = Blue Medium
house3 = Red Big
Solution 4:
house1 = Red Big
house2 = Blue Medium
house3 = Green Small
Solution 5:
house1 = Red Big
house2 = Green Small
house3 = Blue Medium
Solution 6:
house1 = Blue Medium
house2 = Red Big
house3 = Green Small
Which I believe is in line with what you are trying to achieve. Hopefully, you can start from this skeleton and turn it into something that you can use as you model more complicated constraints.

One way or another you'd need to add a condition:
s.add(Or(And(h1c == Green, h1s == Small),
And(h2c == Green, h2s == Small),
And(h3c == Green, h3s == Small)))
Everything can be written a bit more flexible with arrays:
from z3 import EnumSort, Consts, Solver, Distinct, Or, And, sat
Color, (Red, Green, Blue) = EnumSort('Color', ('Red', 'Green', 'Blue'))
Size, (Big, Medium, Small) = EnumSort('Size', ('Big', 'Medium', 'Small'))
hc = Consts('h1c h2c h3c', Color)
hs = Consts('h1s h2s h3s', Size)
s = Solver()
s.add(Distinct(hc))
s.add(Distinct(hs))
s.add(Or([And(hci == Green, hsi == Small) for hci, hsi in zip(hc, hs)]))
res = s.check()
n = 1
while (res == sat):
print(f"{n:-2d}.", end=" ")
m = s.model()
block = []
for i, (hci, hsi) in enumerate (zip(hc, hs), start=1):
hci_v = m.evaluate(hci, model_completion=True)
hsi_v = m.evaluate(hsi, model_completion=True)
print(f'{f"h{i}:{hci_v}+{hsi_v}":<15}', end="")
block.append(hci != hci_v)
block.append(hsi != hsi_v)
print()
s.add(Or(block))
n += 1
res = s.check()
Result:
1. h1:Blue+Big h2:Green+Small h3:Red+Medium
2. h1:Green+Small h2:Red+Medium h3:Blue+Big
3. h1:Red+Medium h2:Blue+Big h3:Green+Small
4. h1:Red+Big h2:Blue+Medium h3:Green+Small
5. h1:Blue+Big h2:Red+Medium h3:Green+Small
6. h1:Blue+Medium h2:Red+Big h3:Green+Small
7. h1:Blue+Medium h2:Green+Small h3:Red+Big
8. h1:Red+Big h2:Green+Small h3:Blue+Medium
9. h1:Red+Medium h2:Green+Small h3:Blue+Big
10. h1:Green+Small h2:Blue+Medium h3:Red+Big
11. h1:Green+Small h2:Blue+Big h3:Red+Medium
12. h1:Green+Small h2:Red+Big h3:Blue+Medium
PS: An approach that simplifies the condition that the small house is green, is to change the representation. Instead of representing the color and the size for each of the houses, one could represent the house number for each of the colors and each of the sizes. This will need additional conditions that each of the colors should be either 1,2 or 3. And the same condition for the sizes:
from z3 import Ints, Solver, Distinct, Or, And, sat
Red, Green, Blue = Ints('Red Green Blue')
Big, Medium, Small = Ints('Big Medium Small')
colors = [Red, Green, Blue]
sizes = [Big, Medium, Small]
s = Solver()
s.add(Distinct(colors))
s.add(Distinct(sizes))
s.add(And([Or([color == i for i in (1, 2, 3)]) for color in colors]))
s.add(And([Or([size == i for i in (1, 2, 3)]) for size in sizes]))
s.add(Green == Small)
res = s.check()
n = 1
while (res == sat):
print(f"{n:-2d}.", end=" ")
m = s.model()
block = []
for x in colors + sizes:
x_v = m.evaluate(x, model_completion=True).as_long()
print(f"{x}:h{x_v}", end=" ")
block.append(x != x_v)
print()
s.add(Or(block))
n += 1
res = s.check()
Result:
1. Red:h3 Green:h2 Blue:h1 Big:h3 Medium:h1 Small:h2
2. Red:h2 Green:h3 Blue:h1 Big:h2 Medium:h1 Small:h3
3. Red:h2 Green:h3 Blue:h1 Big:h1 Medium:h2 Small:h3
4. Red:h1 Green:h2 Blue:h3 Big:h1 Medium:h3 Small:h2
5. Red:h3 Green:h2 Blue:h1 Big:h1 Medium:h3 Small:h2
6. Red:h1 Green:h3 Blue:h2 Big:h1 Medium:h2 Small:h3
7. Red:h3 Green:h1 Blue:h2 Big:h3 Medium:h2 Small:h1
8. Red:h3 Green:h1 Blue:h2 Big:h2 Medium:h3 Small:h1
9. Red:h1 Green:h3 Blue:h2 Big:h2 Medium:h1 Small:h3
10. Red:h1 Green:h2 Blue:h3 Big:h3 Medium:h1 Small:h2
11. Red:h2 Green:h1 Blue:h3 Big:h2 Medium:h3 Small:h1
12. Red:h2 Green:h1 Blue:h3 Big:h3 Medium:h2 Small:h1
If necessary, the output could be reformatted to the same format as the first solution. Whether one solution is "less workaround" or "clearer" or "easier to maintain" seems a very subjective question. Converting a problem into a format for a SAT/SMT solver is always kind of tricky.

Related

Cutting algorithm of two dimensional board

I have problem with my homework.
Given a board of dimensions m x n is given, cut this board into rectangular pieces with the best total price. A matrix gives the price for each possible board size up through the original, uncut board.
Consider a 2 x 2 board with the price matrix:
3 4
3 6
We have a constant cost for each cutting for example 1.
Piece of length 1 x 1 is worth 3.
Horizontal piece of length 1 x 2 is worth 4.
Vertical piece of length 1 x 2 is worth 3.
Whole board is worth 6.
For this example, the optimal profit is 9, because we cut board into 1 x 1 pieces. Each piece is worth 3 and we done a 3 cut, so 4 x 3 - 3 x 1 = 9.
Second example:
1 2
3 4
Now I have to consider all the solutions:
4 1x1 pieces is worth 4x1 - (cost of cutting) 3x1 = 1
2 horizontal 1x2 is worth 2x2 - (cost of cutting) 1x1 = 3
2 vertical 1x2 is worth 3x2 - (cost of cutting) 1x1 = 5 -> best optimal profit
1 horizontal 1x2 + 2 x (1x1) pieces is worth 2 + 2 - (cost of cutting) 2 = 2
1 vertical 1x2 + 2 x (1x1) pieces is worth 3 + 2 - (cost of cutting) 2 = 3
I've read a lot about rod cutting algorithm but I don't have any idea how to bite this problem.
Do you have any ideas?
I did this in Python. The algorithm is
best_val = value of current board
check each horizontal and vertical cut for better value
for cut point <= half the current dimension (if none, return initial value)
recur on the two boards formed
if sum of values > best_val
... best_val = that sum
... record cut point and direction
return result: best_val, cut point, and direction
I'm not sure what you'll want for return values; I gave back the best value and tree of boards. For your second example, this is
(5, [[2, 1], [2, 1]])
Code, with debugging traces (indent and the labeled prints):
indent = ""
indent_len = 3
value = [[1, 2],
[3, 4]]
def best_cut(high, wide):
global indent
print indent, "ENTER", high, wide
indent += " " * indent_len
best_val = value[high-1][wide-1]
print indent, "Default", best_val
cut_vert = None
cut_val = best_val
cut_list = []
# Check horizontal cuts
for h_cut in range(1, 1 + high // 2):
print indent, "H_CUT", h_cut
cut_val1, cut_list1 = best_cut(h_cut, wide)
cut_val2, cut_list2 = best_cut(high - h_cut, wide)
cut_val = cut_val1 + cut_val2
if cut_val > best_val:
cut_list = [cut_list1, cut_list2]
print indent, "NEW H", h_cut, cut_val, cut_list
best_val = cut_val
cut_vert = False
best_h = h_cut
# Check vertical cuts
for v_cut in range(1, 1 + wide // 2):
print indent, "V_CUT", v_cut
cut_val1, cut_list1 = best_cut(high, v_cut)
cut_val2, cut_list2 = best_cut(high, wide - v_cut)
cut_val = cut_val1 + cut_val2
if cut_val > best_val:
cut_list = [cut_list1, cut_list2]
print indent, "NEW V", v_cut, cut_val, cut_list
best_val = cut_val
cut_vert = True
best_v = v_cut
# Return result of best cut
# Remember to subtract the cut cost
if cut_vert is None:
result = best_val , [high, wide]
elif cut_vert:
result = best_val-1, cut_list
else:
result = best_val-1, cut_list
indent = indent[indent_len:]
print indent, "LEAVE", cut_vert, result
return result
print best_cut(2, 2)
Output (profit and cut sizes) for each of the two tests:
(9, [[[1, 1], [1, 1]], [[1, 1], [1, 1]]])
(5, [[2, 1], [2, 1]])
Let f(h,w) represent the best total price achievable for a board with height h and width w with cutting price c. Then
f(h,w) = max(
price_matrix(h, w),
f(i, w) + f(h - i, w) - c,
f(h, j) + f(h, w - j) - c
)
for i = 1 to floor(h / 2)
for j = 1 to floor(w / 2)
Here's a bottom-up example in JavaScript that returns the filled table given the price matrix. The answer would be in the bottom right corner.
function f(prices, cost){
var m = new Array(prices.length);
for (let i=0; i<prices.length; i++)
m[i] = [];
for (let h=0; h<prices.length; h++){
for (let w=0; w<prices[0].length; w++){
m[h][w] = prices[h][w];
if (h == 0 && w == 0)
continue;
for (let i=1; i<(h+1>>1)+1; i++)
m[h][w] = Math.max(
m[h][w],
m[i-1][w] + m[h-i][w] - cost
);
for (let i=1; i<(w+1>>1)+1; i++)
m[h][w] = Math.max(
m[h][w],
m[h][i-1] + m[h][w-i] - cost
);
}
}
return m;
}
$('#submit').click(function(){
let prices = JSON.parse($('#input').val());
let result = f(prices, 1);
let str = result.map(line => JSON.stringify(line)).join('<br>');
$('#output').html(str);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="input">[[3, 4],
[3, 6]]</textarea>
<p><button type="button" id="submit">Submit</button></p>
<div id="output"><div>
Some thoughts on the problem rather than an answer:
It was a long time ago i studied dynamic programming, but i wrote up the following pseudo code which is think is O(n^2):
// 'Board'-class not included
val valueOfBoards: HashMap<Board, int>
fun cutBoard(b: Board, value: int) : int {
if (b.isEmpty()) return 0
if (valueOfBoards[b] > value) {
return 0;
} else {
valueOfBoards[b] = value
}
int maxValue = Integer.MIN_VALUE
for (Board piece : b.getPossiblePieces()) {
val (cuttingCost, smallerBoard) = b.cutOffPiece(piece)
val valueGained: int = piece.getPrice() - cuttingCost
maxValue = Max(maxValue, valueGained + cutBoard(smallerBoard, value + valueGained))
}
return maxValue;
}
The board class is not trivially implemented, here is some elaboration:
// returns all boards which fits in the current board
// for the initial board this will be width*height subboards
board.getPossiblePieces()
// returns a smaller board and the cutting cost of the cut
// I can see this becoming complex, depends on how one chooses to represent the board.
board.cutOffPiece(piece: Board)
It is not clear to me at the moment if cutOffPiece() breaks the algorithm in that you do not know how to optimally cut. I think since the algorithm will proceed from larger pieces to smaller pieces at some point it will be fine.
I tried to solve the re computation of sub problems (identical boards) by storing results in something like HashMap<Board, price> and comparing the new board with the stored best price before proceeding.
According to your answers I've prepared bottom-up and top-down implementation.
Bottom-up:
function bottomUp($high, $wide, $matrix){
$m = [];
for($h = 0; $h < $high; $h++){
for($w = 0; $w < $wide; $w++){
$m[$h][$w] = $matrix[$h][$w];
if($h == 0 && $w == 0){
continue;
}
for($i = 1; $i < ($h + 1 >> 1) + 1; $i++){
$m[$h][$w] = max(
$m[$h][$w],
$m[$i - 1][$w] + $m[$h - $i][$w] - CUT_COST
);
}
for($i = 1; $i < ($w + 1 >> 1) + 1; $i++){
$m[$h][$w] = max(
$m[$h][$w],
$m[$h][$i - 1] + $m[$h][$w - $i] - CUT_COST
);
}
}
}
return $m[$high-1][$wide-1];
}
Top-down:
function getBestCut($high, $wide, $matrix){
global $checked;
if(isset($checked[$high][$wide])){
return $checked[$high][$wide];
}
$bestVal = $matrix[$high-1][$wide-1];
$cutVert = CUT_VERT_NONE;
$cutVal = $bestVal;
$cutList = [];
for($hCut = 1; $hCut < 1 + floor($high/2); $hCut++){
$result1 = getBestCut($hCut, $wide, $matrix);
$cutVal1 = $result1[0];
$cutList1 = $result1[1];
$result2 = getBestCut($high - $hCut, $wide, $matrix);
$cutVal2 = $result2[0];
$cutList2 = $result2[1];
$cutVal = $cutVal1 + $cutVal2;
if($cutVal > $bestVal){
$cutList = [$cutList1, $cutList2];
$bestVal = $cutVal;
$cutVert = CUT_VERT_FALSE;
$bestH = $hCut;
}
$checked[$hCut][$wide] = $result1;
$checked[$high - $hCut][$wide] = $result2;
}
for($vCut = 1; $vCut < 1 + floor($wide/2); $vCut++){
$result1 = getBestCut($hCut, $vCut, $matrix);
$cutVal1 = $result1[0];
$cutList1 = $result1[1];
$result2 = getBestCut($high, $wide - $vCut, $matrix);
$cutVal2 = $result2[0];
$cutList2 = $result2[1];
$cutVal = $cutVal1 + $cutVal2;
if($cutVal > $bestVal){
$cutList = [$cutList1, $cutList2];
$bestVal = $cutVal;
$cutVert = CUT_VERT_TRUE;
$bestH = $vCut;
}
$checked[$hCut][$vCut] = $result1;
$checked[$high][$wide - $vCut] = $result2;
}
if($cutVert == CUT_VERT_NONE){
$result = [$bestVal, [$high, $wide]];
}else if($cutVert == CUT_VERT_TRUE){
$result = [$bestVal - CUT_COST, $cutList];
}else{
$result = [$bestVal - CUT_COST, $cutList];
}
return $result;
}
Please tell me are they correct implementation of this method?
I wonder if time complexity is O(m^2*n^2) in top-down method?

How do I get HSV values of an average pixel of an image?

In this code
im = Vips::Image.new_from_file "some.jpg"
r = (im * [1,0,0]).avg
g = (im * [0,1,0]).avg
b = (im * [0,0,1]).avg
p [r,g,b] # => [57.1024, 53.818933333333334, 51.9258]
p Vips::Image.sRGB2HSV [r,g,b]
the last line throws
/ruby-vips-1.0.3/lib/vips/argument.rb:154:in `set_property': invalid argument Array (expect #<Class:0x007fbd7c923600>) (ArgumentError)`
P.S.: temporary took and refactored the ChunkyPNG implementation:
def to_hsv r, g, b
r, g, b = [r, g, b].map{ |component| component.fdiv 255 }
min, max = [r, g, b].minmax
chroma = max - min
[
60.0 * ( chroma.zero? ? 0 : case max
when r ; (g - b) / chroma
when g ; (b - r) / chroma + 2
when b ; (r - g) / chroma + 4
else 0
end % 6 ),
chroma / max,
max,
]
end
Pixel averaging should really be in a linear colorspace. XYZ is an easy one, but scRGB would work well too. Once you have a 1x1 pixel image, convert to HSV and read out the value.
#!/usr/bin/ruby
require 'vips'
im = Vips::Image.new_from_file ARGV[0]
# xyz colourspace is linear, ie. the value is each channel is proportional to
# the number of photons of that frequency
im = im.colourspace "xyz"
# 'shrink' is a fast box filter, so each output pixel is the simple average of
# the corresponding input pixels ... this will shrink the whole image to a
# single pixel
im = im.shrink im.width, im.height
# now convert the one pixel image to hsv and read out the values
im = im.colourspace "hsv"
h, s, v = im.getpoint 0, 0
puts "h = #{h}"
puts "s = #{s}"
puts "v = #{v}"
I wouldn't use HSV myself, LCh is generally much better.
https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC
For LCh, just change the end to:
im = im.colourspace "lch"
l, c, h = im.getpoint 0, 0
I realised, that it is obviously wrong to calculate average Hue as arithmetic average, so I solved it by adding vectors of length equal to Saturation. But I didn't find how to iterate over pixels in vips so I used a crutch of chunky_png:
require "vips"
require "chunky_png"
def get_average_hsv_by_filename filename
im = Vips::Image.new filename
im.write_to_file "temp.png"
y, x = 0, 0
ChunkyPNG::Canvas.from_file("temp.png").to_rgba_stream.unpack("N*").each do |rgba|
h, s, v = ChunkyPNG::Color.to_hsv(rgba)
a = h * Math::PI / 180
y += Math::sin(a) * s
x += Math::cos(a) * s
end
h = Math::atan2(y, x) / Math::PI * 180
_, s, v = im.colourspace("hsv").bandsplit.map(&:avg)
[h, s, v]
end
For large images I used .resize that seems to inflict only up to ~2% error when resizing down to 10000 square pixels area with default kernel.

K-means for image compression only gives black-and-white result

I'm doing this exercise by Andrew NG about using k-means to reduce the number of colors in an image. But the problem is my code only gives a black-and-white image :( . I have checked every step in the algorithm but it still won't give the correct result. Please help me, thank you very much
Here is the link of the exercise, and here is the dataset.
The correct result is given in the link of the exercise. And here is my black-and-white image:
Here is my code:
function [] = KMeans()
Image = double(imread('bird_small.tiff'));
[rows,cols, RGB] = size(Image);
Points = reshape(Image,rows * cols, RGB);
K = 16;
Centroids = zeros(K,RGB);
s = RandStream('mt19937ar','Seed',0);
% Initialization :
% Pick out K random colours and make sure they are all different
% from each other! This prevents the situation where two of the means
% are assigned to the exact same colour, therefore we don't have to
% worry about division by zero in the E-step
% However, if K = 16 for example, and there are only 15 colours in the
% image, then this while loop will never exit!!! This needs to be
% addressed in the future :(
% TODO : Vectorize this part!
done = false;
while done == false
RowIndex = randperm(s,rows);
ColIndex = randperm(s,cols);
RowIndex = RowIndex(1:K);
ColIndex = ColIndex(1:K);
for i = 1 : K
for j = 1 : RGB
Centroids(i,j) = Image(RowIndex(i),ColIndex(i),j);
end
end
Centroids = sort(Centroids,2);
Centroids = unique(Centroids,'rows');
if size(Centroids,1) == K
done = true;
end
end;
% imshow(imread('bird_small.tiff'))
%
% for i = 1 : K
% hold on;
% plot(RowIndex(i),ColIndex(i),'r+','MarkerSize',50)
% end
eps = 0.01; % Epsilon
IterNum = 0;
while 1
% E-step: Estimate membership given parameters
% Membership: The centroid that each colour is assigned to
% Parameters: Location of centroids
Dist = pdist2(Points,Centroids,'euclidean');
[~, WhichCentroid] = min(Dist,[],2);
% M-step: Estimate parameters given membership
% Membership: The centroid that each colour is assigned to
% Parameters: Location of centroids
% TODO: Vectorize this part!
OldCentroids = Centroids;
for i = 1 : K
PointsInCentroid = Points((find(WhichCentroid == i))',:);
NumOfPoints = size(PointsInCentroid,1);
% Note that NumOfPoints is never equal to 0, as a result of
% the initialization. Or .... ???????
if NumOfPoints ~= 0
Centroids(i,:) = sum(PointsInCentroid , 1) / NumOfPoints ;
end
end
% Check for convergence: Here we use the L2 distance
IterNum = IterNum + 1;
Margins = sqrt(sum((Centroids - OldCentroids).^2, 2));
if sum(Margins > eps) == 0
break;
end
end
IterNum;
Centroids ;
% Load the larger image
[LargerImage,ColorMap] = imread('bird_large.tiff');
LargerImage = double(LargerImage);
[largeRows,largeCols,~] = size(LargerImage); % RGB is always 3
% Dist = zeros(size(Centroids,1),RGB);
% TODO: Vectorize this part!
% Replace each of the pixel with the nearest centroid
for i = 1 : largeRows
for j = 1 : largeCols
Dist = pdist2(Centroids,reshape(LargerImage(i,j,:),1,RGB),'euclidean');
[~,WhichCentroid] = min(Dist);
LargerImage(i,j,:) = Centroids(WhichCentroid);
end
end
% Display new image
imshow(uint8(round(LargerImage)),ColorMap)
imwrite(uint8(round(LargerImage)), 'D:\Hoctap\bird_kmeans.tiff');
You're indexing into Centroids with a single linear index.
Centroids(WhichCentroid)
This is going to return a single value (specifically the red value for that centroid). When you assign this to LargerImage(i,j,:), it will assign all RGB channels the same value resulting in a grayscale image.
You likely want to grab all columns of the selected centroid to provide an array of red, green, and blue values that you want to assign to LargerImage(i,j,:). You can do by using a colon : to specify all columns of Centroids which belong to the row indicated by WhichCentroid.
LargerImage(i,j,:) = Centroids(WhichCentroid,:);

IMAGEMAGICK: Merge multiple images but keep zones with a certain color

I recently had to merge images like these:
These images represents different sorts of events happening at different locations. The idea is to merge these images in a way to keep the "hot" zones of each images (red-yellow-green) to get a global picture of what happens globally.
In my current approach, I take take the second image and extracts the red/green channel, in order to form a mask of the relevant parts, like this:
Then I merge it with the first image by using this mask so only the relevant parts gets copied over.
Here is the script used for this:
#!/bin/bash
# Extract RGB
convert b.png -colorspace RGB -separate b-sep-%d.png
# Keep red & green only
convert b-sep-2.png b-sep-0.png -compose minus -composite b-tmp-br.png
convert b-sep-2.png b-sep-1.png -compose minus -composite b-tmp-bg.png
convert b-tmp-br.png b-tmp-bg.png -compose plus -composite -level 10%,100% b-mask.png
# Composite!
composite b.png a.png b-mask.png final.png
Here is my current result so far:
As you can see, it works well for the red-yellow-green part, but the blue part is missing. The problem is that if I enlarge the mask to include the blue part, then it'll overwrite red-yellow-green parts from the first image with blue parts from the second one! This is already visible in the final result, the top left first image red part is overwritten by the green part of the second image.
Getting the blue part correctly is trickier, but I think the following algorithm should work (pseudo code):
function merge_pixel(pixel a, pixel b)
{
points = { :red => 4, :yellow => 3, :green => 2, :blue => 1, :default => 0 }
a_points = points[a.color()]
b_points = points[b.color()]
return a_points > b_points ? a : b
}
That is, when merging images, copy the pixel from image a or b depending on which color is the most important for the final image. Maybe this algorithm isn't sound (e.g how to handle the gradient part, maybe with a threshold), feel free to debunk it.
REAL QUESTION:
using imagemagick, how to:
get the desired result using any technique/whatever?
implement the algorithm from above?
You don't need to answer both questions, just finding an imagemagick way of getting the desired result is fine.
[EDIT]
Hint: I just had an idea, I think you can generate the masks (including blue parts) for both images and do some "set intersection/union/difference/whatever" of the masks to generate the appropriate "final" mask so only the real relevant parts of image b is copied over.
Ok, I did the "merge_pixel" strategy and it worked!
require 'RMagick'
include Magick
def pixel_score(p)
r, g, b = [p.red, p.green, p.blue].map{ |i| i / 256 }
is_flat = (r-g).abs < 20 && (r-b).abs < 20 && (g-b).abs < 20
is_grey = is_flat && r < 200
is_red = r >= 240 && g <= 100 # && b < 10
is_yellow = r >= 150 && g >= 100 && b <= 10
is_green = r <= 200 && g >= 200 && b <= 100
is_cyan = r <= 10 && g >= 100 && b >= 30
is_blue = r <= 10 && g <= 100 && b >= 200
if is_red
(999**8) + (r - g)
elsif is_yellow
(999**7) + (r + g)
elsif is_green
(999**6) + (g - b)
elsif is_cyan
(999**5) + (g + b)
elsif is_blue
(999**4) + (b - g)
else
(999**1) + r ** 3 + g ** 2 + b
end
end
def rmagick_merge(file_a, file_b, file_merged)
img_a = ImageList.new(file_a)
img_b = ImageList.new(file_b)
result = Image.new(img_a.columns, img_a.rows)
img_a.columns.times do |col|
img_a.rows.times do |row|
pixel_a = img_a.pixel_color(col, row)
pixel_b = img_b.pixel_color(col, row)
pixel = [pixel_a, pixel_b].sort_by{ |p| pixel_score(p) }.last
#pixel = [pixel_a, pixel_b].sort_by{ |p| [p.red - p.green, p.green, p.blue] }.first
#pixel = [pixel_a, pixel_b].sort_by{ |p| [p.red - p.green - p.blue * 100, p.green, p.blue] }.last
result.pixel_color(col, row, pixel)
end
end
result.format = "PNG"
result.write(file_merged)
end
if __FILE__ == $0
if ARGV.size < 3
puts "usage #{__FILE__} a.png b.png merged.png"
exit 1
end
rmagick_merge(ARGV[0], ARGV[1], ARGV[3])
end
Here is the result (not perfect but fine tuned for my needs on the real pictures):

How to calculate center of gravity in grid?

Given a grid (or table) with x*y cells. Each cell contains a value. Most of these cells have a value of 0, but there may be a "hot spot" somewhere on this grid with a cell that has a high value. The neighbours of this cell then also have a value > 0. As farer away from the hot spot as lower the value in the respective grid cell.
So this hot spot can be seen as the top of a hill, with decreasing values the farer we are away from this hill. At a certain distance the values drop to 0 again.
Now I need to determine the cell within the grid that represents the grid's center of gravity. In the simple example above this centroid would simply be the one cell with the highest value. However it's not always that simple:
the decreasing values of neighbour cells around the hot spot cell may not be equally distributed, or a "side of the hill" may fall down to 0 sooner than another side.
there is another hot spot/hill with values > 0 elsewehere within the grid.
I could think that this is kind of a typical problem. Unfortunately I am no math expert so I don't know what to search for (at least I have not found an answer in Google).
Any ideas how can I solve this problem?
Thanks in advance.
You are looking for the "weighted mean" of the cell values. Assuming each cell has a value z(x,y), then you can do the following
zx = sum( z(x, y) ) over all values of y
zy = sum( z(x, y) ) over all values of x
meanX = sum( x * zx(x)) / sum ( zx(x) )
meanY = sum( y * zy(y)) / sum ( zy(y) )
I trust you can convert this into a language of your choice...
Example: if you know Matlab, then the above would be written as follows
zx = sum( Z, 1 ); % sum all the rows
zy = sum( Z, 2 ); % sum all the columns
[ny nx] = size(Z); % find out the dimensions of Z
meanX = sum((1:nx).*zx) / sum(zx);
meanY = sum((1:ny).*zy) / sum(zy);
This would give you the meanX in the range 1 .. nx : if it's right in the middle, the value would be (nx+1)/2. You can obviously scale this to your needs.
EDIT: one more time, in "almost real" code:
// array Z(N, M) contains values on an evenly spaced grid
// assume base 1 arrays
zx = zeros(N);
zy = zeros(M);
// create X profile:
for jj = 1 to M
for ii = 1 to N
zx(jj) = zx(jj) + Z(ii, jj);
next ii
next jj
// create Y profile:
for ii = 1 to N
for jj = 1 to M
zy(ii) = zy(ii) + Z(ii, jj);
next jj
next ii
xsum = 0;
zxsum = 0;
for ii = 1 to N
zxsum += zx(ii);
xsum += ii * zx(ii);
next ii
xmean = xsum / zxsum;
ysum = 0;
zysum = 0;
for jj = 1 to M
zysum += zy(jj);
ysum += jj * zy(ii);
next jj
ymean = ysum / zysum;
This Wikipedia entry may help; the section entitled "A system of particles" is all you need. Just understand that you need to do the calculation once for each dimension, of which you apparently have two.
And here is a complete Scala 2.10 program to generate a grid full of random integers (using dimensions specified on the command line) and find the center of gravity (where rows and columns are numbered starting at 1):
object Ctr extends App {
val Array( nRows, nCols ) = args map (_.toInt)
val grid = Array.fill( nRows, nCols )( util.Random.nextInt(10) )
grid foreach ( row => println( row mkString "," ) )
val sum = grid.map(_.sum).sum
val xCtr = ( ( for ( i <- 0 until nRows; j <- 0 until nCols )
yield (j+1) * grid(i)(j) ).sum :Float ) / sum
val yCtr = ( ( for ( i <- 0 until nRows; j <- 0 until nCols )
yield (i+1) * grid(i)(j) ).sum :Float ) / sum
println( s"Center is ( $xCtr, $yCtr )" )
}
You could def a function to keep the calculations DRYer, but I wanted to keep it as obvious as possible. Anyway, here we run it a couple of times:
$ scala Ctr 3 3
4,1,9
3,5,1
9,5,0
Center is ( 1.8378378, 2.0 )
$ scala Ctr 6 9
5,1,1,0,0,4,5,4,6
9,1,0,7,2,7,5,6,7
1,2,6,6,1,8,2,4,6
1,3,9,8,2,9,3,6,7
0,7,1,7,6,6,2,6,1
3,9,6,4,3,2,5,7,1
Center is ( 5.2956524, 3.626087 )

Resources