Die Diskussionen um die halbautomatische Abseitstechnologie nach dem Abseitstor gestern irritieren mich.
Viele sind fest der Meinung, dass das System fehlerfrei arbeitet. Und es deshalb klar ein Abseits war.
Das stimmt einfach nicht. Jedes Messsystem hat einen systematischen und einen statistischen Fehler. Deswegen werden die Systeme von der FIFA zertifiziert und im Stadion regelmäßig kalibriert.
Das Ergebnis: Der vom Herstellers angegebene Fehler liegt bei ±12,5cm.
Mehrere Kameras um den Platz machen alle 25ms ein Bild. Ein AI gestütztes System erstellt daraufhin für jeden Spieler ein virtuelles Skelett anhand von 29 Punkten, die es versucht zu erkennen. Da weder die Objektive, Timings, die Analyse noch die zeitliche Auflösung perfekt sind, gibt es immer Abweichungen.
Die FIFA erlaubt bei der räumlichen Messung auf dem Feld ein Fehler von ±2,5cm.
Durch die niedrige Bildfrequenz wird zudem der genaue Zeitpunkt der Ballberührung nicht ausreichend erkannt.
Viel problematischer jedoch: Die Körpererkennung.
Wie gesagt wird ein virtuelles Skelett erzeugt, das die Ausmaße des Körpers der Spielers aber nicht berücksichtigt. Die Länge des Schuhs und die Breite der Glieder wird dabei nur mit durchschnittlichen Werte visualisiert.
Aber auch die Punkte des Skelett sind nicht sehr akkurat. Für die Lizenzierung der FIFA gibt Richtlinien: Die einzelnen, erkannten Körperpunkte dürfen maximal 15cm neben den echten liegen, im Durchschnitt unter 10cm.
Um zertifiziert zu werden, dürfen die Messungen in maximal 5% der Fällen die erlaubten Toleranzen überschreiten.
Die Prüfungsberichte der FIFA sind jedoch eindeutig: In Situationen mit vielen Personen nah aneinander und in der Nähe der Strafräume ist die Zuverlässigkeit deutlich geringer und der Fehler häufig überhalb der Vorgabe.
Die Kameras können einfach nicht viel erkennen in den Situationen. Das Skelett des Spielers kann nicht vollständig rekonstruiert werden.
Der Hersteller Hawk-Eye gibt für seine Abseitserkennung innerhalb der Software deshalb einen Fehler an von ±12,5cm. In diesem Bereich stuft es die Situation als „möglicherweise Abseits“ oder „möglicherweise kein Abseits“ ein.
Das System ist also für Situationen unter 25cm einfach nicht zuverlässig, da mögen die Animationen noch so schön sein. In der Regel gilt dann: Im Zweifel gilt das Tor.
Und das wurde dieses Mal einfach nicht beachtet.
Kurze Anmerkung zur Angabe des Fehlers von Hawk-Eye selbst für Ihre Ballerkennung: Sie nennen einen Fehler von 3,6mm an. Jedoch bezieht sich der Wert auf einen Tennisball (kleiner als ein Fußball) auf einem Tennisplatz (kleiner als ein Fußballplatz) beim Auftreffen auf den Boden (2 Dimensionen statt 3 Dimensionen).
Das offizielle Datenblatt für die Ball- oder Skeletterkennung von Hawk-Eye ist leider nicht öffentlich einsehbar. Deshalb beziehe ich meine Daten von der FIFA, Studien und Artikeln.
from decimal import *
def set(x,y, z=None) :
return x > 0 and x**2 < y and y < (8-x**2) # Eingabe der Menge
ranp = Decimal('9') #Range nach oben
rann = Decimal('-1') #Range nach unten
res = Decimal('0.1') #Auflösung
def r2d() :
vx = [float(rann),float(ranp)]
vy = [float(rann),float(ranp)]
x = rann
y = rann
while x <= ranp :
while y <= ranp :
#print(x,y,set(x,y))
if set(x,y) :
vx.append(float(x))
vy.append(float(y))
y += res
x += res
y = rann
print(
"x=" + str(vx) + ";" +
"y=" + str(vy) + ";" +
'plot(x,y,".");'
) #2D Render
def r3d() :
vx = [float(rann),float(ranp)]
vy = [float(rann),float(ranp)]
vz = [float(rann),float(ranp)]
x = rann
y = rann
z = rann
while x <= ranp :
while y <= ranp :
while z <= ranp :
if set(x,y,z) :
vx.append(float(x))
vy.append(float(y))
vz.append(float(z))
z += res
y += res
z = rann
x += res
y = rann
print(
"x=" + str(vx) + ";" +
"y=" + str(vy) + ";" +
"z=" + str(vz) + ";" +
'plot3(x,y,z,".");'
) #3D Render
r2d()
I’m using an always-on vpn for all my devices. It’s not because of the anonymity, it’s because of security and safety. I’m using an dedicated IP, so I can make sure, where I log in, if I did something and have better control over my network all around.
The only problem I encountered with it: The Amazon Prime App for android. When connected to any VPN or Proxy, even if it is just local (like AdGuard), you can’t use the streaming service.
This means, every time I want to watch scrubs or Malcolm in the Middle, I have to disable not only my vpn, but also the kill switch from the app and android settings.
How to do a walkaround
Sadly I didn’t figured out a way to let the data still go through the vpn. But I found a way to use it comfortably without having to go to the setting: Isolating the App into a different environment, where no vpn is active. For that I use the open source app Shelter, that uses the “Work Profile” feature of Android to create an isolated space.
Install Shelter (I’m not in any way involved with that open source project. Use with caution. Check the documentation for support.)
Go trough the installation.
Clone the apps you want isolate, so Prime Video (you may need to allow the Shelter to install apks)
Open Prime Video from the Shelter app in the Shelter Tab and log in
That should do the trick. To open and close the app, you would need to open Shelter again and freeze the apps. To make it more comfortable:
In the Shelter App click on Prime Video and add a shortcut to your main screen
In the Shelter App click on the burger menu and add a shortcut to freeze all apps to the main screen
In the last round of dungeons and dragons the DM told us a riddle:
The goal ist to fill it out in a way, that in every row and in every column and in every diagonal line no two letters appear twice.
As the nerd I am, I solved the problem with a backtracking algorithm:
Backtracking is a general algorithm for finding all (or some) solutions to some computational problems, notably constraint satisfaction problems, that incrementally builds candidates to the solutions, and abandons a candidate („backtracks“) as soon as it determines that the candidate cannot possibly be completed to a valid solution.
problem = [ ["A" ,"B" ,"C" ,"D" ,"E" ,"F" ],
[None,None,None,None,None,None],
[None,None,False,False,None,None],
[None,None,False,False,None,None],
[None,None,None,None,None,None],
[None,None,None,None,None,None] ]
def isRow(p,r,v) : # Checks, if the value v is in p at row r
for i in p[r] :
if i == v : return True
return False
def isColumn(p,c,v) : # Checks, if the value v is in p at column c
for i in range(6) :
if p[i][c] == v : return True
return False
def isDiagonal(p,r,c,v) : # Checks, if the value v is in p in the diagonals from position r and c
pos = [r-1,c-1] # Check the top left values
while pos[0] >= 0 and pos[1] >= 0 :
if p[pos[0]][pos[1]] == v : return True
pos = [pos[0]-1,pos[1]-1]
pos = [r-1,c+1] # Check the top right values
while pos[0] >= 0 and pos[1] <= 5 :
if p[pos[0]][pos[1]] == v : return True
pos = [pos[0]-1,pos[1]+1]
pos = [r+1,c+1] # Check the bottom right values
while pos[0] <= 5 and pos[1] <= 5 :
if p[pos[0]][pos[1]] == v : return True
pos = [pos[0]+1,pos[1]+1]
pos = [r+1,c-1] # Check the right left values
while pos[0] <= 5 and pos[1] >= 0 :
if p[pos[0]][pos[1]] == v : return True
pos = [pos[0]+1,pos[1]-1]
return False
def res(p,r=0,c=0) :
values = ["A","B","C","D","E","F"]
if r == 5 and c == 5 : # If last Value
if p[r][c] == None : # And it is empty
for v in values : # Check for every value
if not (isRow(p,r,v) + isColumn(p,c,v) + isDiagonal(p,r,c,v)) : # Checks, if v can be placed at (r,c)
p[r][c] = v # Insert value
for j in p : # Does the printing and formatting
str = " "
for k in j :
if k == False : str += " "
else : str += k + " "
print(str)
print()
p[r][c] = None # Resets the position for cleaning
elif p[r][c] == None :
for v in values :
if not (isRow(p,r,v) + isColumn(p,c,v) + isDiagonal(p,r,c,v)) :
p[r][c] = v
if c == 5 : res(p,r+1,0) # If last value in row jump one down
else : res(p,r,c+1) # else next value
p[r][c] = None
else :
if c == 5 : res(p,r+1,0)
else : res(p,r,c+1)
res(problem)
Result:
A B C D E F
C F E B A D
E A F B
B D C E
F C B E D A
D E A F B C
A B C D E F
C F E B A D
E A F B
B D C E
F C B E D A
D E F A B C
A B C D E F
D E A F B C
B C D A
F D C E
C A E B F D
E F D C A B
A B C D E F
D E A F B C
B C D E
F D C A
C A E B F D
E F D C A B
A B C D E F
D E A F B C
F C D A
B D C E
C A B E F D
E F D C A B
A B C D E F
D E A F B C
F C D A
B D C E
C A E B F D
E F D C A B
A B C D E F
D E A F B C
F C D E
B D C A
C A E B F D
E F D C A B
A B C D E F
D E F A B C
F C D A
B D C E
C A B E F D
E F D C A B
A B C D E F
D E F A B C
F C D A
B D C E
C A E B F D
E F D C A B
A B C D E F
D E F B A C
F C D B
B A C E
C D B E F A
E F A C B D
A B C D E F
D E F B C A
F C D B
B D A E
C A B E F D
E F D A B C
A B C D E F
D F E A B C
E C D A
B D F E
F A B E C D
C E D F A B
A B C D E F
D F E B A C
E A D B
B C F E
F D B E C A
C E A F B D
A B C D E F
D F E B A C
E A F B
B C D E
F D B E C A
C E A F B D
A B C D E F
D F E B A C
E A F B
B C D E
F D B E C A
C E F A B D
A B C D E F
D F E B A C
E C D B
B A F E
F D B E C A
C E A F B D
A B C D E F
D F E B A C
E C D B
B D F A
F E B A C D
C A D F B E
A B C D E F
D F E B A C
E C D B
F A C E
C D F E B A
B E A C F D
A B C D E F
D F E B A C
E C D B
F D C A
C E F A B D
B A D C F E
A B C D E F
D F E B A C
E C F B
B A D E
F D B E C A
C E A F B D
A B C D E F
D F E B C A
E C D B
F D A E
C A F E B D
B E D A F C
A B C D E F
F D E A B C
E C D A
B F C E
C A B E F D
D E F C A B
A B C D E F
F D E B A C
E C D B
B F C A
C E B A F D
D A F C B E
A B C D E F
F D E B C A
E C D B
B F A E
C A B E F D
D E F A B C
My client had a conflict with a business partner of her. In her anger she wanted to block her on WhatsApp, so that further conversations have to be formal. But sadly she reported this partner, and if you do that, whatsapp deletes that chat.
What did I tried?
First I tried restoring an old backup with reinstalling WhatsApp and only have that old backup saved. But that didn’t work, WhatsApp just didn’t want to load any backup other than out of google drive.
Thanks to recent changes, on how the google drive backup system works, it isn’t easy/possible to access the backup.
So the only option is to encrypt the local backup. But therefore you need the key. This key is easy accessible – if your phone is rooted. But sadly every other way to access the key without root failed.
How to do it
1. Safe the local backups
Connect the phone to the computer, accept media access and go into the data system.
The backups are stored as /WhatsApp/Databases/msgstore-20XX-XX-XX.1.db.crypt12 or alike, either on the sd card or the internal storage. Better transfer them all to your computer.
The date of the backup is easily readable.
2. Root the phone
To access the key, you need to root your phone. Sadly I can’t help you with that – every phone is different.
But there are a lot of good tutorials out there. Just google for them.
3. Access the key
If the root is successful, go to the play store and download a „root file manager„. Use it to go to the root dictionary of your android. From there just go to /data/data/com.whatsapp/files/key and copy the „key“ file to the sd card or the internal storage.
Go to „Browse Data“ and open the Table „message_view“.
There are them, every message you’ve sent.
The interesting columns are
chat_row_id : Every chat has one id
from_me : Who sent the message
timestamp: When was it sent in UNIX in ms
data: The content of the message
Now you should check the data for chat messages of the lost chat. Now remember the chat_row_id from this message.
Go to execute SQL and run the following code, but change ? for the id you found out.
SELECT from_me, timestamp, data
FROM message_view
WHERE chat_row_id = ?
6. Use the Data
To get to this point I needed 6 hours. So the rest of the trick is pretty dirty – I wanted to finish. The code is terrible, the methods disgusting and the result just okay. Please don’t judge.
Copy the table into a new excel sheet and safe it as messages.csv.
I wrote this code to convert the .csv to a stylized .html document. The code has a lot of bugs, is pretty bad code and isn’t commented, but it somehow did work.
puts "Start!"
mes = File.read("messages.csv").force_encoding('iso-8859-1')
pos = 0
res = []
while mes[pos+6] != nil do
me = ( mes[pos] == "1" )
pos += 2
time = Time.at(mes[pos,10].to_i)
pos += 14
cont = ""
while (mes[pos-1,2] != "0;") and (mes[pos-1,2] != "1;") do
cont = cont + mes[pos]
pos += 1
end
pos -= 1
cont = cont.chop.chop
if cont[0] == '"' then
cont = cont[1,cont.size - 5]
end
if cont != "" and cont != nil then
res.push( {:time => time , :me => me, :content => cont} )
end
end
html = ""
res.each do |i|
if i[:me] then
html = '<div class="me"><div class="message">' + i[:content] + '</div><div class="time">' + i[:time].to_s[0,19] + '</div></div>' + html
else
html = '<div class="her"><div class="message">' + i[:content] + '</div><div class="time">' + i[:time].to_s[0,19] + '</div></div>' + html
end
end
pos = 0
while html[pos] != nil do
if html[pos] == "\n"
html[pos] = "<br>"
end
pos += 1
end
html = '<html><head>
<style>
html {
border-top: solid 20px #005e54;
}
body {
background: #efe7dd url("https://cloud.githubusercontent.com/assets/398893/15136779/4e765036-1639-11e6-9201-67e728e86f39.jpg") repeat;
padding: 20px;
padding-top: 5px;
}
.me, .her {
margin: 20px;
padding: 20px 20px 8px 20px;
width: 70%;
max-width: 960px;
box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.2);
}
.me {
background-color: #e1ffc7;
position: relative;
left:20%;
border-radius: 15px 0px 15px 15px;
}
.her {
background-color: #fefefe;
border-radius: 0px 15px 15px 15px;
}
.message {
font-family: "Roboto", sans-serif;
width: 90%;
max-width: 820px;
line-height: 1.2em;
}
.time {
font-family: "Roboto", sans-serif;
color: grey;
font-size: .7em;
margin-top: 8px;
text-align: right;
}
</style>
</head><body>' + html + "</body></html>"
output = File.open("messages.html","w")
output << html.force_encoding('iso-8859-1')
output.close
puts "Ende"
You need ruby to run it, but if someone wants, I can make it to an .exe.
I had a pretty similar situation a few years back with another client. Back then I searched online for a tool, but couldn’t found one. So I said down and programmed it myself. After the one time use I just forgot about it and over the time I lost it somewhere in my files.
So when I got this request by my client, I first tried to find the file, but gave up short after. So back to programming again.
And due to the lack of a easy tool online I decided to publish the tool myself.
This is just the first, unpolished version. I’ll try to optimize it in the next days, add more features and make it a better user experience.
What does it do?
It asks the user to input the filename of a file on the same level the program is in. It loads it into a string, makes all the characters downcase and removes every line break. Then it searches the file with an RegEx, checks, if the address is already known, and then outputs the mail adresses in the terminal and creates the file/adds them to the file extractedmails.txt in a format, that allows you to just copy paste them into the sending form.
What to change?
I want to optimize the extraction progress, add an options menu for changing the output style, adding a filter and choose the output file. Also I want to add multiple language support.
# Mail RegEx
mail = /[a-zA-Z0-9._-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/
# Imports the file as a string and formats it
print("Bitte gib den Dateinamen der Datei an, die ich nach eMails durchsuchen soll: ")
filename = gets.chop
str = File.read(filename)
str = str.downcase
while str.slice!("\n ") != nil
end
# Uses the RegEx to store any mail in an array
m = mail.match(str)
puts("Alle Mails in der Datei:")
a = []
while m!=nil do
if not a.include?(m[0]) then
a.push(m[0])
end
str = m.post_match
m = str.match(mail)
end
# Formats the array into a usable string
res = ""
a.each do |i|
res = i + ", " + res
end
puts res = res.chop.chop
# Creates an output file
output = File.open( "extractedmails.txt","w" )
output << res
output.close
print "Finished"
gets
I know, I waste far to much of my time on youtube watching random videos. But out of luck I sometimes find inspiring stuff. Like this video by standupmaths:
And so I got on the journey to waist even more of my time.
How does it work?
The system of the approximation works pretty simple:
The probability, that two random numbers are coprime (Greatest common divisor is 1) to each other, is
The possibility x is based on random numbers between 1 and inf. If we limit the maximum random number we decrease the value of pi. So we need to maximize the max random number, what increases the calculation time. So we need a fast coprime algorithm.
The code
My first try is based on the euclidean algorithm.
def coprime?(x,y) # Finds out if two numbers are coprime (Euclidean algorithm)
if y == 0 then
return x == 1
else
return coprime?(y, x % y)
end
end
def pi(time,height) # Approximate Pi out of random numbers
truestate = 0.0
time.times do
if coprime?(rand(1..height),rand(1..height)) then truestate += 1 end
end
return Math.sqrt(6 /(truestate / time))
end
t = Time.new
p pi(100000000,1000000)
p Time.new - t
On my PC the calculation time of this is around 66 seconds. The value for pi changes in each runthrough, but its still pretty accurate.
The optimized version
Thanks to reddit I found a few changes, that improve the runtime by cutting in half:
def pi(time,height) # Calculates Pi out of random numbers
truestate = 0
range = 1..height
i = 0
while i < time
truestate += 1 if rand(range).gcd(rand(range)) == 1
i += 1
end
Math.sqrt(6 / (truestate.to_f / time))
end
t = Time.new
p pi(100000000,1000000)
p Time.new - t