-
Notifications
You must be signed in to change notification settings - Fork 321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[py-tx] Add rotation reference to brute force lookup #1669
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this! I like a lot of the interface proposals, including the new rotation enum.
I spoke to Eric offline, who had look into the PDQ algorithm, and I think there might be a problem with the approach that is hard to fix, though I think you have made some of the right supporting interface.
Namely, my assertion that you can "rotate" a PDQ hash may not actually be true. The rotation code you have copied evaluates rotations on the DCT matrix and not the PDQ hash itself. The output of the DCT -> PDQ looks like it might be destroying information, and so there might not be a valid equivalence from image -> rotate -> hash and image -> hash -> rotate.
We can still get the same effects as rotating the hash, but we may have to do it in more steps (we can discuss here async or over a meeting).
- Simplest option - move rotating the image as a helper in PhotoContent, then just hash all 8 rotations (we may want to do this anyways), but we will need to go in more steps.
- Requires research - Using solution 1 or by mathing out the matrix work + torbens in the PDQ code, we can check to see whether there are actually valid rotations of the PDQ hash itself, and then resuming from there.
In either case, I think we should retain your proposed enum, and add some functionality
Top level changes suggested in current version of code:
- Let's avoid adding it to the base-level interface, because this doesn't apply to text, and since it's not universal I don't think we should add it just yet - instead add it to PDQ distance
- For your test plan, look into how we might demonstrate this using either the
hash
ormatch
commands.
FLIPPLUS1 = auto() | ||
FLIPMINUS1 = auto() | ||
|
||
class PDQHashRotations: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ymmv: python modules are already pretty namepsaced, so there's usually not an additional benefit to using classes like this unless.
What do you think about removing this wrapping class and having them just be functions in this module? The only one that is likely to be called by the caller is the rotate(hash, rotation_type) version.
) | ||
|
||
rotator = PDQHashRotations() | ||
rotations = rotator._try_all_rotation(hash2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blocking: by naming this function with a leading underscore (_try_...
), you are signaling you don't want it to be used outside of the module. However, you are then using it outside of the module!
@@ -68,10 +68,28 @@ def compare_hash( | |||
hash1: str, | |||
hash2: str, | |||
pdq_dist_threshold: int = PDQ_CONFIDENT_MATCH_THRESHOLD, | |||
try_rotation: bool = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's default this to being true for now, though we may need to come back to it in the future,
) | ||
|
||
@classmethod | ||
def from_dist( | ||
cls, dist: index.SignalSimilarityInfo, threshold: index.SignalSimilarityInfo | ||
cls, dist: index.SignalSimilarityInfo, threshold: index.SignalSimilarityInfo, rotation_type: RotationType = RotationType.ORIGINAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blocking: I like this idea! However, this concept applies to more than just photos and videos, the big alternatives being URLs and Text, where rotations doesn't make sense, so I don't think it should go onto the base class at this time.
However, you should add it to PDQ distnace.
@@ -0,0 +1,98 @@ | |||
# Copyright (c) Meta Platforms, Inc. and affiliates. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for writing tests!
@@ -0,0 +1,102 @@ | |||
# Copyright (c) Meta Platforms, Inc. and affiliates. | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocking: Add a docstring at the top that links to the explainer for how rotations work and a one line of what this module is for
ORIGINAL = auto() | ||
ROTATE90 = auto() | ||
ROTATE180 = auto() | ||
ROTATE270 = auto() | ||
FLIPX = auto() | ||
FLIPY = auto() | ||
FLIPPLUS1 = auto() | ||
FLIPMINUS1 = auto() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While auto()
is very fancy, it can lead to very challenging bugs when using it with Enum
which can cause the numbers to change if you re-order these! Therefore, I recommend one of the following:
- Use StrEnum instead, which makes
auto()
the lowercase version of the names - Manually type out all the numbers
Also consider adding a brief note explaining these rotations.
import numpy as np | ||
from enum import Enum, auto | ||
|
||
class RotationType(Enum): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am convinced by you that this Enum should be generic, but I think we should move it under content_base instead. Perhaps it can live here for now: https://github.com/facebook/ThreatExchange/blob/main/python-threatexchange/threatexchange/content_type/content_base.py
WDYT?
class TestPDQHashRotations: | ||
SAMPLE_HASH = "f8f8f0cee0f4a84f06370a22038f63f0b36e2ed596621e1d33e6b39c4e9c9b22" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blocking: While this test does test some things, something it doesn't tell us is whether the rotations actually represent the rotated images! We shouldn't land that unless we can prove that.
# Convert hex string to binary | ||
binary = bin(int(hash_str, 16))[2:].zfill(256) | ||
# Convert to 16x16 matrix of floats | ||
return np.array([float(int(b)) for b in binary]).reshape(16, 16) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
q: Why turn to floats?
ignorable: Take a look at some of the other np helpers like frombuffer() to see if you can get something that is a native operation in numpy, which is likely to be implemented as a c binding, which will be faster than doing the equivalent logic in python.
I've just written and tried to run the test cases with compare_hash using the same pictures (with one rotated) and the match result is false. So I confirm that rotating the hash doesn't work. I will now try rotating the image in PhotoContent instead and will update later if this approach works. I will also do some research to see if there's a way to rotate the hash itself |
Thanks for submitting this as a draft @haianhng31 - I often recommend engineers on my team to "have the discussion with code" - it's much easier to talk about concrete things, and I can also give you some pointers on other things you've written. |
@Dcallies Please help me confirm if this is the file I should be working with to rotate the image before hashing & matching and if this approach sounds good:
|
Let's break this up into multiple steps / PRs.
|
Summary
Test Plan