Logo

dev-resources.site

for different kinds of informations.

Nextflow: Organizando fotos por geoposicion

Published at
9/26/2023
Categories
groovy
nextflow
Author
jagedn
Categories
2 categories in total
groovy
open
nextflow
open
Author
6 person written this
jagedn
open
Nextflow: Organizando fotos por geoposicion
WARNING

Nextflow es una herramienta orientada a casos de uso mucho más interesantes pero este ejemplo creo que puede servir para prácticar un poco y ver algunas funcionalidades de este DSL

En este post vamos a desarrollar un pipeline de Nextflow para organizar las fotos de un directorio y ordernarlas por el sitio que fueron tomadas respecto de un punto que indiquemos.

Para ello el pipeline accederá a la meta información de cada foto y buscará si tiene la posición donde fue tomada. Si la foto no cuenta con esta info, la foto será ignorada.

Una vez obtenida la información de todas las fotos, el pipeline las ordenará según lo lejos que se encuentren de un punto dado (en formato "latitud, longitud"). Si no se proporciona ningun punto se tomará el punto [0,0] como referencia.

La idea es obtener al final del proceso un directorio con las imágenes copiadas pero renombradas como

0-imagexxxx.jpg 1-imageyyyy.png 2-imagezzzz.jpg etc

Groovy util

Para mejorar la legibilidad del pipeline (y separar la "lógica de negocio" del pipeline) vamos a crear una clase Groovy con 2 métodos estáticos:

Uno servirá para extraer la posición donde fue tomada la foto

static def extractCoord( path ){
    def metadata = Imaging.getMetadata(path.toFile())
    if( !metadata || !metadata.metaClass.getMetaMethod("getExif") )
        return null

    def latitude = metadata.exif?.GPS?.latitudeAsDegreesNorth
    def longitude = metadata.exif?.GPS?.longitudeAsDegreesEast

    [latitude, longitude]
}
Enter fullscreen mode Exit fullscreen mode

El otro método servirá para ordenar dos posiciones geográficas:

static double metersTo( a, b) {
    double lat1 = a[0] as double
    double lng1 = a[1] as double
    double lat2 = b[0] as double
    double lng2 = b[1] as double
    double radioTierra = 6371;
    double dLat = Math.toRadians(lat2 - lat1);
    double dLng = Math.toRadians(lng2 - lng1);
    double sindLat = Math.sin(dLat / 2);
    double sindLng = Math.sin(dLng / 2);
    double va1 = Math.pow(sindLat, 2) + Math.pow(sindLng, 2) * Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2));
    double va2 = 2 * Math.atan2(Math.sqrt(va1), Math.sqrt(1 - va1));
    double meters = radioTierra * va2;
    meters;
}
Enter fullscreen mode Exit fullscreen mode

Image

Para mejorar la legibilidad del código (y no estar usando mapas genéricos donde guardar la información) vamos a crear una clase Image

class Image{
    def file
    def coord
    def order
}
Enter fullscreen mode Exit fullscreen mode

Nos servirá para guardar referencia a la imagen original, las coordenadas y su posicion en la lista

Process

El pipeline se va a componer de dos procesos:

Uno para iterar sobre las fotos y extraer su posicion

process EXIF_GPS{
    input:
        path fImage
    output:
        val image
    exec:
        coord = Images.extractCoord( file("$params.directory/$fImage") )
        image = new Image(file:fImage, coord: coord)
}
Enter fullscreen mode Exit fullscreen mode

y otro para copiar la imagen original al destino, usando la posicion como nombre

process PROCESS_IMAGE{
    input:
        each img
    output:
        val target
    exec:
        target = file("$params.directory/$img.file").copyTo(file("$params.outputDir/$img.order-$img.file"))
}
Enter fullscreen mode Exit fullscreen mode

Pipeline

Por último el pipeline a ejecutar:

  • Para todas las fotos que existan en un directorio

  • Extraeremos la posicion

  • Filtraremos las que tengan información de la posición

  • Las ordenaremos segun el punto de origin

  • Copiaremos a directorio de salida

workflow{
    def images = Channel.fromPath("$params.directory/*.jpg")

    images //read all images
        | EXIF_GPS // extract gpf information
        | branch { // diferenciate if exif information or not
            no_info: !it.coord?[0]
            with_info: it.coord
        }
        | set{ gps_images } // send to new channel

    gps_images.with_info // only images with gps info
        | toSortedList{ a, b-> // sort respect how far are from origin
            Images.metersTo(origin,a.coord) <=> Images.metersTo(origin, b.coord)
        }
        | map { // assign the index
            it.eachWithIndex{ img, idx-> img.order = idx}
        }
        | set{ images_sorted } //send to new channel

    images_sorted
        | PROCESS_IMAGE
        | view

}
Enter fullscreen mode Exit fullscreen mode

Este workflow tiene algunas cosas interesantes como:

branch que nos permite crear un canal con canales "hijos" según el criterio que queramos

set que nos permite crear canales "al vuelo". En este caso creamos un canal images_sortedalimentándolo con un ArrayList de images y así poder ejecutar en pararelo cada imagen

Ejecutando

Si tienes instalado nextflow y tienes un directorio con imágenes puedes ejecutar

nextflow run -r main https://github.com/jagedn/nextflow-images.git --directory "/MI/DIRECTORIO/ORIGEN" --outputDir "/MI/DIRECTORIO/SALIDA"

Como ves Nextflow es capaz de descargar desde un repositorio git un pipeline completo e incluso de poder especificarle qué rama del mismo queremos ejecutar (main en mi caso)

Si quieres ordenar las fotos por algún punto que no sea [0,0] añade al final del comando --origin "20.23,12.13123" o las coordenadas que quieras

Conclusión

Probablemente no sea un pipeline muy útil pero me ha servido para practicar un poco el cómo encadenar canales y procesos

groovy Article's
30 articles in total
Favicon
Exploring Groovy: Features and Advantages Over Java
Favicon
Tasting Groogle
Favicon
Machine Learning with Spark and Groovy
Favicon
Groogle 4.0.0 (Google DSL)
Favicon
Tutorial: Learn how to use the H2 Database with Spring Boot! 🤔
Favicon
Mocking with Groovy
Favicon
Check for newer versions of dependencies in pom.xml
Favicon
Setting up linters in Gitlab CI for C++ and Groovy / Jenkins code
Favicon
Machine Learning con Groovy
Favicon
Groovy 🎷 Cheat Sheet - 01 Say "Hello" from Groovy
Favicon
Advice needed
Favicon
HackTheBox - Writeup Builder [Retired]
Favicon
The golden age of Kotlin and its uncertain future
Favicon
Probando a firmar documentos con Docuten
Favicon
Groogle al rescate
Favicon
Tech Watch #3 — October, 20, 2023
Favicon
Swagger-Operator, let groovy operate your cluster
Favicon
Nextflow: Organizando fotos por geoposicion
Favicon
🎶 Groovy: The Dynamic and Versatile JVM Language! 🚀
Favicon
FediSearch
Favicon
Introduction To Jenkins Shared Libraries
Favicon
Remove items matching a pattern from a list in Groovy
Favicon
Processing in parallel with Groovy
Favicon
Wednesday Links - Edition 2023-05-03 🇵🇱📜
Favicon
Wednesday Links - Edition 2023-04-26
Favicon
An Introduction to Jenkins Pipeline: Simplifying Continuous Integration and Delivery with Examples
Favicon
Graalvanizando un script de Groovy
Favicon
Passage of time calculations in Groovy
Favicon
Write a method like Python's string.printable in Groovy
Favicon
Neat time and date manipulation with Groovy

Featured ones: