Gabi und Sascha
Kategorien : Alle | Berlin | Bücher | Fotografie | Java | Linkhalde | Weichware | Verfassung

Ich muss ermitteln wie breit die Abstände vom Inhalt eines Bildes zum Rand sind. Eines ziemlich einfachen Bildes. Dies allerdings nicht für 1 Bild sondern für ein paar tausend Bilder. Bei solchen Aufgaben helfen Photoshop und Gimp nur bedingt weiter. Aber zum Glück gibt es das Schweizer Taschenmesser für Grafikbearbeitung: imagemagick.

Die Bilder sehen alle ungefähr wie das Folgende aus:


640 % 400

Allerdings habe ich bei imagemagick auch keine Bordmittel gefunden wie die Rahmenbreite ermittelt werden kann. Deswegen musste ich einen Umweg gehen.

Zuerst wird das Bild so zugeschnitten, dass nur der innere Bereich erhaten bleibt. Mittels des -trim Kommandos kann der Rahmen weggeschnitten werden. Dabei schneidet imagemagick alles weg was nicht den Farben der 4 Eckpunkten entspricht.


    convert "two black elipses.png" -trim "two black elipses (trimmed).png"

Das Ergebnis sieht so aus:


299 x 177

Mit dem Wissen um den -trim Befehl kann jetzt die Breite des linken Randes bestimmt werden. Damit der rechte Rand nicht ebenfalls weggeschnitten wird, wird in das Originalbild genommen und dort ein schwarzer Strich gezeichnet, gefolgt von einem weiteren weissen Strich. Das neue Bild wird unter einem eigenen Namen abgelegt:


    convert "two black elipses.png" \
            -gravity East           \
            -background black       \
            -splice 1x0             \
            -background white       \
            -splice 1x0             \
            "two black elipses (helper).png"

    convert "two black elipses (helper).png" \
            -trim "two black elipses (helper, trimmed).png"

Von dem trimmed Bild muss jetzt die Breite ermittelt werden. Für spätere Berechnungen wird der Wert gleich in einer Variablen gespeichert:


    W=$(convert "two black elipses (helper, trimmed).png" -format "%w" info: )

Um die Breite des linken Rahmens zu berechnen, muss noch die Breite des Originalbildes ermittelt werden. Auch dieser Wert wird gleich in einer Variable gespeichert. Ebenfalls benötigt wird noch die Breite des trimmed original Bildes.


    TOTAL_WIDTH=$(convert "two black elipses.png" -format "%w" info: )
    TRIMMED_WIDTH=$(convert "two black elipses (trimmed).png" -format "%w" info: )

Mittels Subtraktion kann jetzt die jeweilige Rahmenbreite Links und Rechts berechnet werden:


    BORDER_LEFT_WIDTH=$(expr ${TOTAL_WIDTH) - ${W})
    BORDER_RIGHT_WIDTH=$(expr ${TOTAL_WIDTH} - ${BORDER_RIGHT_WIDTH} - ${TRIMMED_WIDTH})

Die Berechnung der oberen und unteren Rahmenhöhe ist ensprechend:


    TRIMMED_HEIGHT=$(convert "two black elipses (trimmed).png" -format "%h" info: )

    convert "two black elipses.png" \
            -gravity South          \
            -background black       \
            -splice 0x1             \
            -background white       \
            -splice 0x1             \
            "two black elipses (helper).png"

    convert "two black elipses (helper).png" \
            -trim "two black elipses (helper, trimmed).png"

    H=$(convert "two black elipses (helper, trimmed).png" -format "%h" info: )
    TOTAL_HEIGHT=$(convert "two black elipses.png" -format "%h" info: )
    BORDER_TOP_HEIGHT=$(expr ${TOTAL_HEIGHT} - ${H})
    BORDER_BOTTOM_HEIGHT=$(expr ${TOTAL_HEIGHT} - ${BORDER_TOP_HEIGHT} - ${TRIMMED_HEIGHT})

Ergebnis:

border top height 119
border bottom height 104
border left width 167
border right width 174

Download Bash Script der kompletten Umsetzung.

Habe heute einen kurzen R Benchmark gemacht, bei der ein data.frame mit 7,7 Millionen Zeilen erweitert wird. Dabei habe ich einmal den eingebauten within verwendet und ein anderes mal die Funktion dplyr::mutate.

Der within Code:


    logdata <- within(logdata, {
        YEAR  <- as.numeric(format(logdata$timestamp, "%Y"))
        MONTH <- as.numeric(format(logdata$timestamp, "%m"))
        WEEK  <- as.numeric(format(logdata$timestamp, "%V"))
        DOM   <- as.numeric(format(logdata$timestamp, "%d"))
        DOW   <- ifelse((DOW = as.numeric(format(logdata$timestamp, "%w"))) == 0, 7, DOW)
        HOUR  <- as.numeric(format(logdata$timestamp, "%H"))})

Der dplyr::mutate Code:


    logdata <- logdata %>% mutate(
        YEAR  = as.numeric(format(timestamp, "%Y")),
        MONTH = as.numeric(format(timestamp, "%m")),
        WEEK  = as.numeric(format(timestamp, "%V")),
        DOM   = as.numeric(format(timestamp, "%d")),
        DOW   = ifelse((DOW = as.numeric(format(timestamp, "%w"))) == 0, 7, DOW),
        HOUR  = as.numeric(format(timestamp, "%H")))

Durchschnittliches Laufzeitverhalten nach jeweils 10 Durchgängen:

    mutate: 36s
    within: 31s

Bleibe bei dieser Umwandlung erst einmal bei within.


Hardware: MacBook Pro (Retina 13 Zoll, Anfang 2015); 3,1 GHz Intel Core i7; 16 GB 1867 MHz DDR3
OS: macOS 10.11.6
R: version 3.3.1 (2016-06-21) -- "Bug in Your Hair"
dplyr: version 0.5.0

