[shotwell/wip/phako/enhanced-faces: 22/136] First working version of face extraction into FaceLocationTable



commit 76a130d28615de67646b1163c3b9c1ddb68df9ba
Author: NarendraMA <narendra_m_a yahoo com>
Date:   Sat Jul 7 18:22:32 2018 +0530

    First working version of face extraction into FaceLocationTable

 src/Commands.vala                           | 22 ++++++-----
 src/Photo.vala                              |  2 +-
 src/db/DatabaseTable.vala                   |  2 +-
 src/db/Db.vala                              | 15 ++++++++
 src/db/FaceLocationTable.vala               | 57 +++++++++++++++++++++++++++--
 src/faces/Face.vala                         |  2 +-
 src/faces/FaceLocation.vala                 | 42 ++++++++++++++-------
 src/faces/FaceShape.vala                    | 12 ++++++
 src/faces/FacesTool.vala                    | 30 +++++++++++----
 thumbnailer/shotwell-video-thumbnailer.vala | 12 +++---
 10 files changed, 153 insertions(+), 43 deletions(-)
---
diff --git a/src/Commands.vala b/src/Commands.vala
index 6924f826..a0d3766c 100644
--- a/src/Commands.vala
+++ b/src/Commands.vala
@@ -2542,7 +2542,8 @@ public class RemoveFacesFromPhotosCommand : SimpleProxyableCommand {
         
         face.attach_many(map_source_geometry.keys);
         foreach (Gee.Map.Entry<MediaSource, string> entry in map_source_geometry.entries)
-            FaceLocation.create(face.get_face_id(), ((Photo) entry.key).get_photo_id(), entry.value);
+            FaceLocation.create(face.get_face_id(), ((Photo) entry.key).get_photo_id(),
+                                                    { entry.value, null });
     }
     
     private void on_source_destroyed(DataSource source) {
@@ -2608,7 +2609,8 @@ public class DeleteFaceCommand : SimpleProxyableCommand {
                 Face face = (Face) source;
                 
                 face.attach(photo);
-                FaceLocation.create(face.get_face_id(), entry.key, entry.value);
+                FaceLocation.create(face.get_face_id(), entry.key,
+                                                        { entry.value, null });
             }
         }
     }
