Tuesday, April 19, 2016

Call me! PythonJS and variadic functions

I’m currently developing UI automation tests for Adobe Experience Design, aka Adobe XD. Or, for anybody who hasn’t heard of the recent name change: Project Comet!

Combine and attach different material 

1. This is a note to myself. Mostly

Today, I got stuck in something that looked pretty straight forward to me. I wanted to call a function in a Python API from my JavaScript code. We use PythonJS to make this magic happen. In the most cases, calling a function in the Python API is not a big deal. Scalar values work well, named arguments work well, but variadic function args do not.
The function I wanted to call looks like this:

def menuItem(self, *args)
Calling the function from JavaScript, I tried several things. The most promising for me was to convert the JavaScript arguments into an Array and pass it on.

pythonObj.menuItem(['value1', 'value2', 'value3'])
The result was an error, telling me that I should call the function with a string or int. Okay, the function signature accept either one or multiple `int`s or `string`s. In various combinations.
I guess, my initial approach was a bit too naive. A JavaScript array doesn’t translate to a vararg in Python. Got it! But how do I pass the list of varargs from JavaScript to Python? I tried to apply the arguments from the JavaScript function to obj.menuItem. But this didn’t work with the same error.
I tried to call the Python function by

obj.menuItem("value1", "value2", "value3")
That worked well. But I can’t make this happen from the JavaScript side? How can I split up an unknown number of arguments and pass them all as single arguments? I had no answer for that.

2. Workaround, Hack, you name it

If I would have a fixed number of arguments, the solution would be easier. I ended up with a hacky solution (IMHO), since I didn’t wanted to spent more time on this interesting but blocking issue.

function callPythonFunction(args) {
    switch (args.length) {
        case 1: {
            return app.menuItem(args[0]);
        }

        case 2: {
            return app.menuItem(args[0], args[1]);
        }

        case 3: {
            return app.menuItem(args[0], args[1], args[2]);
        }

        default:
            console.error("Unsupported amount of arguments for args", args.length);
        }
}
Is this a good solution? Until I find something better, I consider it as a good solution.

3. Summary

Bridging languages and make up for missing or non transferrable features requires creative solutions. They might not be the best solutions, but they unblock and let you move on.

What do you think? Is there a better way of doing this? Let me know in the comments.

Thanks for reading!

Wednesday, April 13, 2016

DxO One - Ein Erfahrungsbericht

Schon als die interessante Ansteck-Kamera im Frühjahr 2015 angekündigt wurde, hab ich ein Auge auf sie geworfen. Da die Kamera aber nur für den Anschluss an iPhones hergestellt wird, hatte sich die Sache nach Durchsicht der technischen Daten dann aber auch schon erledigt. Ich hatte zu diesem Zeitpunkt kein iPhone. Lediglich ein iPad, aber ich halte nicht so viel davon, mit meinem riesigen iPad durch die Gegend zu laufen und Bilder zu machen.

1. TL;DR

1.1. Kaufen? Ja, Nein, Weiss nicht

1.1.1. Pros

  • Bildqualität ist sehr gut
  • Selfie Mode (für alle die es brauchen)
  • minimales Gewicht, handlich, passt in jede Tasche
  • mit der Kamera App lässt alles aus der Kamera rausholen
  • Akku lädt sehr schnell wieder auf

1.1.2. Cons

  • Batterie hat eine kurze Lebenszeit (und kann nicht gewechselt werden)
  • Braucht beim speichern von Super RAW Bildern extrem lange
  • neigt zu überhitzen
  • der Lightning Connector macht für mich keinen robusten Eindruck

2. Jetzt aber

Mitte 2015 ist die Kamera dann für den Preis von $599 hier auf den Markt gekommen. Der Preisschock ging nach kurzer Zeit wieder weg, aber die Frage blieb: warum ist die Kamera genauso teuer wie ein iPhone oder wie eine gute handliche Kompaktkamera? Und diese Kamera macht definitiv nur mit einem iPhone Sinn, weil sie nur ein winziges Display zur Anzeige von Statusinformationen hat. Also ohne iPhone praktisch unbrauchbar. Ein iPad mit Lightning Anschluss würde auch funktionieren. Aber wie gesagt, bin ich kein großer Freund davon.
Im Februar 2016 hatte dann ein Händler die Kamera für $350 im Angebot. Das war dann der Moment. Ja, ich konnte nicht mehr widerstehen und hab die Kamera gekauft. Ja, ja, ein neues Spielzeug in der Sammlung.

3. Die Kamera

Ist sehr handlich und die Elektronik befindet sich in einem Aluminium Gehäuse. Eine Plastikklappe verdeckt den SD Karten Slot und den USB Anschluss. An der Seite des Gehäuses ist der Lighting Connector angebracht. Er wird einfach eingeklappt. Mehr Anschlüsse hat die Kamera auch nicht

4. Die iPhone/iPad App