Ich habe eine CSV Datei mit Log Informationen. Die Log Information können durch ein Timestamp geordnet werden. Für dir Auswertung benötige ich allerdings nur Log Informationen vollständiger Wochen (Montag → Sonntag). In der Datei befinden sich auch nicht vollständige Wochen.

Hier nun ein Lösungsweg in R nur die vollständigen Wochen zu ermitteln:

Hinweis: Die CSV Datei wurde in die data.frame Variable log eingelesen. Die Spalte mit dem Zeitstempel heisst timestamp.

    # Umwandeln des ISO 8601 Datums.
    log$timestamp <- as.POSIXct(strptime(log$timestamp, "%Y-%m-%dT%H:%M:%S"))

    # Hilfsfelder einfügen.
    log <- within(log, {YEAR <- as.numeric(format(log$timestamp, "%Y"))
                        MONTH <- as.numeric(format(log$timestamp, "%m"))
                        WEEK <- as.numeric(format(log$timestamp, "%V"))
                        DOW <- ifelse((DOW = as.numeric(format(log$timestamp, "%w"))) == 0, 7, DOW)
                   })

    # Ziel data.frame.
    fullweeks <- data.frame()


    # Durchlaufen der eindeutigen Wochen.
    for (week in unique(log$WEEK)) {

        # Ermitteln der eindeutigen Wochentage der eindeutigen Wochen.
        days <- unique(log$DOW[log$WEEK == week])

        # Enthält Vektor eindeutigen Eintrag für jeden Wochentag?
        if (length(days) == 7) {
            fullweeks <- rbind(fullweeks, log[log$WEEK == week,])
        }
    }

fullweeks enthält nun nur Logeinträge voller Wochen.


Rscript zum selber ausprobieren.

Angenommen ich habe ein R data.frame DF mit den Spalten A, B und C. Ich möchte nun alle Zeilen aus DF deren Spalte B den Wert "Closed" hat und in Kombination mit Zeile A doppelt vorkommt.

Mein Lösungsweg:

  1. Ein data.frame erzeugen, der nur die Spalten A und B enthält:
    sl <- data.frame(A = DF$A, B = DF$B)

  2. Filtern nach Zeilen, die nur B == "Closed" entsprechen:
    slc <- sl[which(sl$B == 'Closed'),]

  3. Im letzten Schritt die doppelten Zeilen ermitteln: slcd <- slc[duplicated(slc),]

Noch so ein R Problem gelöst. Wieder mit dem plyr API.

Ich habe ein data.frame mit den Spalten für Jahr, Monat und Laufzeit. Für jede Jahr-Monats Kombination gibt es eine unterschiedliche Anzahl von Laufzeitwerten. Die Laufzeit der Jahr-Monatskombination möchte ich nun in einem Reihenboxplot visualisiert darstellen.


    year month duration
    2016     6      345
    2016     6      378
    2016     6      501
    2016     6      390
    2016     6      333
    2016     6      321
    2016     5      401
    2016     5      378
    2016     5      390
    2016     5      331
    2016     5      345
    2016     4      411
    2016     4      428
    2016     4      190
    2016     4      231

Für den Boxplot muss ich die Daten allerdings anders organisiert haben. Die Daten für die Laufzeitwerte eines Monats müssen in jeweils einem Vector innerhalb einer Spalte gespeichert werden. Ausserdem benötige ich nicht alle Monate, sondern nur die Daten der beiden letzten Monate (5 und 6).


      5    6
    401  345
    378  378
    390  501
    331  390
    345  333
     NA  321

Um dies zu erreichen erzeuge ich zuerst eine leere Liste:


    li <- list()

Im Anschluss durchlaufe ich die Ursprungsliste in einer Schleife und weise die jeweiligen Laufzeitwerte der neuen Liste l zu:


    for (i in 5:6) {
        li[[i-4]] <- with(dur, dur$duration[dur$month == i])
    }

Die so erzeugte Liste aus Vektoren wandel ich jetzt in einen data.frame um:


    dur.df <- ldply(li, rbind)

Das Ergebnis sieht jetzt so aus:


      1   2   3   4   5   6
    401 378 390 331 345  NA
    345 378 501 390 333 321

MIttels des transpose Befehls t(…) kann die Matrix so angepasst werden, dass aus den Zeilen Spalten werden:


    dur.dft <- t(dur.df)

Damit kann jetzt der Boxplot mittels boxplot(dur.dft)erzeugt werden:

Unsicherheit

Da for Schleifen in R eher selten sind bleibt die Unsicherheit, ob die Lösung die eleganteste ist. Fürs Erste allerdings reicht es.


CSV Datei und Code zum selber ausprobieren.

Ich habe ein kleines Programm geschrieben, um CSV Dateien mit SQL Befehlen von der Kommenadozeile ad-hoc zu untersuchen: sql4csv. Ziel war es ein Query wie das folgende von der Bash Kommandozeile ab zu setzen:


    > scq select distinct author from books.csv

Das Ergebnis wird dann wieder in Form einer CSV Datei ausgegeben:


    author  
    Douglas Adams  
    Terry Pratchet

Bei der Entwicklung habe ich Wert auf einfache Verwendung gelegt. So soll es nicht nötig sein die Werttypen (integer, numeric oder Datumsangaben) der Spalten vor der Benutzung. Vielmehr soll dies das Tools möglichst selbst erkennen und die Spaltentypen entsprechend anpassen. Damit sollen auch Abfragen wie


    > scq select ABC from xyz.csv where VALUE < 10

möglich sind.

Für integer und numeric Typen ist das auch ohne grosse Probleme möglich. Kniffelig ist es bei Datums- und Zeitabgaben. Hier gibt es zig unterschiedliche Formate. Deswegen habe ich mich für einen pragmatischen Weg der Umsetzung entschieden: es werden aktuell die drei für mich wichtigsten Datumsformate für den Gregorianischen Kalender unterstützt :-)

Datums- und Zeitformate

  • Y - steht für das Jahr. Jahre werden immer vierstellig angegeben
  • M - steht für den Monat
  • D - steht für den Tag im Monat
  • h - steht für Stunden
  • m - steht für Minuten
  • s - steht für Sekunden

