image tile registrations - computer-vision

I'm trying to create a mapping for tile/pixel positions in one tiled image dataset to tile/pixel position in a different dataset (that roughly cover the same image area). Due to rotations of the imaged area, panning axis, translations, and scale changes, there are many unknowns to account for in this mapping. This situation is illustrated below, where the rectangles represent the imaged area for each tile, and the layout of the tiles is governed by the relationship between the panning axis (black lines) and the camera axis:
The problem reduces down to the following linear system which takes into account the scanning axis being rotated with respect to the camera axis differently in the two datasets, plus rotation,scale,and translations between the imaged areas in the two datasets. Unfortunately I'm not sure how to go about solving for the unknowns:
|tx ty|X|a b|+|px py|X|e f|+|i j|=|tx* ty*|X|k l|+|px* py*|
|c d| |g h| |m n|
The unknowns in this system are (a,b,c,d,e,f,g,h,i,j,k,l,m,n) and basically describe a mapping from tile and pixel positions in dataset1 (tx,ty,px,py) to tile and pixel positions in dataset2(tx*,ty*,px*,py*). Coming up with 14 (or more) such correspondences isn't too big a deal for me, as I have a log-polar image registration that works pretty well for affine mapping a tile from dataset1 to a tile in dataset2 if they contain sufficient image overlap. The problem is I'd rather get the above mapping determined rather than doing this registration between every single tile in dataset1 and 2 would take forever!!.
Any help greatly appreciated!
-Craig
Edit:
I just figured out I can rearrange the problem like so (I think?):
|tx ty px py 1|X|a b|=|tx* ty* px* py* 1|X|k l|
|c d| |m n|
|e f| |1 0|
|g h| |0 1|
|i j| |1 1|
This is looking closer to what I need, but I still need help trying to figure out how to convert the answer I'd get back into a form I could use (most importantly I think I'd need to explicitly know (k,l,m,n) to convert the mapped coordinate back into a tile and pixel position in the target.... no?
Edit2: Changed the notation to be correct for matrix multiplication
Edit3: Added illustration images since I now can!

OK, I've long since figured out the problem, but am posting here for documentation.
|tx ty px py 1|X|a b|=|tx* ty* px* py* 1|X|k l|
|c d| |m n|
|e f| |1 0|
|g h| |0 1|
|i j| |1 1|
can be rewritten conveniently as:
|tx ty px py| X A X B + T = |tx* ty* px* py*| X C
where the matrices A and C describe the transform from the slide coordinate system to a global |x y| coordinate system, and 'B + T' is an affine transform |x y| -> |x* y*|
rewritten again:
|tx ty px py 1 tx* ty*| X |A*| = |px* py*|
|B |
|T |
|C*|
shows that
|B| == |e f|
|T| |g h|
|i j|
A* X inv(B) = |a b|
|c d|
-C* = |k l|
|m n|
To determine the linear least-squares solution to the problem from correspondences:
points on slide 1
tp1 = [[tilex,tiley,pixelx,pixely],
...,]
to corresponding points on slide 2
tp2 = similar as above
def findABC(tp1,tp2):
dp1 = np.hstack([tp1,np.ones([len(tp1),1]),tp2[:,0:2]])
dp2 = tp2[:,2:4]
E,_,rank,_ = np.linalg.lstsq(dp1,dp2)
if rank == 7:
B = E[2:5]
A = np.dot(E[0:2,0:2],np.linalg.inv(E[2:4,0:2]))
C = -E[5:7]
return A,B,C
def SlideToGlobal(X,tp):
return np.dot(tp,X)
def GlobalToSlide(X,p):
tptp = np.dot(p,n.linalg.inv(X[0:2,0:2]))
tpoints = np.floor(tptp)
ppoints = tptp-np.dot(tpoints,X[0:2,0:2])
tp = np.hstack([tpoints,ppoints])
return tp

Related

Pyspark Mean value of each element in multiple lists

I have a df with 2 columns:
id
vector
This is a sample of how it looks:
+--------------------+----------+
| vector| id|
+--------------------+----------+
|[8.32,3.22,5.34,6.5]|1046091128|
|[8.52,3.34,5.31,6.3]|1046091128|
|[8.44,3.62,5.54,6.4]|1046091128|
|[8.31,3.12,5.21,6.1]|1046091128|
+--------------------+----------+
I want to groupBy appid and take the mean of each element of the vectors. So for example the first value in the aggregated list will be (8.32+8.52+8.44+8.31)/4 and so on.
Any help is appreciated.
This assumes that you know the length of the array column:
l = 4 #size of array column
df1 = df.select("id",*[F.col("vector")[i] for i in range(l)])
out = df1.groupby("id").agg(F.array([F.mean(i)
for i in df1.columns[1:]]).alias("vector"))
out.show(truncate=False)
+----------+----------------------------------------+
|id |vector |
+----------+----------------------------------------+
|1046091128|[8.3975, 3.325, 5.35, 6.325000000000001]|
+----------+----------------------------------------
You can use posexplode function and then aggregate the column based upon average. Something like below -
from pyspark.sql.functions import *
from pyspark.sql.types import *
data = [([8.32,3.22,5.34,6.5], 1046091128 ), ([8.52,3.34,5.31,6.3], 1046091128), ([8.44,3.62,5.54,6.4], 1046091128), ([8.31,3.12,5.21,6.1], 1046091128)]
schema = StructType([ StructField("vector", ArrayType(FloatType())), StructField("id", IntegerType()) ])
df = spark.createDataFrame(data=data,schema=schema)
df.select("id", posexplode("vector")).groupBy("id").pivot("pos").agg(avg("col")).show()
Output would look somewhat like :
+----------+-----------------+------------------+-----------------+-----------------+
| id| 0| 1| 2| 3|
+----------+-----------------+------------------+-----------------+-----------------+
|1046091128|8.397500038146973|3.3249999284744263|5.350000023841858|6.325000047683716|
+----------+-----------------+------------------+-----------------+-----------------+
You can rename the columns later if required.
Could also avoid pivot by grouping by id and pos and then later grouping by id alone to collect_list
df.select("id", posexplode("vector")).groupby('id','pos').agg(avg('col').alias('vector')).groupby('id').agg(collect_list('vector').alias('vector')).show(truncate=False)
Outcome
+----------+-----------------------------------------------------------------------------+
|id |vector |
+----------+-----------------------------------------------------------------------------+
|1046091128|[8.397500038146973, 5.350000023841858, 3.3249999284744263, 6.325000047683716]|
+----------+-----------------------------------------------------------------------------+

How to repeatedly insert arguments from a list into a function until the list is empty?

Using R, I am working with simulating the outcome from an experiment where participants choose between two options (A or B) defined by their outcomes (x) and probabilities of winning the outcome (p). I have a function "f" that collects its arguments in a matrix with the columns "x" (outcome) and "p" (probability):
f <- function(x, p) {
t <- matrix(c(x,p), ncol=2)
colnames(t) <- c("x", "p")
t
}
I want to use this function to compile a big list of all the trials in the experiment. One way to do this is:
t1 <- list(1A=f(x=c(10), p=c(0.8)),
1B=f(x=c(5), p=c(1)))
t2 <- list(2A=f(x=c(11), p=c(0.8)),
2B=f(x=c(7), p=c(1)))
.
.
.
tn <- list(nA=f(x=c(3), p=c(0.8)),
nB=f(x=c(2), p=c(1)))
Big_list <- list(t1=t1, t2=t2, ... tn=tn)
rm(t1, t2, ... tn)
However, I have very many trials, which may change in future simulations, why repeating myself in this way is intractable. I have my trials in an excel document with the following structure:
| Option | x | p |
|---- |------| -----|
| A | 10 | 0.8 |
| B | 7 | 1 |
| A | 9 | 0.8 |
| B | 5 | 1 |
|... |...| ...|
I am trying to do some kind of loop which takes "x" and "p" from each "A" and "B" and inserts them into the function f, while skipping two rows ahead after each iteration (so that each option is only inserted once). This way, I want to get a set of lists t1 to tn while not having to hardcode everything. This is my best (but still not very good) attempt to explain it in pseudocode:
TRIALS <- read.excel(file_with_trials)
for n=1 to n=(nrows(TRIALS)-1) {
t(*PRINT 'n' HERE*) <- list(
(*PRINT 'n' HERE*)A=
f(x=c(*INSERT COLUMN 1, ROW n FROM "TRIALS"*),
p=c(*INSERT COLUMN 2, ROW n FROM "TRIALS"*)),
(*PRINT 'Z' HERE*)B=
f(x=c(*INSERT COLUMN 1, ROW n+1 FROM "TRIALS"*),
p=c(*INSERT COLUMN 2, ROW n+1 FROM "TRIALS"*)))
}
Big_list <- list(t1=t1, t2=t2, ... tn=tn)
That is, I want the code to create a numbered set of lists by drawing x and p from each pair of rows until my excel file is empty.
Any help (and feedback on how to improve this question) is greatly appreciated!

