Files
photoassistant/TODO

407 lines
17 KiB
Plaintext

? get rid of style and just use class -- think this should work, so change in
templates/files.html for throbber, etc. and dont set style as much in view_support.js
### GENERAL
* jobs for AI should show path name
* rm dups job should show progress bar
* in viewer, there is no move button (maybe add one?)
* think I killed pa_job_manager without passing an eid to a transform job, shouldn't crash
- SHOULD JUST get AI to help clean-up and write defensive code here...
* 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
* consider how to better version jscript - across all html files, consistently
- mtime, didnt work anyway, my phone still wont pick up the change, it was adding any ?v= changed this (once)
* 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:
- get AI to help
* 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
* 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!