photo-stack-finder 0.1.7__py3-none-any.whl → 0.1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- orchestrator/__init__.py +2 -2
- orchestrator/app.py +6 -11
- orchestrator/build_pipeline.py +19 -21
- orchestrator/orchestrator_runner.py +11 -8
- orchestrator/pipeline_builder.py +126 -126
- orchestrator/pipeline_orchestrator.py +604 -604
- orchestrator/review_persistence.py +162 -162
- orchestrator/static/orchestrator.css +76 -76
- orchestrator/static/orchestrator.html +11 -5
- orchestrator/static/orchestrator.js +3 -1
- overlap_metrics/__init__.py +1 -1
- overlap_metrics/config.py +135 -135
- overlap_metrics/core.py +284 -284
- overlap_metrics/estimators.py +292 -292
- overlap_metrics/metrics.py +307 -307
- overlap_metrics/registry.py +99 -99
- overlap_metrics/utils.py +104 -104
- photo_compare/__init__.py +1 -1
- photo_compare/base.py +285 -285
- photo_compare/config.py +225 -225
- photo_compare/distance.py +15 -15
- photo_compare/feature_methods.py +173 -173
- photo_compare/file_hash.py +29 -29
- photo_compare/hash_methods.py +99 -99
- photo_compare/histogram_methods.py +118 -118
- photo_compare/pixel_methods.py +58 -58
- photo_compare/structural_methods.py +104 -104
- photo_compare/types.py +28 -28
- {photo_stack_finder-0.1.7.dist-info → photo_stack_finder-0.1.8.dist-info}/METADATA +21 -22
- photo_stack_finder-0.1.8.dist-info/RECORD +75 -0
- scripts/orchestrate.py +12 -10
- utils/__init__.py +4 -3
- utils/base_pipeline_stage.py +171 -171
- utils/base_ports.py +176 -176
- utils/benchmark_utils.py +823 -823
- utils/channel.py +74 -74
- utils/comparison_gates.py +40 -21
- utils/compute_benchmarks.py +355 -355
- utils/compute_identical.py +94 -24
- utils/compute_indices.py +235 -235
- utils/compute_perceptual_hash.py +127 -127
- utils/compute_perceptual_match.py +240 -240
- utils/compute_sha_bins.py +64 -20
- utils/compute_template_similarity.py +1 -1
- utils/compute_versions.py +483 -483
- utils/config.py +8 -5
- utils/data_io.py +83 -83
- utils/graph_context.py +44 -44
- utils/logger.py +2 -2
- utils/models.py +2 -2
- utils/photo_file.py +90 -91
- utils/pipeline_graph.py +334 -334
- utils/pipeline_stage.py +408 -408
- utils/plot_helpers.py +123 -123
- utils/ports.py +136 -136
- utils/progress.py +415 -415
- utils/report_builder.py +139 -139
- utils/review_types.py +55 -55
- utils/review_utils.py +10 -19
- utils/sequence.py +10 -8
- utils/sequence_clustering.py +1 -1
- utils/template.py +57 -57
- utils/template_parsing.py +71 -0
- photo_stack_finder-0.1.7.dist-info/RECORD +0 -74
- {photo_stack_finder-0.1.7.dist-info → photo_stack_finder-0.1.8.dist-info}/WHEEL +0 -0
- {photo_stack_finder-0.1.7.dist-info → photo_stack_finder-0.1.8.dist-info}/entry_points.txt +0 -0
- {photo_stack_finder-0.1.7.dist-info → photo_stack_finder-0.1.8.dist-info}/licenses/LICENSE +0 -0
- {photo_stack_finder-0.1.7.dist-info → photo_stack_finder-0.1.8.dist-info}/top_level.txt +0 -0
utils/channel.py
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
"""Typed data channels connecting pipeline stages.
|
|
2
|
-
|
|
3
|
-
A Channel connects an OutputPort to an InputPort, establishing a typed
|
|
4
|
-
dependency edge in the pipeline graph. The Generic[T] type parameter
|
|
5
|
-
ensures compile-time type safety - the output and input must have
|
|
6
|
-
matching types.
|
|
7
|
-
|
|
8
|
-
Architecture:
|
|
9
|
-
- Auto-registration: Channels register themselves with the active graph
|
|
10
|
-
- Type safety: Generic[T] enforces matching port types
|
|
11
|
-
- Binding: Constructor automatically binds input port to output port
|
|
12
|
-
|
|
13
|
-
Analogous to: Channels in SystemC, wires connecting modules in Verilog
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
from typing import TypeVar
|
|
19
|
-
|
|
20
|
-
from .base_ports import BaseChannel
|
|
21
|
-
from .graph_context import get_active_graph
|
|
22
|
-
from .ports import InputPort, OutputPort
|
|
23
|
-
|
|
24
|
-
T = TypeVar("T")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class Channel[T](BaseChannel):
|
|
28
|
-
"""Typed data connection between pipeline stages.
|
|
29
|
-
|
|
30
|
-
Creates a dependency edge from producer (output port) to consumer
|
|
31
|
-
(input port). The Generic[T] type parameter ensures type safety:
|
|
32
|
-
both ports must handle the same data type.
|
|
33
|
-
|
|
34
|
-
The channel automatically:
|
|
35
|
-
1. Binds the input port to the output port
|
|
36
|
-
2. Registers itself with the active PipelineGraph (if within PipelineBuilder context)
|
|
37
|
-
|
|
38
|
-
Type parameter T specifies the data type flowing through this channel.
|
|
39
|
-
|
|
40
|
-
Example:
|
|
41
|
-
# Connect stages with type safety
|
|
42
|
-
sha_bins_o: OutputPort[dict[str, list[str]]] = stage1.sha_bins_o
|
|
43
|
-
sha_bins_input: InputPort[dict[str, list[str]]] = stage2.sha_bins_i
|
|
44
|
-
|
|
45
|
-
# This will type-check correctly
|
|
46
|
-
channel = Channel(sha_bins_o, sha_bins_input)
|
|
47
|
-
|
|
48
|
-
# This would fail mypy type checking if types don't match
|
|
49
|
-
# channel = Channel(different_type_output, sha_bins_input) # Error!
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def __init__(self, port_o: OutputPort[T], port_i: InputPort[T]) -> None:
|
|
53
|
-
"""Create a typed channel connecting two ports.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
port_o: The producer port (where data comes from)
|
|
57
|
-
port_i: The consumer port (where data goes to)
|
|
58
|
-
|
|
59
|
-
Note:
|
|
60
|
-
If called within a PipelineBuilder context (i.e., when
|
|
61
|
-
PipelineStage._graph is set), this channel will auto-register
|
|
62
|
-
with the graph.
|
|
63
|
-
"""
|
|
64
|
-
self.output = port_o
|
|
65
|
-
self.input = port_i
|
|
66
|
-
|
|
67
|
-
# Bind the input port to the output port
|
|
68
|
-
# This establishes the data flow connection
|
|
69
|
-
port_i.bind(port_o)
|
|
70
|
-
|
|
71
|
-
# Auto-register this edge if we're within a PipelineBuilder context
|
|
72
|
-
active_graph = get_active_graph()
|
|
73
|
-
if active_graph is not None:
|
|
74
|
-
active_graph.add_edge(self)
|
|
1
|
+
"""Typed data channels connecting pipeline stages.
|
|
2
|
+
|
|
3
|
+
A Channel connects an OutputPort to an InputPort, establishing a typed
|
|
4
|
+
dependency edge in the pipeline graph. The Generic[T] type parameter
|
|
5
|
+
ensures compile-time type safety - the output and input must have
|
|
6
|
+
matching types.
|
|
7
|
+
|
|
8
|
+
Architecture:
|
|
9
|
+
- Auto-registration: Channels register themselves with the active graph
|
|
10
|
+
- Type safety: Generic[T] enforces matching port types
|
|
11
|
+
- Binding: Constructor automatically binds input port to output port
|
|
12
|
+
|
|
13
|
+
Analogous to: Channels in SystemC, wires connecting modules in Verilog
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import TypeVar
|
|
19
|
+
|
|
20
|
+
from .base_ports import BaseChannel
|
|
21
|
+
from .graph_context import get_active_graph
|
|
22
|
+
from .ports import InputPort, OutputPort
|
|
23
|
+
|
|
24
|
+
T = TypeVar("T")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Channel[T](BaseChannel):
|
|
28
|
+
"""Typed data connection between pipeline stages.
|
|
29
|
+
|
|
30
|
+
Creates a dependency edge from producer (output port) to consumer
|
|
31
|
+
(input port). The Generic[T] type parameter ensures type safety:
|
|
32
|
+
both ports must handle the same data type.
|
|
33
|
+
|
|
34
|
+
The channel automatically:
|
|
35
|
+
1. Binds the input port to the output port
|
|
36
|
+
2. Registers itself with the active PipelineGraph (if within PipelineBuilder context)
|
|
37
|
+
|
|
38
|
+
Type parameter T specifies the data type flowing through this channel.
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
# Connect stages with type safety
|
|
42
|
+
sha_bins_o: OutputPort[dict[str, list[str]]] = stage1.sha_bins_o
|
|
43
|
+
sha_bins_input: InputPort[dict[str, list[str]]] = stage2.sha_bins_i
|
|
44
|
+
|
|
45
|
+
# This will type-check correctly
|
|
46
|
+
channel = Channel(sha_bins_o, sha_bins_input)
|
|
47
|
+
|
|
48
|
+
# This would fail mypy type checking if types don't match
|
|
49
|
+
# channel = Channel(different_type_output, sha_bins_input) # Error!
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, port_o: OutputPort[T], port_i: InputPort[T]) -> None:
|
|
53
|
+
"""Create a typed channel connecting two ports.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
port_o: The producer port (where data comes from)
|
|
57
|
+
port_i: The consumer port (where data goes to)
|
|
58
|
+
|
|
59
|
+
Note:
|
|
60
|
+
If called within a PipelineBuilder context (i.e., when
|
|
61
|
+
PipelineStage._graph is set), this channel will auto-register
|
|
62
|
+
with the graph.
|
|
63
|
+
"""
|
|
64
|
+
self.output = port_o
|
|
65
|
+
self.input = port_i
|
|
66
|
+
|
|
67
|
+
# Bind the input port to the output port
|
|
68
|
+
# This establishes the data flow connection
|
|
69
|
+
port_i.bind(port_o)
|
|
70
|
+
|
|
71
|
+
# Auto-register this edge if we're within a PipelineBuilder context
|
|
72
|
+
active_graph = get_active_graph()
|
|
73
|
+
if active_graph is not None:
|
|
74
|
+
active_graph.add_edge(self)
|
utils/comparison_gates.py
CHANGED
|
@@ -291,8 +291,8 @@ class AspectRatioGate(BaseGate):
|
|
|
291
291
|
"""
|
|
292
292
|
# Get aspect ratios (may trigger lazy dimension extraction)
|
|
293
293
|
# This may load pixels if dimensions not yet cached
|
|
294
|
-
ratio1 = img1.
|
|
295
|
-
ratio2 = img2.
|
|
294
|
+
ratio1 = img1._photo.aspect_ratio
|
|
295
|
+
ratio2 = img2._photo.aspect_ratio
|
|
296
296
|
|
|
297
297
|
# Normalize to portrait orientation (AR < 1.0)
|
|
298
298
|
# This allows matching landscape ↔ portrait photos
|
|
@@ -372,8 +372,14 @@ class GateSequence:
|
|
|
372
372
|
# MethodGate: check if pixels needed (cache miss detection)
|
|
373
373
|
# Only load pixels if we'll actually need them
|
|
374
374
|
# Note: cache keys are (method_name, rotation) tuples, always tuples even for rotation=0
|
|
375
|
-
will_need_pixels1 = (photo1.id, 0) not in gate.cache and (
|
|
376
|
-
|
|
375
|
+
will_need_pixels1 = (photo1.id, 0) not in gate.cache and (
|
|
376
|
+
gate._name,
|
|
377
|
+
0,
|
|
378
|
+
) not in photo1.cache
|
|
379
|
+
will_need_pixels2 = (photo2.id, 0) not in gate.cache and (
|
|
380
|
+
gate._name,
|
|
381
|
+
0,
|
|
382
|
+
) not in photo2.cache
|
|
377
383
|
|
|
378
384
|
if will_need_pixels1 or will_need_pixels2:
|
|
379
385
|
# Cache miss detected - load pixels if not already loaded
|
|
@@ -430,29 +436,24 @@ class GateSequence:
|
|
|
430
436
|
# Flexible ImageData management: create contexts only for photos that don't have pre-created ImageData
|
|
431
437
|
# Case 1: Both provided - use directly
|
|
432
438
|
if ref_img is not None and cand_img is not None:
|
|
433
|
-
return self._compare_with_rotation_impl(
|
|
434
|
-
reference, candidate, ref_img, cand_img, short_circuit
|
|
435
|
-
)
|
|
439
|
+
return self._compare_with_rotation_impl(reference, candidate, ref_img, cand_img, short_circuit)
|
|
436
440
|
|
|
437
441
|
# Case 2: Only ref provided - create context for candidate
|
|
438
442
|
if ref_img is not None and cand_img is None:
|
|
439
443
|
with candidate.image_data() as cand_img_ctx:
|
|
440
|
-
return self._compare_with_rotation_impl(
|
|
441
|
-
reference, candidate, ref_img, cand_img_ctx, short_circuit
|
|
442
|
-
)
|
|
444
|
+
return self._compare_with_rotation_impl(reference, candidate, ref_img, cand_img_ctx, short_circuit)
|
|
443
445
|
|
|
444
446
|
# Case 3: Only cand provided - create context for reference
|
|
445
447
|
if ref_img is None and cand_img is not None:
|
|
446
448
|
with reference.image_data() as ref_img_ctx:
|
|
447
|
-
return self._compare_with_rotation_impl(
|
|
448
|
-
reference, candidate, ref_img_ctx, cand_img, short_circuit
|
|
449
|
-
)
|
|
449
|
+
return self._compare_with_rotation_impl(reference, candidate, ref_img_ctx, cand_img, short_circuit)
|
|
450
450
|
|
|
451
451
|
# Case 4: Neither provided - create contexts for both (original pattern)
|
|
452
|
-
with
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
452
|
+
with (
|
|
453
|
+
reference.image_data() as ref_img_new,
|
|
454
|
+
candidate.image_data() as cand_img_new,
|
|
455
|
+
):
|
|
456
|
+
return self._compare_with_rotation_impl(reference, candidate, ref_img_new, cand_img_new, short_circuit)
|
|
456
457
|
|
|
457
458
|
def _compare_with_rotation_impl(
|
|
458
459
|
self,
|
|
@@ -508,7 +509,13 @@ class GateSequence:
|
|
|
508
509
|
# Second attempt: try 180° rotation ONLY if first failed
|
|
509
510
|
if not overall_pass:
|
|
510
511
|
ref_rotation_180 = ref_norm_rotation + 180
|
|
511
|
-
|
|
512
|
+
(
|
|
513
|
+
scores_180,
|
|
514
|
+
overall_pass_180,
|
|
515
|
+
final_gate_score_180,
|
|
516
|
+
ref_pixels,
|
|
517
|
+
cand_pixels,
|
|
518
|
+
) = self._attempt_comparison(
|
|
512
519
|
reference,
|
|
513
520
|
candidate,
|
|
514
521
|
ref_img,
|
|
@@ -539,7 +546,13 @@ class GateSequence:
|
|
|
539
546
|
ref_pixels: npt.NDArray[np.uint8] | None,
|
|
540
547
|
cand_pixels: npt.NDArray[np.uint8] | None,
|
|
541
548
|
short_circuit: bool,
|
|
542
|
-
) -> tuple[
|
|
549
|
+
) -> tuple[
|
|
550
|
+
dict[str, float],
|
|
551
|
+
bool,
|
|
552
|
+
float,
|
|
553
|
+
npt.NDArray[np.uint8] | None,
|
|
554
|
+
npt.NDArray[np.uint8] | None,
|
|
555
|
+
]:
|
|
543
556
|
"""Attempt comparison at specific rotation angles.
|
|
544
557
|
|
|
545
558
|
Args:
|
|
@@ -569,11 +582,17 @@ class GateSequence:
|
|
|
569
582
|
elif isinstance(gate, MethodGate):
|
|
570
583
|
# Check if we need to load pixels (cache miss detection)
|
|
571
584
|
# Cache keys are always (method_name, rotation) tuples
|
|
572
|
-
will_need_ref_pixels = (
|
|
585
|
+
will_need_ref_pixels = (
|
|
586
|
+
reference.id,
|
|
587
|
+
ref_rotation,
|
|
588
|
+
) not in gate.cache and (
|
|
573
589
|
gate._name,
|
|
574
590
|
ref_rotation,
|
|
575
591
|
) not in reference.cache
|
|
576
|
-
will_need_cand_pixels = (
|
|
592
|
+
will_need_cand_pixels = (
|
|
593
|
+
candidate.id,
|
|
594
|
+
cand_rotation,
|
|
595
|
+
) not in gate.cache and (
|
|
577
596
|
gate._name,
|
|
578
597
|
cand_rotation,
|
|
579
598
|
) not in candidate.cache
|