pyspark: dataframe from rdd containing list of lists

I am new to Spark (with Python) and couldn't figure this out even after looking through relevant posts.
I have a RDD. Each record of the RDD is a list of lists as below
[[1073914607, 0, -1],[1073914607, 2, 7.88],[1073914607, 0, -1],[1073914607, 4, 40.0]]
[[1074079003, 0, -1],[1074079003, 2, 2.87],[1074079003, 0, -1],[1074079003, 4, 35.2]]
I want to convert the RDD to a dataframe with 3 columns, basically stack all the element lists. The dataframe should look like below.
account_id product_id price
1073914607 0 -1
1073914607 2 7.88
1073914607 0 -1
1073914607 4 40
1074079003 0 -1
1074079003 2 2.87
1074079003 0 -1
1074079003 4 35.2
I have tried my_rdd.toDF(), but it gives me two rows and four columns with each element list in a column. I also tried some solutions suggested in other posts which might be relevant. Since I am pretty new to spark, I got various errors that I could figure out. Please help. Thanks.
Added on 07/28/2021. In the end I did the following to loop through each element and generate a long list and convert it into a dataframe. Probably it is not the most efficient way but it solved my issue.
result_lst=[]
for x in my_rdd.toLocalIterator():
for y in x:
result_lst.append(y)
result_df=spark.createDataFrame(result_lst, ['account_id','product_id','price'])
>>> data = ([[1,2],[1,4]],[[2,5],[2,6]])
>>> df = sc.parallelize(data).toDF(['c1','c2'])
>>> df.show()
+------+------+
| c1| c2|
+------+------+
|[1, 2]|[1, 4]|
|[2, 5]|[2, 6]|
+------+------+
>>> df1 = df.select(df.c1.alias('c3')).union(df.select(df.c2).alias('c3'))
>>> df1.show()
+------+
| c3|
+------+
|[1, 2]|
|[2, 5]|
|[1, 4]|
|[2, 6]|
+------+
>>> df1.select(df1.c3,df1.c3[0],df1.c3[1]).show()
+------+-----+-----+
| c3|c3[0]|c3[1]|
+------+-----+-----+
|[1, 2]| 1| 2|
|[2, 5]| 2| 5|
|[1, 4]| 1| 4|
|[2, 6]| 2| 6|
+------+-----+-----+
I later used another way below to solve the problem without bringing the rdd to Localiterator() and looping through it. I guess this new way is more efficient.
from pyspark.sql.functions import explode
from pyspark.sql import Row
df_exploded=my_rdd.map(lambda x : Row(x)).toDF().withColumn('_1', explode('_1'))
result_df=df_exploded.select([df_exploded._1[i] for i in range(3)]).toDF('account_id','product_id','price')

Select right kernel size for median blur to reduce noise