Datumsangaben

YYYY-MM-TT

ISO 8601 ähnlich.

Unterstützt wird auch eine sogenannte lazy Schreibweise, bei der auf einen führende Null (0) bei Zahlen unter 10 verzichtet werden kann. Dies bedeutet, dass auch ein Datum 2016-9-6 als 6. September 2016 erkannt wird.

MM/DD/YYYY

Amerikanische Datumsschreibweise.

Wie bei der ISO 8601 Schreibweise wird auch hier eine lazy Schreibweise ohne führende Null unterstützt.

DD.MM.YYYY

Entspricht der numerischen DIN 5008.

Wie bei der ISO 8601 Schreibweise wird auch hier eine lazy Schreibweise ohne führende Null unterstützt.

Zeitangaben

Zeiten werden immer mit einem Doppelpunkt getrennt. Dabei unterstützt sql4csv nur die Reihenfolge hh:mm:ss. Dieses Format allerdings auch lazy, also ohne führende Null und die Sekunde kann ebenfalls weggelassen werden. In diesem Fall wird die Sekunde intern auf Null gesetzt..

Zeitstempel

Zeitstempel sind eine Kombination aus Datum und Zeit. sql4csv erlaubt alle Kombinationen von Datums- und Zeitangaben. Dabei kann als Separator das ISO 8601 T verwendet werden oder ein Leerzeichen.

Beispiele

  • 2016-7-3 1:16
  • 8/30/1969T23:5:07
  • 11.11.2011 11:11:11

Implementierung

sql4csv wurde in Java 8 implementiert. Ich habe es auch geschrieben, um die neuen Sprachfeatures von Java 8 in der Praxis zu lernen und anzuwenden. So kommen intensiv Lambdas zum Einsatz und auch Methodenreferenzen. Die gesamte Datums- und Zeitbearbeitung basiert auf dem java.time API.

Als Datenbank verwende ich Derby als in-memory Datenbank. Ich verwende nicht SQLite - wie dies einige andere Implementierungen für ein solches Tool tun - da dies entweder eine vorhandene Installation von SQLite voraussetzt oder ich die Binaries für die unterschiedlichsten Plattformen mitliefern müsste. Das ist zu kompliziert und wird der Einfachheit des Werkzeuges nicht gerecht.

Probleme

SQL Queries und die Bash als Kommandozeileninterpreter vertragen sich nur mässig miteinander. Beispielsweise interpretiert die Bash das Asterisk Zeichen (*) anders als in SQL vorgesehen. Und diese Interpretation kann auch nicht einfach ausgeschaltet werden. Ein Query wie


    > scq select * from books.csv where author = 'Douglas Adams '

ist nicht möglich. Die Bash ersetzt das * Zeichen durch die Dateinamen des aktuellen Verzeichnisses. Als Abhilfe kann das Hilfstool scq-cols verwendet werden:


    > scq select $(scq-cols books.csv) from books.csv where author = 'Douglas Adams'

Das ist nicht schön, aber eine pragmatische Lösung. Nach einigen malen benutzen hatte ich mich daran gewöhnt.

Ebenso müssen runde Klammern (z.B. für Gruppierungen) und spitze Klammern <> für grösser und kleiner Vergleiche durch eine Backslash (\) maskiert werden. Insgesamt kann durch diese Einschränkungen die Lesbarkeit des Queries beeinträchtigt werden.

Download und Installation

Im sql4csv Repository auf Github findet ihr eine Download und Installationsanleitung, um das Tools selbst einmal ausprobieren zu können.

Mit meinem kleinen R Projekt geht es weiter. Ich hatte die folgende Situation: Ich habe die Zeitpunkte mehrere Statusübergänge eines Issues in einer denormalisierten Tabelle. Dabei kommt der Status created pro Issue exakt einmal vor. Zusätzlich habe ich noch die Dauer, die ein Issue in dem jeweiligen Status war. Ich will nun die Dauer und den created Zeitpunkt behalten. Alle anderen Zeitpunkte und auch sämtliche Status benötige ich im weiteren Verlauf nicht mehr.

Die Ausgangstabelle X sieht ungefähr so aus:


      IssueID                Date  Status Duration
            A 2016-08-09T14:33:09 created        0
            A 2016-08-19T09:21:11    open   845282
            A 2016-08-19T16:01:53  closed    24042
            A 2016-08-21T16:06:12    open   172800
            A 2016-08-25T08:13:45  closed   345600
            B 2015-09-23T12:22:21 created        0
            B 2015-09-25T07:11:01    open   172800
            B 2015-10-01T14:05:28  closed   518400

Ziel ist eine Tabelle, in der Duration der einzelnen Issues aufsummiert sind und das Date von created erhalten bleibt als CreationDate. Also in etwa so:


    IssueID,CreationDate,Duration
    A,2016-08-09T14:33:09,1387724
    B,2015-09-23T12:22:21,691200

Stellt sich heraus: das ist mit R nicht trivial zu lösen, aber auch nicht unmöglich.

1. Schritt

Im ersten Schritt erstelle ich ein Subset ausschliesslich dür den Status created:


    created.datetime <- subset(X, Status == "created", select = c("IssueID", "Date"))

2. Schritt

Im zweiten Schritt wird in die Tabelle X eine neue Spalte CreationDate eingefügt mit den Werten aus der Tabelle created.datetime:


    X$CreationDate <- created.datetime[match(X$IssueID, created.datetime$IssueID), 2]

3. Schritt

Im dritten und letzten Schritt verwende ich das plyr API, um die Tabelle zu reduzieren. Das API wurde zuvor installiert und mittels library(plyr) eingebunden.

Der entsprechende Code sieht wie folgt aus:


    reduced <- ddply(X,
                     c("IssueID", "CreationDate"),
                     function(t) data.frame(Duration = sum(t$Duration))
    )

Der Übersichtlichkeit halber habe ich den Code in 3 Zeilen dargestellt.

