415 lines
18 KiB
Plaintext
415 lines
18 KiB
Plaintext
### GENERAL
|
|
* recent jobs is slow, think its this 'sql':
|
|
jobs = Job.query.filter( Job.last_update >= (func.now() - func.cast(concat(settings.job_archive_age, 'DAYS'), INTERVAL)) ).order_by(Job.id.desc()).all()
|
|
I'm guessing remove the funcs and make the sql just grab last 50 or something simpler
|
|
|
|
* add a reference image from existing image, or even "replace? ref image with this one" <- new menu option
|
|
- started on this in both jscript and python
|
|
-- believe I should definitively add person id to jscript (so thats in files)
|
|
-- then pass that through the context menut to the route and update the DB
|
|
|
|
* rm dups job should show progress bar
|
|
* in viewer, there is no move button (maybe add one?)
|
|
* consider doing duplicates before AI, and if there are say 100s+, then maybe pause the AI work
|
|
- had 5000+ new photos, took 8 hours to finish, for me to just delete them anyway
|
|
* optimisation:
|
|
- keep track of just new files since scan (even if we did this from the DB),
|
|
then we could just feed those eid's explicitly into a 'get_file_details_on_new_files'.
|
|
- ALSO use new_eids list IF no new refimgs to do a 'run_ai_on_new_files' :)
|
|
|
|
* allow changing dates in move dbox and then re-scan for existing folders OR just have a browse for existing...
|
|
- for use on scanned photos, they register as 2010, but are datestamped in visuals for 95
|
|
(is there a library for this???)
|
|
|
|
* sqlalchemy 2 migration:
|
|
* fix unmapped (in fact make all the code properly sqlachemy 2.0 compliant)
|
|
-- path.py has the __allow_unmapped__ = True
|
|
* remove all '.execute' from *.py
|
|
|
|
* allow actions for wrong person:
|
|
-> someone else? OR override no match for this person ever for this image?
|
|
* groups of persons (non-exclusive, recursive), so:
|
|
- fam (ddp, mandy, cam, mich)
|
|
- mandy_fam (mandy, kirsty, tony, anne, tenille)
|
|
- uncle_peters_fam (peter, joan, jarrod, aaron, alana)
|
|
- cousin_aarons_fam (aaron, karly, kai, etc)
|
|
- storviks (mandy_fam, uncle_peters_fam, cousin_aarons_fam, etc)
|
|
|
|
* birthdates in PA:
|
|
- M was interested in birthdate appearing in hass, so need an API /
|
|
sensor in hass too - maybe could store (optional) birthdate and
|
|
heads up (of X days, e.g. so can get present) or just come up on the day so you can remember to text them
|
|
* search logic (AND vs OR)
|
|
|
|
* read this: https://flask.palletsprojects.com/en/2.2.x/testing/#faking-resources-and-context
|
|
|
|
* ignore face should ignore ALL matching faces (re: Declan)
|
|
|
|
* should allow context menu from View thumbs (particularly useful on search) to show other files around this one by date (maybe that folder or something?)
|
|
|
|
* folder manipulation
|
|
- does search of matching dirname give all entries of subdirs of subdirs, etc. (think not)
|
|
- delete
|
|
- rename (does this work already somehow? see issue below)
|
|
- dont allow me to stupidly move a folder to itself
|
|
|
|
* browser back/forward buttons dont work -- use POST -> redirect to GET
|
|
- need some sort of clean-up of pa_user_state -- I spose its triggered by browser session, so maybe just after a week is lazy/good enough
|
|
- pa_user_state has last_used as a timestamp so can be used to delete old entries
|
|
|
|
* back button will fail if we do these POSTs:
|
|
job.py:@app.route("/jobs", methods=["GET", "POST"])
|
|
job.py:@app.route("/job/<id>", methods=["GET","POST"])
|
|
-- these need to store 'job prefs' somewhere... (extras?)
|
|
* if on jobs page and jobs increase, then 'rebuild' the content of the page to show new jobs, and potentially do that every 5 seconds...
|
|
- THINK: could also 'refresh' /job/id via Ajax not a reload, to avoid the POST issue above needing to remember job prefs somewhere?
|
|
|
|
files.py:@app.route("/fix_dups", methods=["POST"])
|
|
???
|
|
|
|
* GUI overhaul?
|
|
* on a phone, the files.html page header is a mess "Oldest.." line is too large to fit on 1 line (make it a hamburger?)
|
|
- searched for text overlaps buttons above and below
|
|
- < 10 files > is subsequently not centered
|
|
- the folder/bin icons might be best below search then? (and on same line as XS/S, etc.)
|
|
* on phone login page, job* pages, etc. are all squished
|
|
* when search, have a way to hide deleted files
|
|
-> not sure where to put this on GUI, its so busy...
|
|
|
|
* comment your code -> only html files remaining
|
|
|
|
* dup issues:
|
|
* when we have lots of dups, sort the directories by alpha so its consistent when choosing
|
|
|
|
* fix up logging in general
|
|
ProcessFileForJob --> really need to better handle log levels and counting
|
|
|
|
* video player cannot handle non mp4 formats... do I care? (could just offer a download link and hope the client deals with it)
|
|
--> OR? https://jsmpeg.com/
|
|
--> OR? convert all videos to mp4/webm
|
|
|
|
* support animated gifs in html5 canvas
|
|
|
|
* read that guys face matching / clustering / nearest neighbour examples, for a whole new AI capability
|
|
https://www.pyimagesearch.com/2018/07/09/face-clustering-with-python/
|
|
|
|
### DB
|
|
* Dir can have date in the DB, so we can do Oldest/Newest dirs in Folder view
|
|
|
|
### BACKEND
|
|
* revisit SymlinkName() and make it simpler (see comment in shared.py)
|
|
|
|
*** Need to use thread-safe sessions per Thread, half-assed version did not work
|
|
|
|
Admin
|
|
-> do I want to have admin roles/users?
|
|
-> purge deleted files (and associated DB data) needs a dbox or privs
|
|
(only show in DEV for now)
|
|
|
|
### AI
|
|
* faces per file - need a threshold for too many?
|
|
|
|
### UI
|
|
* viewer needs to allow toggle to scan_model (and prob. right-click on file... AI (with CNN) AI (with hog)
|
|
- make the form-select AI_Model actually do the change (but need more mem on mara really -- even mem is not enough
|
|
need graphic support --> need to allow pa_job_manager run on borric with acceleration)
|
|
- test this with new CPU in mara
|
|
- test this on borric for comparison
|
|
|
|
For AI / rescan:
|
|
way to override per file:
|
|
the threshold used?
|
|
|
|
file details is sort of crap - only works on import path
|
|
- probably better to have a different 'view', e.g. folders/flat/detailed
|
|
|
|
timelineview? (I think maybe sunburst for large amounts of files, then maybe something more timeline-series for drilling in?)
|
|
(vertical timeline, date has thumbnails (small) horizontally along
|
|
a page, etc.?
|
|
https://www.highcharts.com/docs/chart-and-series-types/timeline-series
|
|
https://www.highcharts.com/demo/sunburst
|
|
https://www.highcharts.com/demo/heatmap
|
|
https://www.highcharts.com/demo/packed-bubble-split
|
|
|
|
|
|
### SORTER
|
|
* exif processing?
|
|
* location stuff - test a new photo from my camera out
|
|
-- image is in dir, need to look at exifread output
|
|
|
|
### FUTURE:
|
|
* real first-run, 'no or empty settings' -- need to think this through
|
|
- should set base_path to /pa (and then either /pa or /pa/import, /pa/storage, etc. can be 1 or more volume(s) in docker)
|
|
|
|
* deal with changing a path in settings
|
|
|
|
* can emby use nfo for images (for AI/tags?)
|
|
-NO sadly
|
|
|
|
* work out why gitignore does not ignore say .pa_bin but has to ignore .pa_metada
|
|
|
|
#### CHAT GPT f/b
|
|
|
|
docstrings for documentation / example:
|
|
|
|
def AddRefimgToPerson(person, refimg):
|
|
"""
|
|
Adds a reference image to a person object's list of reference images.
|
|
|
|
Args:
|
|
person (Person): A Person object to which the reference image should be added.
|
|
refimg (str): The filepath to the reference image to be added.
|
|
|
|
Returns:
|
|
None
|
|
|
|
Raises:
|
|
ValueError: If person or refimg are None or empty strings.
|
|
TypeError: If person is not an instance of the Person class.
|
|
|
|
Example:
|
|
person = Person(name="John Doe")
|
|
AddRefimgToPerson(person, "/path/to/reference/image.jpg")
|
|
"""
|
|
if not person:
|
|
raise ValueError("person is None or empty")
|
|
if not refimg:
|
|
raise ValueError("refimg is None or empty")
|
|
|
|
if not isinstance(person, Person):
|
|
raise TypeError("person must be an instance of the Person class")
|
|
|
|
person.add_reference_image(refimg)
|
|
|
|
|
|
|
|
TESTING:
|
|
|
|
def test_AddRefimgToPerson():
|
|
# Create a test person object
|
|
person = Person(name='John Doe', age=30)
|
|
|
|
# Create a test image object
|
|
image = Image(file_path='test.jpg', caption='Test image')
|
|
|
|
# Call the function to add the image to the person
|
|
AddRefimgToPerson(person, image)
|
|
|
|
# Check that the image was added to the person's list of images
|
|
assert image in person.images, "Image was not added to person's list of images"
|
|
|
|
# Check that the image's reference to the person is set correctly
|
|
assert image.person == person, "Image's reference to person is not set correctly"
|
|
|
|
TYPING:
|
|
look at from typing, e.g. ...
|
|
|
|
from typing import Type
|
|
|
|
def get_person_info(person: Type[Person]) -> str:
|
|
|
|
|
|
OR:
|
|
|
|
|
|
from typing import List
|
|
|
|
def AddRefimgToPerson(person_id: int, ref_images: List[str]) -> bool:
|
|
"""
|
|
Adds reference images to a person's profile.
|
|
|
|
Args:
|
|
person_id (int): The ID of the person whose profile should be updated.
|
|
ref_images (List[str]): A list of file paths or URLs for the reference images to add.
|
|
|
|
Returns:
|
|
bool: True if the images were successfully added, False otherwise.
|
|
|
|
Raises:
|
|
ValueError: If person_id is not a positive integer or if ref_images is empty or contains
|
|
any non-string values.
|
|
|
|
Example:
|
|
>>> AddRefimgToPerson(123, ['http://example.com/img1.jpg', '/path/to/img2.png'])
|
|
True
|
|
"""
|
|
|
|
|
|
|
|
|
|
checkout deepface:
|
|
|
|
|
|
User: I have this code I wrote using pythons face_recognition library, but I would like to alter it to work with deepface. My code is: im = face_recognition.load_image_file(e.FullPathOnFS())
|
|
face_locations = face_recognition.face_locations(im, model=model.name )
|
|
unknown_encodings = face_recognition.face_encodings(im, known_face_locations=face_locations)
|
|
|
|
Assistant: To alter your existing code to work with **DeepFace**, the changes involve replacing the methods of the `face_recognition` library with similar functionalities provided by DeepFace.
|
|
|
|
In **DeepFace**, the equivalent functionalities include:
|
|
|
|
1. **Detecting faces**: This is automatically handled by the `analyze` method in DeepFace. You don't need to manually specify the face locations.
|
|
|
|
2. **Encoding faces**: DeepFace internally computes embeddings for faces through its supported models (like VGG-Face, Facenet, OpenFace, DeepID, etc.).
|
|
|
|
Here's how you can modify your code to use DeepFace instead of `face_recognition`:
|
|
|
|
### Original Code (using `face_recognition`):
|
|
```python
|
|
im = face_recognition.load_image_file(e.FullPathOnFS())
|
|
face_locations = face_recognition.face_locations(im, model=model.name)
|
|
unknown_encodings = face_recognition.face_encodings(im, known_face_locations=face_locations)
|
|
```
|
|
|
|
### Modified Code (using `DeepFace`):
|
|
```python
|
|
from deepface import DeepFace
|
|
import cv2
|
|
|
|
# Load the image
|
|
im = cv2.imread(e.FullPathOnFS())
|
|
|
|
# Analyze the image to get face embeddings (encodings) and other details
|
|
results = DeepFace.represent(img_path=e.FullPathOnFS(), model_name=model.name)
|
|
|
|
# `results` will contain a list of dictionaries where each dictionary contains the embedding (encoding) of a detected face.
|
|
# Example of accessing the embeddings (face encodings)
|
|
unknown_encodings = [res['embedding'] for res in results]
|
|
```
|
|
|
|
### Explanation:
|
|
|
|
- `DeepFace.represent()` takes the path to the image (`e.FullPathOnFS()`) and the name of the model (`model.name`). It processes the image and extracts the embeddings (encodings) for each face detected.
|
|
|
|
- **Note**: In DeepFace, the model name can be one of several options: `VGG-Face`, `Facenet`, `OpenFace`, `DeepFace`, `DeepID`, etc. Make sure that `model.name` corresponds to one of these supported models.
|
|
|
|
- The result is a list of dictionaries. Each dictionary contains a detected face's encoding (embedding), which you can access using the `'embedding'` key.
|
|
|
|
DeepFace abstracts away a lot of the manual face detection work that you were handling with `face_recognition`. Let me know if you'd like to handle anything else, like visualizing the detected faces or working with other DeepFace models.
|
|
|
|
|
|
User: I want to use Deepface to take a set of reference images and match against all the images I have to find the best matches, how would you go about that?
|
|
|
|
Assistant: To use **DeepFace** to match a set of reference images against other images to find the best matches, you can follow these general steps:
|
|
|
|
1. **Compute embeddings (encodings) for the reference images**.
|
|
2. **Compute embeddings for the target images** (the ones you're searching through).
|
|
3. **Compare the embeddings** of the reference images with those of the target images to find the best matches based on a distance metric.
|
|
4. **Rank the matches** by similarity and return the best ones.
|
|
|
|
Here's a step-by-step guide on how you can achieve this with **DeepFace**:
|
|
|
|
### Step 1: Compute embeddings for reference images
|
|
You will load the reference images, compute their embeddings using DeepFace, and store them.
|
|
|
|
### Step 2: Compute embeddings for all target images
|
|
Do the same for the target images you want to match against.
|
|
|
|
### Step 3: Compare embeddings
|
|
You will compare the embeddings from the reference images to the embeddings of each target image. The smaller the distance between the embeddings, the more similar the faces are.
|
|
|
|
### Step 4: Return the best matches
|
|
|
|
### Example Code
|
|
|
|
```python
|
|
from deepface import DeepFace
|
|
import os
|
|
import cv2
|
|
import numpy as np
|
|
|
|
# Function to compute embeddings for a given image path
|
|
def compute_embeddings(image_path, model_name='VGG-Face'):
|
|
try:
|
|
# This returns a list of embeddings for each face found in the image
|
|
embeddings = DeepFace.represent(img_path=image_path, model_name=model_name)
|
|
return [res['embedding'] for res in embeddings]
|
|
except Exception as e:
|
|
print(f"Error processing image {image_path}: {e}")
|
|
return []
|
|
|
|
# Function to find the best matches between reference and target images
|
|
def find_best_matches(reference_images, target_images, model_name='VGG-Face', metric='cosine'):
|
|
# Store embeddings for reference images
|
|
reference_embeddings = {}
|
|
|
|
for ref_img in reference_images:
|
|
ref_embeddings = compute_embeddings(ref_img, model_name=model_name)
|
|
if ref_embeddings:
|
|
reference_embeddings[ref_img] = ref_embeddings
|
|
|
|
# Store best matches
|
|
best_matches = []
|
|
|
|
# Loop through each target image
|
|
for target_img in target_images:
|
|
target_embeddings = compute_embeddings(target_img, model_name=model_name)
|
|
|
|
for target_emb in target_embeddings:
|
|
for ref_img, ref_emb_list in reference_embeddings.items():
|
|
# Compare each reference embedding with the target embedding
|
|
for ref_emb in ref_emb_list:
|
|
# Compute the distance between embeddings using DeepFace's distance functions
|
|
distance = DeepFace.find_distance(ref_emb, target_emb, distance_metric=metric)
|
|
|
|
# Store match details (target image, reference image, and distance)
|
|
best_matches.append({
|
|
'target_image': target_img,
|
|
'reference_image': ref_img,
|
|
'distance': distance
|
|
})
|
|
|
|
# Sort matches by the distance (smallest distances indicate best matches)
|
|
best_matches = sorted(best_matches, key=lambda x: x['distance'])
|
|
|
|
return best_matches
|
|
|
|
# Example usage
|
|
reference_images = [
|
|
"path_to_reference_image1.jpg",
|
|
"path_to_reference_image2.jpg"
|
|
]
|
|
|
|
target_images = [
|
|
"path_to_target_image1.jpg",
|
|
"path_to_target_image2.jpg",
|
|
# Add more target images here...
|
|
]
|
|
|
|
# Find the best matches
|
|
matches = find_best_matches(reference_images, target_images, model_name='VGG-Face', metric='cosine')
|
|
|
|
# Display the best match
|
|
for match in matches[:5]: # Display top 5 matches
|
|
print(f"Reference Image: {match['reference_image']}, Target Image: {match['target_image']}, Distance: {match['distance']}")
|
|
```
|
|
|
|
### Explanation:
|
|
|
|
1. **`compute_embeddings(image_path, model_name)`**: This function uses DeepFace's `represent` function to compute face embeddings for each face found in the image.
|
|
|
|
2. **`find_best_matches()`**:
|
|
- It first computes and stores embeddings for all reference images in the `reference_embeddings` dictionary.
|
|
- For each target image, it computes the embeddings, then compares those embeddings with all the reference embeddings using the chosen distance metric (default is `'cosine'`).
|
|
- The function then stores the matches and sorts them by distance (the smaller the distance, the closer the match).
|
|
|
|
3. **Distance metrics**: You can choose between several distance metrics like `'cosine'`, `'euclidean'`, or `'euclidean_l2'`, depending on the model and your preference.
|
|
|
|
4. **Matching logic**: It finds the best matches by comparing each face embedding in the target images with each face embedding in the reference images and storing the distances.
|
|
|
|
### DeepFace Distance Metrics:
|
|
|
|
- **Cosine**: Measures the cosine of the angle between two vectors (common for face embeddings).
|
|
- **Euclidean**: Measures the straight-line distance between two vectors.
|
|
- **Euclidean L2**: A variant of Euclidean where embeddings are normalized before distance calculation.
|
|
|
|
### Example of top match output:
|
|
```bash
|
|
Reference Image: path_to_reference_image1.jpg, Target Image: path_to_target_image1.jpg, Distance: 0.256
|
|
Reference Image: path_to_reference_image2.jpg, Target Image: path_to_target_image2.jpg, Distance: 0.310
|
|
...
|
|
```
|
|
|
|
This approach will give you the best matching images based on facial similarity. You can adjust the model used (`VGG-Face`, `Facenet`, etc.) and the distance metric to fine-tune results. Let me know if you'd like to visualize the results or handle larger datasets more efficiently!
|
|
|