Um die Kamera überhaupt bedienen zu können, muß die kostenlose iPhone App installiert werden. Die App kann vom iTunes Store installiert werden. Die App bietet alle Einstellmöglichkeiten die man von einer Kamera her kennt. Es gibt einen Auto Mode, verschiedene Szenenprogramme und die üblichen manuellen Modi (P, A, S, M). Blende und Belichtungszeit kann eingestellt werden. Der Fokus Mode kann aus der App heraus geändert werden. Die App dient auch dazu die Bilder von der Kamera auf das iPhone zu kopieren. Auf dem iPhone kann man sie dann weiter bearbeiten oder direkt von dort im Internet verteilen.
iPhone App - Camera Modi

iPhone App - Sucher und Einstellmöglichkeiten

5. Die Verbindung

Die Kamera wird über einen ausfahrbaren Lightning Connector an das iPhone angeschlossen. Der Anschluss klappt nur aus, wenn man den Objektivschutz ganz nach unten schiebt. Damit wird die Kamera eingeschaltet und der Lightning Connector fährt aus dem Gehäuse heraus.
Der LIghtning Connector zum Anschluss ans iPhone

6. Sensor

Die kleine Kamera hat einen nicht ganz so kleinen Sensor. Im Gehäuse steckt ein 20MP 1 Inch Sensor. Das gleiche Kaliber wie in einer guten handlichen Kompaktkamera. Leider habe ich nicht herausbekommen, ob der Sensor eine Eigenentwicklung ist, oder von einem anderen Hersteller lizensiert wurde. Macht aber auch nicht wirklich einen Unterschied. Ich wollte hier ohnehin nicht die Sensoren vergleichen.

7. Geschwindigkeit

Die Kamera legt ein gutes Tempo an den Tag. Fokussieren (vornehmlich mit dem Finger auf dem Display des iPhones) und dann den Auslöser betätigen. Die Bilder werden schnell gespeichert. Es sollte auf jeden Fall nicht an der SD Karte gespart werden (die übrigens nicht im Liederumfang enthalten ist). Mit einer schnellen SD Karte ist die Kamera schnell wieder einsatzbereit.

8. Bild Qualität

Die Bilder sind gut bis sehr gut. Auf jeden Fall besser als die mit dem iPhone erstellten Bilder. Aber das sollte mich auch nicht wirklich wundern. Die Kamera kann Bilder als JPEG und RAW und Super RAW speichern (dazu unten mehr). Ich fotografiere generell in RAW, um bei der Nachbearbeitung die meisten Möglichkeiten für die Korrekturen zur Verfügung zu haben.

9. Super RAW

Hoppla, was ist das denn? Besser als RAW? Ich wusste ja nur, das es unkomprimiertes RAW gibt, aber Super RAW. Okay, und was ist es nun? Die Kamera macht 4 Aufnahmen kurz hintereinander und diese 4 Aufnahmen werden in eine Datei geschrieben. Zum importieren muss man dann DxO Connect bemühen (siehe unten). Das ist eine zusätzliche Software, die man erst downloaden kann, nachdem man die Kamera registriert hat. Leider funktioniert die Registrierung der Kamera nur aus der iPhone App heraus. Beim Import mit DxO Connect (siehe unten), werden die Bilder dann mittels verschiedener Algorithmen zusammengeführt (das Rauschen wird stark minimiert) und so ein besseres Bild erzeugt. Das resultierende Bild liegt dann als optimierte JPEG Datei vor. Das Rauschen ist im JPEG definitiv geringer als in der orignalen RAW Datei. Aber ich weiß nicht, ob das diese riesigen Files Super RAW Files das Ergebnis rechtfertigen. Aber okay, irgendein merkwürdiges Verkaufsargument darf eben nicht fehlen.
Das Rauschen im JPEG ist auf jeden Fall geringer als in der Super RAW Ausgangsdatei
Warnung Auf jeden Fall sorgt der Super RAW Modus dafür, dass die Kamera ziemlich lange braucht um die Daten auf die Karte zu schreiben. Das ist auch nicht besonders überraschend, da die 4 fache Datenmenge gespeichert werden muß. Und ein RAW Bild ist so um die 40 MB groß! Die DXO Datei ist satte 160 MB groß.
Ich habe es mehrere male erlebt, dass die Kamera im Super RAW Modus ziemlich warm geworden ist. Ich habe nur einmal die Hitze Warnmeldung im Display der Kamera gesehen. Andere Benutzer scheinen damit mehr Erfahrung gemacht zu haben.

10. Firmware Updates

Das letzte Firmwareupdate hat der Kamera einen "Retro-Mode" verschafft. Was ist das? Das kleine Monochrome Display kann jetzt als Sucher benutzt werden, wenn die Kamera nicht an das iPhone angeschlossen ist. Damit kann man dann ohne das iPhone mit der Kamera Bilder machen (hätte man vorher auch schon machen können, wäre aber ein kompletter Blindflug gewesen). Das geht so als Notfall-Lösung durch. Richtig was sehen, kann man auf dem kleinen Display nicht. Nur Schwarz/Weiß und viel zu geringe Auflösung. Aber man sieht zumindest in welche Richtung die Kamera blickt.
Das kleine Display