I am new to image processing. We have a requirement to get circle centers with sub pixel accuracy from an image. I have used median blurring to reduce the noise. A portion of the image is shown below. The steps I followed for getting circle boundaries is given below
Reduced the noise with medianBlur
Applied OTSU thresholding with threshold API
Identified circle boundaries with findContours method.
I get different results when used different kernel size for medianBlur. I selected medianBlur to keep edges. I tried kernel size 3, 5 and 7. Now I am confused to use the right kernel size for medianBlur.
How can I decide the right kernel size?
Is there any scientific approach to decide the right kernel size for medianBlur?
I will give you two suggestions here for how to find the centroids of these disks, you can pick one depending on the level of precision you need.
First of all, using contours is not the best method. Contours depend a lot on which pixels happen to fall within the object on thresholding, noise affects these a lot.
A better method is to find the center of mass (or rather, the first order moments) of the disks. Read Wikipedia to learn more about moments in image analysis. One nice thing about moments is that we can use pixel values as weights, increasing precision.
You can compute the moments of a binary shape from its contours, but you cannot use image intensities in this case. OpenCV has a function cv::moments that computes the moments for the whole image, but I don't know of a function that can do this for each object separately. So instead I'll be using DIPlib for these computations (I'm an author).
Regarding the filtering:
Any well-behaved linear smoothing should not affect the center of mass of the objects, as long as the objects are far enough from the image edge. Being close to the edge will cause the blur to do something different on the side of the object closest to the edge compared to the other sides, introducing a bias.
Any non-linear smoothing filter has the ability to change the center of mass. Please avoid the median filter.
So, I recommend that you use a Gaussian filter, which is the most well-behaved linear smoothing filter.
Method 1: use binary shape's moments:
First I'm going to threshold without any form of blurring.
import diplib as dip
a = dip.ImageRead('/Users/cris/Downloads/Ef8ey.png')
a = a(1) # Use green channel only, simple way to convert to gray scale
_, t = dip.Threshold(a)
b = a<t
m = dip.Label(b)
msr = dip.MeasurementTool.Measure(m, None, ['Center'])
print(msr)
This outputs
| Center |
- | ----------------------- |
| dim0 | dim1 |
| (px) | (px) |
- | ---------- | ---------- |
1 | 18.68 | 9.234 |
2 | 68.00 | 14.26 |
3 | 19.49 | 48.22 |
4 | 59.68 | 52.42 |
We can now apply a smoothing to the input image a and compute again:
a = dip.Gauss(a,2)
_, t = dip.Threshold(a)
b = a<t
m = dip.Label(b)
msr = dip.MeasurementTool.Measure(m, None, ['Center'])
print(msr)
| Center |
- | ----------------------- |
| dim0 | dim1 |
| (px) | (px) |
- | ---------- | ---------- |
1 | 18.82 | 9.177 |
2 | 67.74 | 14.27 |
3 | 19.51 | 47.95 |
4 | 59.89 | 52.39 |
You can see there's some small change in the centroids.
Method 2: use gray scale moments:
Here we use the error function to apply a pseudo-threshold to the image. What this does is set object pixels to 1 and background pixels to 0, but pixels around the edges retain some intermediate value. Some people refer to this as a "fuzzy thresholding". These two images show the normal ("hard") threshold, and the error function clip ("fuzzy threshold"):
By using this fuzzy threshold, we retain more information about the exact (sub-pixel) location of the edges, which we can use when computing the first order moments.
import diplib as dip
a = dip.ImageRead('/Users/cris/Downloads/Ef8ey.png')
a = a(1) # Use green channel only, simple way to convert to gray scale
_, t = dip.Threshold(a)
c = dip.ContrastStretch(-dip.ErfClip(a, t, 30))
m = dip.Label(a<t)
m = dip.GrowRegions(m, None, -2, 2)
msr = dip.MeasurementTool.Measure(m, c, ['Gravity'])
print(msr)
This outputs
| Gravity |
- | ----------------------- |
| dim0 | dim1 |
| (px) | (px) |
- | ---------- | ---------- |
1 | 18.75 | 9.138 |
2 | 67.89 | 14.22 |
3 | 19.50 | 48.02 |
4 | 59.79 | 52.38 |
We can now apply a smoothing to the input image a and compute again:
a = dip.Gauss(a,2)
_, t = dip.Threshold(a)
c = dip.ContrastStretch(-dip.ErfClip(a, t, 30))
m = dip.Label(a<t)
m = dip.GrowRegions(m, None, -2, 2)
msr = dip.MeasurementTool.Measure(m, c, ['Gravity'])
print(msr)
| Gravity |
- | ----------------------- |
| dim0 | dim1 |
| (px) | (px) |
- | ---------- | ---------- |
1 | 18.76 | 9.094 |
2 | 67.87 | 14.19 |
3 | 19.50 | 48.00 |
4 | 59.81 | 52.39 |
You can see the differences are smaller this time, because the measurement is more precise.
In the binary case, the differences in centroids with and without smoothing are:
array([[ 0.14768417, -0.05677508],
[-0.256 , 0.01668085],
[ 0.02071882, -0.27547569],
[ 0.2137167 , -0.03472741]])
In the gray-scale case, the differences are:
array([[ 0.01277204, -0.04444567],
[-0.02842993, -0.0276569 ],
[-0.00023144, -0.01711335],
[ 0.01776011, 0.01123299]])
If the centroid measurement is given in µm rather than px, it is because your image file contains pixel size information. The measurement function will use this to give you real-world measurements (the centroid coordinate is w.r.t. the top-left pixel). If you do not desire this, you can reset the image's pixel size:
a.SetPixelSize(1)
The two methods in C++
This is a translation to C++ of the code above, including a display step to double-check that the thresholding produced the right result:
#include "diplib.h"
#include "dipviewer.h"
#include "diplib/simple_file_io.h"
#include "diplib/linear.h" // for dip::Gauss()
#include "diplib/segmentation.h" // for dip::Threshold()
#include "diplib/regions.h" // for dip::Label()
#include "diplib/measurement.h"
#include "diplib/mapping.h" // for dip::ContrastStretch() and dip::ErfClip()
int main() {
auto a = dip::ImageRead("/Users/cris/Downloads/Ef8ey.png");
a = a[1]; // Use green channel only, simple way to convert to gray scale
dip::Gauss(a, a, {2});
dip::Image b;
double t = dip::Threshold(a, b);
b = a < t; // Or: dip::Invert(b,b);
dip::viewer::Show(a);
dip::viewer::Show(b); // Verify that the segmentation is correct
dip::viewer::Spin();
auto m = dip::Label(b);
dip::MeasurementTool measurementTool;
auto msr = measurementTool.Measure(m, {}, { "Center"});
std::cout << msr << '\n';
auto c = dip::ContrastStretch(-dip::ErfClip(a, t, 30));
dip::GrowRegions(m, {}, m, -2, 2);
msr = measurementTool.Measure(m, c, {"Gravity"});
std::cout << msr << '\n';
// Iterate through the measurement structure:
auto it = msr["Gravity"].FirstObject();
do {
std::cout << "Centroid coordinates = " << it[0] << ", " << it[1] << '\n';
} while(++it);
}

How to flip the texture on a CC3PlaneNode?

I'm creating a CC3PlaneNode (cocos3d) with code that looks something like this:
CC3PlaneNode *bnode = [CC3PlaneNode nodeWithName: name];
CC3Texture *texture = [CC3Texture textureFromFile: texName];
[bnode populateAsCenteredRectangleWithSize: sz
andTessellation: ccg(1, 1)
withTexture: texture
invertTexture: YES];
bnode.material.specularColor = kCCC4FLightGray;
bnode.material.emissionColor = kCCC4FWhite;
bnode.material.isOpaque = NO;
bnode.shouldCullBackFaces = NO;
bnode.isTouchEnabled = YES;
bnode.location = loc;
[bnode retainVertexLocations];
Under certain circumstances, I'd like the plane to display the mirror image of its texture (a la the Flip Horizontal feature on many image programs.) I don't just want to flip the plane, itself, because that would throw out-of-position all of it's child-nodes, which is undesirable behaviour.
That is, if X & Y are parts of my texture, and "c" and "c'" are child-nodes in this diagram:
+--------+
| |
| x y |
| |
+--------+
c c'
after the flip, I want things to look like this:
+--------+
| |
| y x |
| |
+--------+
c c'
Other than not wanting to rotate/spin/flip the plane, itself, I'm otherwise pretty flexible on a solution.
Any hints?
Thanks!