@@ -2618,10 +2620,10 @@ public class ModifyFacesCommand : SingleDataSourceCommand {
     private MediaSource media;
     private Gee.ArrayList<SourceProxy> to_add = new Gee.ArrayList<SourceProxy>();
     private Gee.ArrayList<SourceProxy> to_remove = new Gee.ArrayList<SourceProxy>();
-    private Gee.Map<SourceProxy, string> to_update = new Gee.HashMap<SourceProxy, string>();
-    private Gee.Map<SourceProxy, string> geometries = new Gee.HashMap<SourceProxy, string>();
+    private Gee.Map<SourceProxy, FaceLocationData?> to_update = new Gee.HashMap<SourceProxy, 
FaceLocationData?>();
+    private Gee.Map<SourceProxy, FaceLocationData?> geometries = new Gee.HashMap<SourceProxy, 
FaceLocationData?>();
     
-    public ModifyFacesCommand(MediaSource media, Gee.Map<Face, string> new_face_list) {
+    public ModifyFacesCommand(MediaSource media, Gee.Map<Face, FaceLocationData?> new_face_list) {
         base (media, Resources.MODIFY_FACES_LABEL, "");
         
         this.media = media;
@@ -2640,13 +2642,13 @@ public class ModifyFacesCommand : SingleDataSourceCommand {
                         FaceLocation.get_face_location(face.get_face_id(), ((Photo) media).get_photo_id());
                     assert(face_location != null);
                     
-                    geometries.set(proxy, face_location.get_serialized_geometry());
+                    geometries.set(proxy, face_location.get_face_data());
                 }
             }
         }
         
         // Add any face that's in the new list but not the original
-        foreach (Gee.Map.Entry<Face, string> entry in new_face_list.entries) {
+        foreach (Gee.Map.Entry<Face, FaceLocationData?> entry in new_face_list.entries) {
             if (original_faces == null || !original_faces.contains(entry.key)) {
                 SourceProxy proxy = entry.key.get_proxy();
                 
@@ -2662,13 +2664,13 @@ public class ModifyFacesCommand : SingleDataSourceCommand {
                 assert(face_location != null);
                 
                 string old_geometry = face_location.get_serialized_geometry();
-                if (old_geometry != entry.value) {
+                if (old_geometry != entry.value.geometry) {
                     SourceProxy proxy = entry.key.get_proxy();
                     
                     to_update.set(proxy, entry.value);
                     proxy.broken.connect(on_proxy_broken);
                     
-                    geometries.set(proxy, old_geometry);
+                    geometries.set(proxy, face_location.get_face_data());
                 }
             }
         }
@@ -2695,7 +2697,7 @@ public class ModifyFacesCommand : SingleDataSourceCommand {
         foreach (SourceProxy proxy in to_remove)
             ((Face) proxy.get_source()).detach(media);
         
-        foreach (Gee.Map.Entry<SourceProxy, string> entry in to_update.entries) {
+        foreach (Gee.Map.Entry<SourceProxy, FaceLocationData?> entry in to_update.entries) {
             Face face = (Face) entry.key.get_source();
             FaceLocation.create(face.get_face_id(), ((Photo) media).get_photo_id(), entry.value);
         }
diff --git a/src/Photo.vala b/src/Photo.vala
index c317714e..fc541003 100644
--- a/src/Photo.vala
+++ b/src/Photo.vala
@@ -5226,7 +5226,7 @@ public class LibraryPhoto : Photo, Flaggable, Monitorable {
                 if (location != null) {
                     face.attach(dupe);
                     FaceLocation.create(face.get_face_id(), dupe.get_photo_id(), 
-                        location.get_serialized_geometry());
+                        location.get_face_data());
                 }
              }
         }
diff --git a/src/db/DatabaseTable.vala b/src/db/DatabaseTable.vala
index 5ec5be12..293d7546 100644
--- a/src/db/DatabaseTable.vala
+++ b/src/db/DatabaseTable.vala
@@ -21,7 +21,7 @@ public abstract class DatabaseTable {
      * tables are created on demand and tables and columns are easily ignored when already present.
      * However, the change should be noted in upgrade_database() as a comment.
      ***/
-    public const int SCHEMA_VERSION = 20;
+    public const int SCHEMA_VERSION = 21;
     
     protected static Sqlite.Database db;
     
diff --git a/src/db/Db.vala b/src/db/Db.vala
index 3eca8cee..ba565b4b 100644
--- a/src/db/Db.vala
+++ b/src/db/Db.vala
@@ -349,6 +349,21 @@ private VerifyResult upgrade_database(int input_version) {
     //
     
     version = 20;
+
+#if ENABLE_FACES
+    //
+    // Version 21:
+    // * Added face pixels column to FaceLocationTable
+    //
+    
+    if (!DatabaseTable.has_column("FaceLocationTable", "pix")) {
+        message("upgrade_database: adding pix column to FaceLocationTable");
+        if (!DatabaseTable.add_column("FaceLocationTable", "pix", "BLOB"))
+            return VerifyResult.UPGRADE_ERROR;
+    }
+    
+    version = 21;
+#endif
     
     //
     // Finalize the upgrade process
diff --git a/src/db/FaceLocationTable.vala b/src/db/FaceLocationTable.vala
index 14fef4c7..f8132615 100644
--- a/src/db/FaceLocationTable.vala
+++ b/src/db/FaceLocationTable.vala
@@ -29,6 +29,7 @@ public class FaceLocationRow {
     public FaceID face_id;
     public PhotoID photo_id;
     public string geometry;
+    public uint8[] pix;
 }
 
 public class FaceLocationTable : DatabaseTable {
@@ -44,7 +45,8 @@ public class FaceLocationTable : DatabaseTable {
             + "id INTEGER NOT NULL PRIMARY KEY, "
             + "face_id INTEGER NOT NULL, "
             + "photo_id INTEGER NOT NULL, "
-            + "geometry TEXT"
+            + "geometry TEXT, "
+            + "pix BLOB"
             + ")", -1, out stmt);
         assert(res == Sqlite.OK);
         
@@ -60,10 +62,10 @@ public class FaceLocationTable : DatabaseTable {
         return instance;
     }
  
-    public FaceLocationRow add(FaceID face_id, PhotoID photo_id, string geometry) throws DatabaseError {
+    public FaceLocationRow add(FaceID face_id, PhotoID photo_id, string geometry, uint8[]? pix = null) 
throws DatabaseError {
         Sqlite.Statement stmt;
         int res = db.prepare_v2(
-            "INSERT INTO FaceLocationTable (face_id, photo_id, geometry) VALUES (?, ?, ?)",
+            "INSERT INTO FaceLocationTable (face_id, photo_id, geometry, pix) VALUES (?, ?, ?, ?)",
              -1, out stmt);
         assert(res == Sqlite.OK);
         
@@ -73,6 +75,13 @@ public class FaceLocationTable : DatabaseTable {
         assert(res == Sqlite.OK);
         res = stmt.bind_text(3, geometry);
         assert(res == Sqlite.OK);
+        message("face table pix len %d", pix.length);
+        if (pix == null) {
+            res = stmt.bind_blob(4, null, 0);
+        } else {
+            res = stmt.bind_blob(4, pix, pix.length);
+        }
+        assert(res == Sqlite.OK);
         
         res = stmt.step();
         if (res != Sqlite.DONE)
@@ -83,6 +92,7 @@ public class FaceLocationTable : DatabaseTable {
         row.face_id = face_id;
         row.photo_id = photo_id;
         row.geometry = geometry;
+        row.pix = pix;
         
         return row;
     }
@@ -165,6 +175,28 @@ public class FaceLocationTable : DatabaseTable {
         return stmt.column_text(0);
     }
     
+    public uint8[]? get_face_source_pix(Face face, MediaSource source)
+        throws DatabaseError {
+        Sqlite.Statement stmt;
+        int res = db.prepare_v2(
+            "SELECT pix FROM FaceLocationTable WHERE face_id=? AND photo_id=?",
+            -1, out stmt);
+        assert(res == Sqlite.OK);
+        
+        res = stmt.bind_int64(1, face.get_instance_id());
+        assert(res == Sqlite.OK);
+        res = stmt.bind_int64(2, ((Photo) source).get_instance_id());
+        assert(res == Sqlite.OK);
+        
+        res = stmt.step();
+        if (res == Sqlite.DONE)
+            return null;
+        else if (res != Sqlite.ROW)
+            throw_error("FaceLocationTable.get_face_source_pix", res);
+        
+        return (uint8[])stmt.column_blob(0);
+    }
+    
     public void remove_face_from_source(FaceID face_id, PhotoID photo_id) throws DatabaseError {
         Sqlite.Statement stmt;
         int res = db.prepare_v2(
@@ -197,6 +229,25 @@ public class FaceLocationTable : DatabaseTable {
         if (res != Sqlite.DONE)
             throw_error("FaceLocationTable.update_face_location_serialized_geometry", res);
     }
+
+    public void update_face_location_face_data(FaceLocation face_location)
+        throws DatabaseError {
+        Sqlite.Statement stmt;
+        int res = db.prepare_v2("UPDATE FaceLocationTable SET geometry=?, pix=? WHERE id=?", -1, out stmt);
+        assert(res == Sqlite.OK);
+
+        FaceLocationData face_data = face_location.get_face_data();
+        res = stmt.bind_text(1, face_data.geometry);
+        assert(res == Sqlite.OK);
+        res = stmt.bind_blob(2, face_data.pixbuf, face_data.pixbuf.length);
+        assert(res == Sqlite.OK);
+        res = stmt.bind_int64(3, face_location.get_face_location_id().id);
+        assert(res == Sqlite.OK);
+        
+        res = stmt.step();
+        if (res != Sqlite.DONE)
+            throw_error("FaceLocationTable.update_face_location_serialized_geometry", res);
+    }
 }
 
 #endif
diff --git a/src/faces/Face.vala b/src/faces/Face.vala
index 722e1727..2443df03 100644
--- a/src/faces/Face.vala
+++ b/src/faces/Face.vala
@@ -417,7 +417,7 @@ public class Face : DataSource, ContainerSource, Proxyable, Indexable {
         
         return face;
     }
-    
+
     // Utility function to cleanup a face name that comes from user input and prepare it for use
     // in the system and storage in the database.  Returns null if the name is unacceptable.
     public static string? prep_face_name(string name) {
diff --git a/src/faces/FaceLocation.vala b/src/faces/FaceLocation.vala
index cc5c4cf3..fce8e99e 100644
--- a/src/faces/FaceLocation.vala
+++ b/src/faces/FaceLocation.vala
@@ -6,6 +6,12 @@
 
 #if ENABLE_FACES
 
+// Encapsulate geometry and pixels of a Face
+public struct FaceLocationData {
+    public string geometry;
+    public uint8[] pixbuf;
+}
+
 public class FaceLocation : Object {
     
     private static Gee.Map<FaceID?, Gee.Map<PhotoID?, FaceLocation>> face_photos_map;
@@ -14,17 +20,17 @@ public class FaceLocation : Object {
     private FaceLocationID face_location_id;
     private FaceID face_id;
     private PhotoID photo_id;
-    private string geometry;
-    
+    private FaceLocationData face_data;
+
     private FaceLocation(FaceLocationID face_location_id, FaceID face_id, PhotoID photo_id,
-    string geometry) {
+            FaceLocationData face_data) {
         this.face_location_id = face_location_id;
         this.face_id = face_id;
         this.photo_id = photo_id;
-        this.geometry = geometry;
+        this.face_data = face_data;
     }
     
-    public static FaceLocation create(FaceID face_id, PhotoID photo_id, string geometry) {
+    public static FaceLocation create(FaceID face_id, PhotoID photo_id, FaceLocationData face_data) {
         FaceLocation face_location = null;
         
         // Test if that FaceLocation already exists (that face in that photo) ...
@@ -35,12 +41,11 @@ public class FaceLocation : Object {
             
             face_location = faces_map.get(face_id);
             
-            if (face_location.get_serialized_geometry() != geometry) {
-                face_location.set_serialized_geometry(geometry);
+            if (face_location.get_serialized_geometry() != face_data.geometry) {
+                face_location.set_face_data(face_data);
                 
                 try {
-                    FaceLocationTable.get_instance().update_face_location_serialized_geometry(
-                        face_location);
+                    FaceLocationTable.get_instance().update_face_location_face_data(face_location);
                 } catch (DatabaseError err) {
                     AppWindow.database_error(err);
                 }
@@ -53,7 +58,7 @@ public class FaceLocation : Object {
         try {
             face_location =
                 FaceLocation.add_from_row(
-                    FaceLocationTable.get_instance().add(face_id, photo_id, geometry));
+                    FaceLocationTable.get_instance().add(face_id, photo_id, face_data.geometry, 
face_data.pixbuf));
         } catch (DatabaseError err) {
             AppWindow.database_error(err);
         }
@@ -86,7 +91,8 @@ public class FaceLocation : Object {
     public static FaceLocation add_from_row(FaceLocationRow row) {
         
         FaceLocation face_location =
-            new FaceLocation(row.face_location_id, row.face_id, row.photo_id, row.geometry);
+            new FaceLocation(row.face_location_id, row.face_id, row.photo_id,
+                { row.geometry, row.pix });
         
         Gee.Map<PhotoID?, FaceLocation> photos_map = face_photos_map.get(row.face_id);
         if (photos_map == null) {photos_map = new Gee.HashMap<PhotoID?, FaceLocation>
@@ -198,11 +204,19 @@ public class FaceLocation : Object {
     }
     
     public string get_serialized_geometry() {
-        return geometry;
+        return face_data.geometry;
     }
-    
+/*    
     private void set_serialized_geometry(string geometry) {
-        this.geometry = geometry;
+        this.face_data.geometry = geometry;
+    }
+*/
+    public FaceLocationData get_face_data() {
+        return face_data;
+    }
+    
+    private void set_face_data(FaceLocationData face_data) {
+        this.face_data = face_data;
     }
 }
 
diff --git a/src/faces/FaceShape.vala b/src/faces/FaceShape.vala
index c14b43b3..0afc5a72 100644
--- a/src/faces/FaceShape.vala
+++ b/src/faces/FaceShape.vala
@@ -172,6 +172,7 @@ public abstract class FaceShape : Object {
     public abstract bool cursor_is_over(int x, int y);
     public abstract bool equals(FaceShape face_shape);
     public abstract double get_distance(int x, int y);
+    public abstract Gdk.Pixbuf? get_pixbuf();
     
     protected abstract void paint();
     protected abstract void erase();
@@ -191,6 +192,7 @@ public class FaceRectangle : FaceShape {
     private Cairo.Context thin_white_ctx = null;
     private int last_grab_x = -1;
     private int last_grab_y = -1;
+    private Gdk.Pixbuf? face_pix;
     
     public FaceRectangle(EditingTools.PhotoCanvas canvas, int x, int y,
         int half_width = NULL_SIZE, int half_height = NULL_SIZE) {
@@ -215,6 +217,12 @@ public class FaceRectangle : FaceShape {
         
             box = Box(x - half_width, y - half_height, right, bottom);
         }
+        
+        Gdk.Pixbuf original = canvas.get_scaled_pixbuf();
+        message("pixbuf get %d, %d, %d, %d of %d, %d", box.left, box.top,
+                                box.get_width(), box.get_height(), original.width, original.height);
+        face_pix = new Gdk.Pixbuf.subpixbuf(original, box.left, box.top,
+                                box.get_width(), box.get_height());
     }
     
     ~FaceRectangle() {
@@ -778,6 +786,10 @@ public class FaceRectangle : FaceShape {
         
         return Math.sqrt((center_x - x) * (center_x - x) + (center_y - y) * (center_y - y));
     }
+
+    public override Gdk.Pixbuf? get_pixbuf() {
+        return face_pix;
+    }
 }
 
 #endif
diff --git a/src/faces/FacesTool.vala b/src/faces/FacesTool.vala
index 00a8b7e0..2c5faf6a 100644
--- a/src/faces/FacesTool.vala
+++ b/src/faces/FacesTool.vala
@@ -314,14 +314,16 @@ public class FacesTool : EditingTools.EditingTool {
     private class FaceDetectionJob : BackgroundJob {
         private Gee.Queue<string> faces = null;
         private string image_path;
+        private float scale;
         public string? spawnError;
 
-        public FaceDetectionJob(FacesToolWindow owner, string image_path,
+        public FaceDetectionJob(FacesToolWindow owner, string image_path, float scale,
             CompletionCallback completion_callback, Cancellable cancellable,
             CancellationCallback cancellation_callback) {
             base(owner, completion_callback, cancellable, cancellation_callback);
 
             this.image_path = image_path;
+            this.scale = scale;
         }
 
         public override void execute() {
@@ -331,7 +333,8 @@ public class FacesTool : EditingTools.EditingTool {
             }
             FaceRect[] rects;
             try {
-                rects = FaceDetect.interface.detect_faces(image_path, 
AppDirs.get_haarcascade_file().get_path(), 1.3);
+                rects = FaceDetect.interface.detect_faces(image_path,
+                                                          AppDirs.get_haarcascade_file().get_path(), scale);
             } catch(Error e) {
                 spawnError = "DBus error: " + e.message + "!\n";
                 return;
@@ -416,6 +419,7 @@ public class FacesTool : EditingTools.EditingTool {
     private Workers workers;
     private FaceShape editing_face_shape = null;
     private FacesToolWindow faces_tool_window = null;
+    private const int FACE_DETECT_MAX_WIDTH = 1200;
 
     private FacesTool() {
         base("FacesTool");
@@ -468,9 +472,12 @@ public class FacesTool : EditingTools.EditingTool {
 
         face_detection_cancellable = new Cancellable();
         workers = new Workers(1, false);
+        Dimensions dimensions = canvas.get_photo().get_dimensions();
+        float scale_factor = (float)dimensions.width / FACE_DETECT_MAX_WIDTH;
         face_detection = new FaceDetectionJob(faces_tool_window,
-            canvas.get_photo().get_file().get_path(), on_faces_detected,
-            face_detection_cancellable, on_detection_cancelled);
+                                              canvas.get_photo().get_file().get_path(), scale_factor,
+                                              on_faces_detected,
+                                              face_detection_cancellable, on_detection_cancelled);
 
         bind_window_handlers();
 
@@ -750,14 +757,23 @@ public class FacesTool : EditingTools.EditingTool {
         if (face_shapes == null)
             return;
 
-        Gee.Map<Face, string> new_faces = new Gee.HashMap<Face, string>();
+        Gee.Map<Face, FaceLocationData?> new_faces = new Gee.HashMap<Face, FaceLocationData?>();
         foreach (FaceShape face_shape in face_shapes.values) {
             if (!face_shape.get_known())
                 continue;
 
             Face new_face = Face.for_name(face_shape.get_name());
-
-            new_faces.set(new_face, face_shape.serialize());
+            uint8[] face_pix;
+            try {
+                face_shape.get_pixbuf().save_to_buffer(out face_pix, "png");
+                message("face pix length %d", face_pix.length);
+            } catch (Error e) {
+                AppWindow.error_message("Cannot get pixbuf for image\n");
+            }
+            FaceLocationData face_data = {
+                face_shape.serialize(), face_pix
+            };
+            new_faces.set(new_face, face_data);
         }
 
         ModifyFacesCommand command = new ModifyFacesCommand(canvas.get_photo(), new_faces);
diff --git a/thumbnailer/shotwell-video-thumbnailer.vala b/thumbnailer/shotwell-video-thumbnailer.vala
index 2b381a6a..94e24515 100644
--- a/thumbnailer/shotwell-video-thumbnailer.vala
+++ b/thumbnailer/shotwell-video-thumbnailer.vala
@@ -54,10 +54,10 @@ class ShotwellThumbnailer {
             // Set to PAUSED to make the first frame arrive in the sink.
             ret = pipeline.set_state(Gst.State.PAUSED);
             if (ret == Gst.StateChangeReturn.FAILURE) {
-                stderr.printf("Failed to play the file: couldn't set state\n");
+                debug("Failed to play the file: couldn't set state\n");
                 return 3;
             } else if (ret == Gst.StateChangeReturn.NO_PREROLL) {
-                stderr.printf("Live sources not supported yet.\n");
+                debug("Live sources not supported yet.\n");
                 return 4;
             }
             
@@ -66,13 +66,13 @@ class ShotwellThumbnailer {
             // better way is to run a mainloop and catch errors there.
             ret = pipeline.get_state(null, null, 5 * Gst.SECOND);
             if (ret == Gst.StateChangeReturn.FAILURE) {
-                stderr.printf("Failed to play the file: couldn't get state.\n");
+                debug("Failed to play the file: couldn't get state.\n");
                 return 3;
             }
 
             /* get the duration */
             if (!pipeline.query_duration (Gst.Format.TIME, out duration)) {
-                stderr.printf("Failed to query file for duration\n");
+                debug("Failed to query file for duration\n");
                 return 3;
             }
 
@@ -86,7 +86,7 @@ class ShotwellThumbnailer {
 
             ret = pipeline.get_state(null, null, 5 * Gst.SECOND);
             if (ret == Gst.StateChangeReturn.FAILURE) {
-                stderr.printf("Failed to play the file: couldn't get state.\n");
+                debug("Failed to play the file: couldn't get state.\n");
                 return 3;
             }
 
@@ -100,7 +100,7 @@ class ShotwellThumbnailer {
             pipeline.set_state(Gst.State.NULL);
             
         } catch (Error e) {
-            stderr.printf(e.message);
+            debug(e.message);
             return 2;
         }
         


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]