11. Software

Eigentlich braucht man die zusätzliche Software nicht um die Bilder von der Kamera zu bekommen. Wenn man aber Bilder im Super RAW Format gemacht hat, dann sollte man DxO Connect auf jeden Fall installieren. Denn nur damit kann man den Super RAW Bildern die ganzen Informationen entlocken. Ansonsten können die Bilder auch direkt von der angeschlossenen Kamera oder der SD-Karte in Lightroom oder Apples Photos importiert werden.

12. Testbilder

Closeup

Farben

Abend

Langzeitbelichtung

Closeup

Licht und Schatten

Sonnenschein

Kunstlicht

Tageslicht

Sonnenschein

Saturday, March 5, 2016

5 npm script secrets

No Secret

Last week I was hosting a workshop about Electron Shell, ES6 and Reactjs at ForwardJS in San Francisco. Preparing all the sample code, I was inspired by this blog post to avoid any build tool like grunt or gulp at all. In fact, I wanted to remove anything that could distract the attendees of the workshop by throwing another unknown toolset at them. Instead, leveraging the build in capabilities of npm.

The Idea

Why use grunt, gulp, etc. if you can leverage the power of npm scripts? Not having to maintain a build script is a good idea and helps to remove another piece of complexity from your project.

Secrets

Different types of scripts

npm supports custom scripts defined by scripts property of package.json. npm distinguishes between 2 different kind of scripts

  • lifecycle scripts

  • directly-run scripts

Lifecycle scripts tie into a specific phase of your project lifecycle, like install, publish, start, stop, etc. These scripts can be executed with npm lifecycle-script. Each lifecycle script can have a pre and post script for that phase, e.g. preinstall and postinstall.

The directly-run scripts need the extra run command after npm, e.g. npm run *my-script-name*. Your directly-run scripts can have a pre and post step with the same name as your script name, e.g. premy-script-name and *postmy-script-name. Always without space between the prefix and the script name.

You can read more about it in the official docs.

Tip Call your own scripts always with npm run <script-name>.

Platform differences

Sometimes you need to do things differenly when it comes to build your project on multiple platforms. I ran into the issue, where I needed to copy files. On OSX this is as simple as cp srcfile.txt destfile.txt. On Windows, this didn’t work, since cp is an unknown command, unless you have cygwin installed. Building your project in the default Windows command prompt fails with an error, once you want to copy those important files. On Windows you should use copy instead.

Tip Create an external copy.js file and do the OS specific tasks there. You can use process.platform === 'win32' to determine if you are running Windows.

DRY - refer to anything defined in your package.json

DRY (don’t repeat yourself) is a great principle when it comes to software development. You can even refer to anything defined in your package.json when you execute your script. Let’s assume, you want to create a zip for distribution.

"scripts": {
    "create-zip": "zip -r app-1.0.zip dist"
}

Now you can call npm run create-zip to create a zip containing all the files to distribute your app.

But why can’t we just refer to the name of the app and the version already defined in package.json? Well, we can. npm exposes all config keys from package.json in the form npm_package_configkey for you to use in your scripts.

"scripts": {
    "create-zip": "zip -r $npm_package_name-$npm_package_version.zip $npm_package_distdir"
}
Tip Stay DRY! Refer to information already available in package.json. You can define whatever config option in package.json and refer to it in the same way.

Call any script in another script

What if you want to run more than one script at the same time? You want for example watch *.js files and *.scss files and preprocess them? How can you do this with one npm script?

You can concatenate different scripts and run them all at the same time:

"scripts": {
    "sass-watch": "node-sass -watch src/style.scss dest/style.css",
    "babel-watch": "babel -w *.js dist",
    "watch": "npm run sass-watch & npm run babel-watch" // 1
}
1 npm run watch will call both custom scripts and run them at the same time. This will create two processes that run independently from each other.
Tip Concatenate multiple scripts with & to run them as one script.

Run any script in your node_modules/.bin directory

Installing any node module that adds it’s own executable script, will be available from projectDir/node_modules/.bin. Let’s say, you need babel for your project and you installed it with npm install babel-cli --save-dev, you can run babel from within your project directory by typing ./node_modules/.bin/babel. That is great, since you don’t have to rely on a global installation of babel.

If you want to run babel as part of a script, you would probably specify the whole path to the babel script:

"scripts": {
    "babel-watch": "./node_modules/.bin/babel -w *.js dist"
}

Since npm adds all executable script to the PATH for the script, you can instead write

"scripts": {
    "babel-watch": "babel -w *.js dist"
}
Tip Don’t specify the full path to a script in your projects node_modules/.bin folder. Use the command without the path.

Thank you very much for reading. If you have any comments or questions, please leave them in the comments below.