Das Ergebnis sieht wie folgt aus:


    > reduced
      IssueID        CreationDate Duration
    1       A 2016-08-09 14:33:09  1387724
    2       B 2015-09-23 12:22:21   691200

Fazit

Ich bin immer mehr begeistert von R. In Java hätte ich für diesen 3-Zeiler sicherlich ein vielfaches an Code produziert.


Zum selber ausprobieren der Sourcecode zum download.

Ich arbeite mich gerade ein wenig in R ein, da ich Daten aus CSV Dateien statistisch aus­werten möchte. Dabei habe ich viel mit Datums­angaben zu tun. Im Folgenden ein paar Notizen die ich mir bisher gemacht habe

Datentyp in Spalte eines data.frame ändern

Beispiel: in einem data.frame existiert die Spalte Date. Beim Einlesen aus einer CSV Datei wird diese als kategorialer String erkannt.


    Date,Status,Solution
    2016-08-09T14:33:09,created,fixed
    2015-08-09T14:33:09,blocked,won't fix

Einlesen: df <- read.csv("date.csv", header=T)

Mittels des Kommandos


    df$Date <- as.Date(df$Date)

werden alle Werte umgewandelt.

Datetime umwandeln

Die R Klasse Date repräsentiert nur ein Datum. Für Datetime sind die beiden Klassen POSIXct und POSIXlt vorhanden. Beide halten Daten unter­schiedlich, liefern ansonsten allerdings ähnliche Funk­tionalität.

Umgewandelt wird wie folgt:


    df$Date <- as.POSIXct(strptime(df$Date, "%Y-%m-%dT%H:%M:%S"))

Als Defaultformat wird das ISO 8601 lang Format (mit Binde­strichen und Doppel­punkten) verwendet. Aller­dings mit dem Unter­schied, dass nicht der Standard­delimiter T verwendet wird, sondern ein Leer­zeichen. Beispiel: 2016-08-09 14:33:09. Aus diesem Grund kommt die Funktion strptime zum Einsatz, der das entsprechende Muster ("%Y-%m-%dT%H:%M:%S") mit T als Delimiter über­geben wird. Wird dies nicht in dieser Art und Wiese gemacht, wird nur das Datum ermittelt und die Zeit auf 00:00:00 gesetzt.

Jahr aus Date ermitteln


    df <- within(df, YEAR <- as.numeric(format(df$Date, "%Y")))

Fügt gleichzeitig eine neue Spalte (YEAR) in den data.frame ein. Dies liesse sich auch mittels df$YEAR <- as.numeric(format(df$Date, "%Y")) erledigen. Der Vorteil von transform ist, das mehrere Änderungen gleichzeitig durchgeführt werden können:


    df <- within(df, {YEAR <- as.numeric(format(df$Date, "%Y")),
                      MONTH <- as.numeric(format(df$Date, "%m"))
                     })

Wochentag (numerisch) mit Basis 1 für Montag berechnen


    df <- within(df, DOW <- ifelse((d = as.numeric(format(df$Date, "%w"))) == 0, 7, d))

Funktion holt aus einem Datum eines data.frames den Wochentag (as.numeric(format(df$Date, "%w"))) und weisst ihn einer Hilfsvariablen zu. Diese wird verglichen mit 0, da POSIX Date eine 0 für Sonntag zurück gibt. Ist der Wert 0 wird eine 7 zurück geliefert, ansonsten der Wert der Hilfsvariablen.

Kalenderwoche ermitteln

  • Mittels der Funktion format(Sys.Date(), "%V") kann die ISO 8601 Kalenderwoche ermittelt werden.
  • Mittels der Funktion format(Sys.Date(), "%U") kann die US Kalenderwoche ermittelt werden.

Ausgabe ist chr. Mittels as.numeric(…) in Zahl umwandeln.

Snippet um neue ISO WEEK Column in df ein zu fügen


    df <- within(df, WEEK <- as.numeric(format(df$Date, "%V")))

Subset aus data.frame

Ein Subset aus Zeilen eines data.frame, bei denen die Zellen einer Column einem Wert entsprechen. Zwei mögliche Lösungen:


    rows1 <- df[df$Status == "created", ]
    rows2 <- subset(df, Status == "created")

Vorteil der subset Funktion: Mittels des Parameters select können Spalten definiert werden, die in rows2 übernommen werden sollen:


    rows2 <- subset(df, Status == "created", select = c("Date", "Solution"))


Neben R verwende ich auch noch Bash Kommandos und sql4csv für die Bearbeitung von CSV Dateien. Für die Datenerhebung verwende ich unter anderen jan.

Gerade schlug mir Netbeans vor das folgende Codeschnipsel in einen Lambda Ausdruck umzuwandeln.


    private final Map originalToReplacement = new TreeMap<>(new Comparator<String>() {
        @Override
        public int compare(final String s1, final String s2) {
            return s1.length() - s2.length();
        }
    });

Das Ergebnis würde so aussehen:

    private final Map originalToReplacement =
            new TreeMap<>((String s1, String s2) -> s1.length() - s2.length());

Keine Frage, die Lambdaschreibweise ist wesentlich kürzer. Allerdings finde ich sie auch noch nicht so verständlich wie die lange Version. Fürs Erste habe ich mich entscheiden in diesem Fall keinen Lambda Ausdruck zu verwenden.

Nachdem der Git Daemon extra mittels


    $ sudo yum install -y git-daemon

nachinstalliert wurde, schlagen Netzwerkverbindungen zum pullen/fetchen der Repositorydaten fehl.

CentOS 7 ist etwas strenger was die Sicherheit angeht. Der Git Daemon dort lauscht zwar standardmässig an Port 9418, kann aber nicht von aussen angefragt werden. Fehlermeldung: "Connection refused". Die Firewall hat zugeschlagen. Damit es doch geht die Firewall-Zone auf 'trusted' stellen. Achtung: das kann Angreifern eventuell Tür und Tor öffnen.

Ich lasse CentOS 7 in einer gebridgten VM laufen, damit Kollegen mittels Git auf die Repositories zugreifen können. Siehe auch Mit Git und Lieutenantsystem auf master entwickeln.

Die Firewall-Zone kann über 'Systemwerkzeuge → Einstellungen → Netzwerk → Kabelgebunden → Options (kleines Sternicon unten rechts) → Identität → Firewall-Zone' eingestellt werden. Bitte auf das korrekte Netzwerkinterface achten.

Git dezentraler Ansatz eignet sich hervorragend für die schnelle Entwicklung im CD (Continuous Delivery) Umfeld. Leider wird Git in den meisten Ent­wicklungs­prozessen als zentralistisches Versions­verwaltungs­werk­zeug eingesetzt und verliert damit seinen entscheidenden Vorteil gegenüber Systemen wie zum Beispiel Subversion.

Im CD Umfeld macht es Sinn nur auf master zu entwickeln. Insbesondere wenn zum Beispiel nur Inhouse Projekte entwickelt werden. Branches haben die unangenehme Eigen­schaft, dass sie zu grossen Än­derungen verleiten und damit ein CD System konter­karieren.

Da meiner Meinung nach ein zentrales Repository für CI (Continuous Integration) System wie Jenkins oder Bamboo Sinn macht musst der Prozess des ausschliesslichen entwickeln auf master durch den Einsatz der dezentralen Möglichkeiten von Git umgesetzt werden.

Grösstes Problem ist der Austausch der entwickelten Artefakte zwischen den Entwicklern ohne über ein zentrales Repository zu gehen, wenn Branches nicht gewünscht sind und jeder Commit auf dem zentralen master einen Release Build mit anschliessendem Blau-Grün Deployment auslöst.

Für diese Problem bringt Git die nötigen Werkzeuge von Haus aus mit.

Lokales Repository remote verfügbar machen


    $ mkdir repos
    $ cd repos
    $ git daemon --base-path=. --export-all --verbose

In der erste Zeile wird ein neues Verzeichnis für die Repositories angelegt und dan in das Verzeichnis gewechselt. In dieses Verzeichnis werden später die remote-Repositories mittels git clone oder mittels git fetch geholt.

Entscheidend ist die letzte Zeile. Mittels git daemonwird ein einfacher Git interner Server (Daemon) gestartet. Dieser lauscht per default auf Port 9418.

Die Kommandoschalter und ihre Bedeutung im Einzelnen:

  • --base-path - exportiert Repositories aus dem angegebenen Ordner. In diesem Fall das Verzeichnis in dem der Daemon gestartet wird.
  • --export-all - exportiert alle Repositories im angegebenen Ordner.
  • --verbose - Jede Operation auf dem Server wird auf der Konsole ausgegeben.

Darüber hinaus bietet das daemon Unterkommando noch weitere Schalter. Diese sind im ersten Schritt allerdings nicht wichtig.

Der lauschende Daemon meldet sich mit der PID und der Meldung 'Ready to rumble'.

In einer weiteren Konsole clont der Entwicker isk jetzt ein Repository vom zentralen Git Server im repos Verzeichnis:


    $ cd repos
    $ git clone http://cetral.repo/general_problem_solver.git

Die entfernte Git Clients der Kollegen können jetzt beginnen anonym fetch und clone Operationen auf den Repositories in repos Verzeichnis auszuführen. push Operationen sind nicht möglich. push Operationen müssen explizit mittels Kommandozeilenparameter erlaubt werden.

Lieutenant fetcht mittels remote Verbindung

Angenommen ein Kollege hat bereits den master Branch des zentralen Repositories geclont (origin). Der er aktuell der Lieutenant ist und als einziger in den zentralen master Branch pushen darf, muss er den Entwicklungsstand der Kollegen auf Zuruf mittels fetch ziehen können. Hierzu richtet mittels des Git remote Kommandos eine entfernte Verbindung ein. Dabei wird der Verbindung ein bestimmter Name gegeben.


    $ git init
    Initialized empty Git repository in /home/sascha/repos/.git/
    $ git remote add isk git://192.168.1.127/general_problem_solver

IP Adressen und Namen können einfach per Chat ausgetauscht werden.

Bevor eine remote Verbindung zugefügt werden kann, muss ein leeres Verzeichnis mittels git init initialisiert werden. Ansonsten meldet git remote add … einen Fehler.

Ist das Verzeichnis initialisiert, wird die remote Verbindung eingerichtet.

Wichtig: die Endung .git fehlt im Verzeichnisnamen des entfernten Git Repositories.


    $ git fetch isk

Das Kommando zieht jetzt den Inhalt des entfernte Repositories. Mittels eines git checkout master wird in den master Branch gewechselt. Änderungen können einem Review unterzogen werden. Nach einem erfolgreichen Testlauf werden die Änderungen in das zentrale Repository gepusht. Das CI-System kann die Build- und Deploymentpipeline starten. Wurde das Blue-Green Deployment erfolgreich durchgeführt, kann ein eventuell anhängiges Ticket approved werden.

Und dann war da noch der Astrophysiker, der als Softwarearchitekt arbeitete. «Es gibt keine Softwarearchitektur.» war sein Mantra, «Alles organisiert sich von selbst. Wie das Universum.» Kann sein, war mein Gedanke und antwortete: «Klar. Das Universum hatte auch 13 Milliarden Jahre Zeit. Bin mir nicht sicher, ob die Firma so lange Zeit hat.» Hatte sie nicht.

["ANDROID GBC BALL COUNTER LOGO - BÄLLE (FUSSBALL, BASKELBALL) IN EINEM SIGMA ZEICHEN.] In the last few weeks I had developed an Android based counter for Lego® Great Ball Contraption systems. The counter should measure contactless the throughput of passing balls.

The hypothesis was: a colored or white Lego ball moves in front of a black background. The Android system camera captures every X milliseconds an image. The first image is captured with no passing ball and the image data is stored in memory for comparison. Every following image is checked for color changed against the first image. If the amount of pixels with a changed color (against the background from the first image) reached a percentage threshold, the ball count will be increased by one. Because of the camera sensor noise the color change value must also reach a threshold to be measured. In the implementation these two parameters can be configured. This improves the detection rate depending on the light environment of the GBC setup.

Measure the color change also reduces false positive ball counts because of environment light changes.

Nevertheless, Lego® also offers not only colored balls but also black and white only balls (footballs/soccer balls). If the user has also black and white balls he can also switch on a brightness detection based on the luminance. This follows the same rules like the color change detection but has a different threshold configuration parameter. Usage of the brightness detection might increase the false positive rate.

After a ball detection

After the systems has detected a passing ball in front of the camera, the system stops analyzing the given images for an amount of time. Otherwise it might happen that the same ball is counted twice.

The delay is depending on the layout of the GBC system and for this the delay time is also configurable.

Two good parameters are:

Capture delay
Delay between two image captures of the camera.
Value: 30ms
Capture delay after detection
The delay of time between the detection of a ball in an image and the next time checking again for a passing ball.
Value: 150ms

This allows a theoretical throughput of 5 Lego balls per second. Note: GBC is defined with 1 Lego ball per second. But as you know we ignore the standard in our constructions. Because of this the Android GBC Ball Counter implementation contains also a balls per second calculation.

Limitation

[SCREENSHOT ANDROID GBC BALL COUNTER] If two balls are passing very close succession there are false negative counts. This means, the two balls are counted as one ball. This is the greatest limitation of this non-contact method. The problem can be limited only by structural measures on GBC system.

Implementation

Image format

I use the YUV 4:2:0 8 bit image format delivered by the Camera2 Android API. This API was introduced with Android 5.0 (Lollipop).

The YUV image format has some advantages over the JPEG format: it doesn't pipe the image data through the RGB calculation and compression routines which takes a lot of time and other resources and doesn't produce more important information.

Android 5.0 Bug

Due to a bug in Android 5.0 the YUV subsystem doesn't deliver correct UV (chrominance) information. only the first 600+ bytes contains color information. The rest of the bytes are always zero. Only the Y (luminance) values are correct. This means: for Android 5.0 the implementation only supports the brightness ball passing detection algorithm.

More information

The implementation of the GBC is open source under the terms of the GNU General Public License 3 and the source code is available on Github. There you can dive deeper into the ball detection strategy.

How it works in practice

Ich habe heute Android 6.0 (Marshmallow) auf meinem Motorola Moto G (2. Generation) installiert. Hauptsächlich wegen eines Bugfixes im YUV_420_888 Image capturing. Das Camera2 API liefert bei Android 5.0 (Lollipop) keine korrekte Farbinformation (siehe auch hier). Diese Farbinformation benötigt ich aber, um den Lego® GBC Ball Counter weiter zu optimieren.

Bei der Installation hatte ich angegeben, dass die SD-Card als interner Speicher verwendet werden soll. Dadurch werden Daten beispielsweise nicht im Speicher des Smartphone gespeichert, sondern auf der Speicherkarte selbst. Vorteil: der interne Speicher des Gerätes wird weniger belastet. Nachteil: mit dem Android Device Monitor aus dem Android Studio kann ich nicht mehr direkt auf das Verzeichnis "/storage/emulated/0/Android/data/de.speexx.lego.gbc.ballcounter/files" zugreifen. Damit kann ich nicht einfach die Beispielbilder, die ich gespeichert habe, herunter laden.

Android 6.0 kann die Daten trotzdem erreichen und kopieren. Dazu muss ich die Einstellungen öffnen. Und dort dann über

Speicher & USB → SD‑Karte von SanDisk* → Erkunden → Android → data → de.speexx.lego.gbc.ballcounter

navigieren. Im Unterverzeichnis files liegen dann die erzeugten *.yuv Dateien.

Da Android mit der Endung yuv nichts anfangen kann, können die Dateien auch nicht geöffnet werden. Allerdings können sie mit einem längeren berühren des Namens ausgewählt werden, auch mehrere Dateien. Die so ausgewählten Dateien können dann geteilt werden. Ich habe die Dateien in Google Drive geteilt und dann von dort wieder herunter geladen.

Ziemlich komplizierte Prozedur und sicherlich nicht für die regelmässige Anwendung geeignet.

Gute Nachricht

Die Chrominanzwerte U und V sind jetzt vorhanden und auch die Strides Angaben machen jetzt Sinn: jeweils 4 Luminanzwerte mit einem U und einem V Wert.

Todo

In den nächsten Tagen werden ich den GBC Ball Counter aufwärtskompatibel anpassen.


* Name SanDisk kann eventuell abweichen.

based architecture is a current hype in architecture. I am therefore not yet clear with it from the point of architecture.

I'm not clear with it

The good
It reduces the complexity in the code artifacts for a simpler maintenance.
The bad
It increases complexity of governance of the software.
The ugly
It moves the complexity of the software to the network.

The ugly

11 years ago died. Wheeler formulated one of the most important phrases for software architectures:

All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections.

I must ask myself: what are the benefits to add the network as a layer of indirections? Handling software with networks is not trivial. The network might be not available or under heavy load. Timeouts are commonplace and many developers have never heard of the design pattern.

The bad

Governance of software is on of the most underestimated factor. In bigger systems you can't do what you want. Rules have introduced, established and must be respected. Without these rules a growing software system will collapse after a while - or maintenance costs will increase dramatically.

REST based microservice architectures are related to systems. And SOA without a strong governance will create an epic cluster fuck also.

Fine grained microservices will increase the complexity of the governance. The diagrams to visualize are increasingly confusing. And we need visualizations to get an overview and understanding of the system. And as we know, a process diagram with more than 7 boxes/tasks/whatever has the tendency to create chaos in our head.

The good

Software artifacts gets smaller. A microservice implementation should not have more than a few hundred lines of code - howsoever you measure it. This is good for refactoring or throwing the code away and write it new from scratch.

But I'm not quite sure that this is really a benefit. Do you really rewrite an implementation? In normal cases it is not a very complex tasks to refactor a code system with a few hundred lines of code. The benefit of rewriting comes in when you switch the technology stack. With microservices this is not a huge change-it-all tasks but you can change one service after the other.

More often than rewriting or refactoring a microservice (that works) is to refactor the entire system. Here REST based microservice based only on can become a pain in the ass. Refactoring a not typed software system is a mess. Also the communicating of code intention without a type system. Without a type system also design and governance (see: The bad) can become very problematic.

Although some advocates of Microservice architectures do not want to read: exports not only JSON, but also XML hedged with or any other technology that simple adds type safety.

Kurze Antwort: Nein.

Längere Antwort: Story Points machen keinen Sinn, weil sich in Scrum die Grundlage von Sprint zu Sprint ändert. Story Points sind wie Schulnoten, die Entwickler des Scrum Develpment Team vergeben. Zwar soll es innerhalb des Development Teams einen Konsens über die Story Point Bewertung geben, trotzdem kann jeder Entwickler eine User Story unterschiedlich bewerten. Es hängt von seinem Wissen und seiner Erfahrung ab. Deswegen gibt es im Planing Meeting den Scrum Poker.

Auch weggehende oder neu hinzukommende Teammitglieder verändern die Velocity in einem Scrum Development Team.

Die jeweiligen Verschiebungen können nur minimal sein. Über einen längeren Zeitraum aber kommt es unweigerlich zu Verschiebungen, welche Story Points als solche wertlos für ein Unternehmen machen.

Wann Story Points als sich Kennzahl eignen

Und trotzdem eignen sich Story Points, wenn z.B. die Abweichung zugesagter (commited) zu erreichten (achieved) Story Points über einen längeren Zeitraum gemessen wird. Diese Relation sagt etwas über die Schätzgenauigkeit des Teams aus. Und diese Schätzgenauigkeit muss ständig verbessert werden. Wird sie schlechter, dann muss geprüft werden warum dies der Fall ist. So kann zum Beispiel die Beschreibung der User Stories schlechter werden - wie messe ich die Qualität der User Stories sonst? Oder aber das Team wird durch hohe Mitarbeiterfluktuation schlechter, die Dauer eines Sprint hat sich verändert (verlängert), es kommen zu viele Bugs rein usw.

Vollkommen ungeeignete Kennzahlen

Vollkommen ungeeignet als langfristige Kennzahl sind die durchschnittlich erreichten Story Points der Sprints über einen Zeitraum X. Dabei wird beispielsweise der Durchschnitt der Story Points über 6 Sprints ermittelt. Nach dem nächsten Sprint wird der Durchschnittswert dann mit dem Durschnittswert des vorhergehenden Durchschnittswert verglichen und so eine Steigerung oder ein abfallen ermittelt. Bereits durch das Planing Poker sind die Story Points eines Sprints schon Durchschnittswerte. Wird aus der Menge der Durchschnittstwerte wiederum der Durchschnitt berechnet, dann ist die Kennzahl am Ende nur noch ein Durchschnittsbrei. Erschwerend kommt hinzu das Einflüsse auf das Team - z.b. Mitarbeiter- oder Zeitveränderungen - nicht berücksichtigt werden.

Zusammenfassung

Story Points eigenen sich nur sehr begrenzt als längerfristige Kennzahl in Scrum. Als Durchschnittswert sind sie vollkommen ungeeignet. In Relation von committed vs achieved Story Points können sie geeignet sein.

Wirklich. Ich finde es toll, dass China eine Hintertür für jedes System möchte, welches Daten verschlüsselt. Um die Daten bei Bedarf einfach zu entschlüsseln - also immer. Und ich kann die Aufregung der USA deswegen nicht verstehen, die das Gleiche selbst fordern.

Sollte China das Recht um- und dann durchsetzen, dann ist Unternehmenscyrpto am Ende. Die grossen US-Unternehmen können es sich nicht leisten den chinesischen Markt zu verlassen. Und sie werden auch keine chinesische Extrawurst produzieren. Zumal damit zu rechnen ist, dass dann andere Länder (Russland, Brasilien usw.) ganz schnell auch ein solches Recht einführen. Und auch der EU-Rat diskutiert die Schlüsselhinterlegung.

Unternehmenscrypto ist dann am Ende, weil wir dann alle offiziell wissen, dass sie nicht sicher ist. Jetzt müssen wir annehmen das sie nicht sicher ist. Weil grundsätzlich keine Cryptografie, die nicht Open-Source ist, als sicher gelten kann.

Open Source Software kann ich selbst übersetzen in ein lauffähiges System. Sie kann durch öffentliche Reviews gehen. Dort eine Hintertür ein zu bauen wird schnell auffallen. Open Source Cryptosoftware ist grundsätzlich vertrauenswürdiger.

Was sollen sie dagegen noch tun? Auch dort Hintertüren verlangen? Oder verbieten? In China… sicher­lich. Im vermeintlich freien Westen würde dies den Kern der freien Meinungsäußerung ad absurdum führen. Im vermeintlichen Kampf gegen den Terrorismus oder organisierte Kriminalität oder was auch immer, würden die Falken dies in Kauf nehmen. Es geht um Macht und Machterhalt. Und da kann auf Befindlichkeiten wie die freie Meinungsäußerung keine Rücksicht genommen werden.

Wir haben heute eine qualifizierte Incident-Bewertung erarbeitet. Dabei haben wir bewusst auf den in der IT-Entwicklung vorherrschenden Begriff Bug verzichtet. Uns erschien der Begriff Incident aus ITIL klarer.

Ein Incident wurde von uns als jegliche Abweichung von den Anforderungen definiert. Er wird in unserem Ticket-System als eigenständige Entität definiert, neben z.B. User Story und Epic. Ein solcher Incident muss bewertet werden. Von dieser Bewertung hängt die Dringlichkeit der Bearbeitung ab.

Die 3 Incident Kategorien

Die von uns vorgeschlagene Bewertung von Incidents hat drei Ebenen. Dabei entspricht 1. der dringlichsten Kategorie und 3. der am wenigsten dringlichen Kategorie.

  1. Major Incident
  2. Defect
  3. Nonconformity

Major Incident

Ein Major Incident kann nur von einem Kunden, an den ein Produkt ausgeliefert wurde, gemeldet werden. Ein solcher Major Incident muss immer durch einen Verstoß gegen ein Service Level Agreement (SLA) oder die wesentliche Nichteinhaltung eines Produktmerkmals begründet sein. Ist ein Major Incident nicht durch ein SLA oder eine wesentliche Nichteinhaltung begründet, wird der Major Incident zu einen Defect herab gestuft. In einem solchen Fall muss dokumentiert werden, dass der Defect von einem Kunden gemeldet wurde.

Ein Major Incident löst immer eine beschleunigte Bearbeitung aus. Das heisst, ein Team muss sich unmittelbar um die Lösung des Incidents kümmern.

wesentliche Nichteinhaltung eines Produktmerkmals

Der Begriff wesentliche Nichteinhaltung eines Produktmerkmals ist noch zu unbestimmt. Der Begriff muss genauer bestimmt werden. Zum Beispiel ist bei einer Datenbankanwendung die fehlende Möglichkeit einen korrekten Datensatz ab zu speichern eine wesentliche Nichteinhaltung eines Produktmerkmals. Ebenso kann das entstehen von nicht konsistenten Datensätzen eine wesentliche Nichteinhaltung sein.

Kann hingegen eine virtuelle Maschine nicht mit z.B. einen Hauptspeicher von 4 GB gestartet werden, wohl aber mit 4,1 GB, so ist dies keine wesentliche Nichteinhaltung eines Produktmerkmals

Defect

Eine Defect (Mangel) ist jede Art von Mangel, welcher den bestimmungsgemäßen Gebrauch des Produktes einschränkt. Das Beispiel aus der virtuellen Maschine im vorhergehenden Kapitel ist ein solcher Defect.

Ein bekannter Defect für eine geplante Produktversion ist dabei immer ein sogenannter Auslieferungs- oder Release-Blocker. Grund: in Deutschland darf ein Produkt nur frei von Sach- und Rechtsmängeln ausgeliefert werden (§ 434 BGB).

Einen Release mit einem bekannten Defect auszuliefern bedarf immer einer Sonderfreigabe durch die Geschäftsführung. Schon damit der Releasemanager von Schadensersatzansprüchen geschützt wird.

Nonconformity

Eine Nonconformity beschreibt einen Fehler, der den bestimmungsgemäßen Gebrauch des Produktes nicht einschränkt. Dies können z.B. falsch geschriebene Wörter sein oder eine nicht korrekte Farbgebung. Dabei können die gegebenen Beispiele nicht pauschal angewendet werden. Bei einer Ampelanlage sind falsche Farben eine wesentliche Nichteinhaltung eines Produktmerkmals und damit ein Major Incident.

Ein Produkt kann ohne Sonderfreigabe durch die Geschäftsführung freigegeben werden, wenn es noch nicht frei von Nonconformities ist.

Externe Indicent-Meldungen

Incident-Meldungen, die nicht von Mitarbeitern der Organisation gemeldet werden, müssen als external incident gekennzeichnet werden. Dies ist bei einem Major Incident impliziet der Fall. Bei einem Major Incident, welcher in einen Defect umgewandelt wird, muss die Markierung automatisch eingefügt werden.

Für "external incidents" gilt, das immer eine Root-Cause-Analyse über die technische und organisatorische Ebene durchgeführt werden muss. Aus dieser Analyse werden dann Massnahmen zur zukünftigen Fehlervermeidung abgeleitet.

Schlussbetrachtung

Ich denke, dass dieses Modell einer Incidentbewertung einigermassen stimmig ist. Unzufrieden bin ich noch mit dem Begriff und der Definition der "wesentlichen Nichteinhaltung eines Produktmerkmals".

Ich brauchte die Telefonnummer des Babysitters. In vergangener Zeit hätte Gabi mir die Nummer einfach diktiert. Da wir aber mobil vernetzt sind, hat sie mir die Nummer von ihrem iPhone aus auf mein Windows Phone geschickt. Und damit begann der Spass.

Das iPhone hat eine vCard als MMS verschickt. Fragt mich nicht wieso. Da ich in Zeiten von WhatsApp kein MMS eingerichtet hatte, bekam ich eine Nachricht, wie ich auf den Inhalt der MMS auf einem Telekom-Server zugreifen kann.

Um die MMS von dem Telekom-Server abzuholen benötige ich erst einmal einen Webzugang. Dann muss ich mich mit eigener Telefonnummer und einer PIN anmelden. Erst einmal die eigene Telefonnummer herausfinden… Dann habe ich mich angemeldet.

Die Weboberfläche zeigte mir einen Textblock an, an dessen Struktur eine vCard erkannt werden kann. Den kann ich als ZIP Datei herunter laden. Okay, das ist jetzt zuviel des Guten. Also neuen Kontakt im Handy aufgemacht und die Daten mit der Hand eingetragen. Zum Glück ist die vCard einigermassen selbsterklärend.

Dauer der Aktion: ca. 8 Minuten. Mit simplen diktieren hätte es vermutlich weniger als eine Minute gedauert.

Würde das Rad nicht ständig neu erfunden werden, würden wir heute noch mit Scheiben­rädern aus Holz, erfunden in der Jung­stein­zeit, herum fahren. Irgend­wann wurden Speichen­räder erfunden, erst aus Holz, bei fast jedem Fahr­rad inzwischen aus Metall. Es wurden nahtlos gezogene Räder für Hoch­geschwindig­keits­züge erfunden und Gummi­räder für Autos. Gebirgs­bahnen fahren mit Zahn­rad­rädern. Es gibt Lenk­räder, Schöpf­räder und Lauf­räder in Turbinen.

Das Rad wurde und wird permanent neu erfunden. Lasst euch also nichts erzählen.

[EIN IN BLAUER FARBE GEHALTENER PFLUG IN EINER WIESE. IM HINTERGRUND WEITERES LANDWIRTSCHAFTLICHES GERÄT]