Deduplication API
The deduplication module provides intelligent optimization for multi-configuration radiomic feature extraction. When multiple configurations share preprocessing steps but differ only in discretization, the system avoids redundant computation by identifying which feature families can be computed once and reused.
Overview
The deduplication system consists of four main components:
DeduplicationRules: Defines which preprocessing steps affect which feature familiesPreprocessingSignature: Creates hashable representations of preprocessing statesConfigurationAnalyzer: Analyzes pipeline configurations to identify optimization opportunitiesDeduplicationPlan: Generates optimized execution plans
Quick Start
Enabled by Default
Deduplication is enabled by default (deduplicate=True). You don't need to explicitly enable it—just create a pipeline and run multiple configurations.
from pictologics import RadiomicsPipeline
# Deduplication is enabled by default!
pipeline = RadiomicsPipeline() # deduplicate=True is the default
# Add multiple configurations with shared preprocessing
# ... (morphology/intensity computed once, reused across configs)
results = pipeline.run(image, mask, config_names=["config1", "config2", "config3"])
# Check performance statistics
print(pipeline.deduplication_stats)
For complete usage examples, see Case 7: Multi-configuration batch with deduplication.
How Results Are Handled
When deduplication reuses features from a previous configuration, the features are copied to the reusing configuration's results—they are never empty or missing.
Result Behavior
| Scenario | Behavior |
|---|---|
| deduplicate=True (default) | Features computed once, then copied to all configs with the same feature family and matching preprocessing. All configs receive complete feature sets. |
| deduplicate=False | Features computed independently for each config. Same results, but slower. |
Example: Results Structure
results = pipeline.run(image, mask, config_names=["fbn_8", "fbn_16", "fbn_32"])
# All configs have IDENTICAL morphology values (computed once, copied to others)
assert results["fbn_8"]["volume_RNU0"] == results["fbn_16"]["volume_RNU0"]
assert results["fbn_8"]["volume_RNU0"] == results["fbn_32"]["volume_RNU0"]
# Texture features DIFFER (depend on discretization)
assert results["fbn_8"]["joint_average_60VM"] != results["fbn_32"]["joint_average_60VM"]
Data Tables and Concatenation
When you concatenate results into a single DataFrame (e.g., for machine learning), every configuration row is complete—no missing values due to deduplication:
import pandas as pd
# Deduplication copies each computed family into every config that reuses it, so all
# configs expose the same feature keys — stacking them leaves no missing values.
rows = [{"config": name, **results[name].to_dict()} for name in results]
df = pd.DataFrame(rows)
print(df.shape) # one row per config; columns = config + all features
print(int(df.isna().sum().sum())) # 0 - no NaN values
DeduplicationRules
pictologics.deduplication.DeduplicationRules
dataclass
Defines which preprocessing steps affect each feature family.
This is a frozen (immutable) dataclass that specifies the dependencies between preprocessing steps and feature families. Rules are versioned to ensure reproducibility when sharing configurations.
Attributes:
| Name | Type | Description |
|---|---|---|
version |
str
|
Semantic version string for this rules definition. |
family_dependencies |
dict[str, frozenset[str]]
|
Mapping of feature family names to the set of preprocessing step names that affect their output. |
ivh_discretization_dependent_unless |
str
|
Condition under which IVH becomes independent of discretization (e.g., "ivh_use_continuous=True"). |
comparison_mode |
str
|
How to compare preprocessing parameters ("exact_params"). |
Example
Source code in pictologics/deduplication.py
version
instance-attribute
family_dependencies
instance-attribute
get_version(version)
classmethod
Get rules for a specific version from the registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
version
|
str
|
Version string (e.g., "1.0.0"). |
required |
Returns:
| Type | Description |
|---|---|
'DeduplicationRules'
|
The DeduplicationRules for that version. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the version is not in the registry. |
Source code in pictologics/deduplication.py
to_dict()
Serialize rules to a dictionary.
Source code in pictologics/deduplication.py
from_dict(data)
classmethod
Deserialize rules from a dictionary.
Source code in pictologics/deduplication.py
PreprocessingSignature
pictologics.deduplication.PreprocessingSignature
dataclass
A hashable signature representing a preprocessing configuration.
Contains both a hash for fast comparison and the full JSON representation for human-readable debugging and logging.
Attributes:
| Name | Type | Description |
|---|---|---|
hash |
str
|
SHA256 hash of the normalized preprocessing steps. |
json_repr |
str
|
Full JSON string of the preprocessing steps. |
Example
Source code in pictologics/deduplication.py
hash
instance-attribute
json_repr
instance-attribute
from_steps(steps)
classmethod
Create a signature from a list of (step_name, params) tuples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
steps
|
list[tuple[str, dict[str, Any]]]
|
Ordered list of (step_name, params_dict) tuples. |
required |
Returns:
| Type | Description |
|---|---|
'PreprocessingSignature'
|
A PreprocessingSignature with deterministic hash and JSON. |
Source code in pictologics/deduplication.py
ConfigurationAnalyzer
pictologics.deduplication.ConfigurationAnalyzer
Analyzes multiple configurations to create a deduplication plan.
Compares preprocessing steps across configurations for each feature family and identifies which config/family pairs produce identical results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
configs
|
dict[str, list[dict[str, Any]]]
|
Dict mapping config names to lists of step dicts. |
required |
rules
|
DeduplicationRules | None
|
The DeduplicationRules to use (defaults to current version). |
None
|
Example
from pictologics.deduplication import ConfigurationAnalyzer
configs = {
"fbn_32": [
{"step": "resample", "params": {"spacing": (1.0, 1.0, 1.0)}},
{"step": "discretise", "params": {"n_bins": 32}},
{"step": "extract_features", "params": {"families": ["morphology"]}},
],
"fbn_64": [
{"step": "resample", "params": {"spacing": (1.0, 1.0, 1.0)}},
{"step": "discretise", "params": {"n_bins": 64}},
{"step": "extract_features", "params": {"families": ["morphology"]}},
],
}
analyzer = ConfigurationAnalyzer(configs)
plan = analyzer.analyze()
print(plan.should_compute("fbn_64", "morphology"))
# False (morphology doesn't depend on discretise, so fbn_64 reuses fbn_32)
print(plan.get_source("fbn_64", "morphology"))
# fbn_32
Source code in pictologics/deduplication.py
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 | |
__init__(configs, rules=None)
analyze()
Analyze configurations and create a deduplication plan.
Returns:
| Type | Description |
|---|---|
DeduplicationPlan
|
A DeduplicationPlan mapping each config/family to its source. |
Source code in pictologics/deduplication.py
DeduplicationPlan
pictologics.deduplication.DeduplicationPlan
dataclass
A plan describing which config/family pairs should compute vs. reuse.
Attributes:
| Name | Type | Description |
|---|---|---|
rules |
DeduplicationRules
|
The DeduplicationRules used to create this plan. |
signatures |
dict[tuple[str, str], PreprocessingSignature]
|
Mapping of (config_name, family) to PreprocessingSignature. |
sources |
dict[tuple[str, str], str | None]
|
Mapping of (config_name, family) to source config name (or None if first). |
configs_hash |
str
|
Hash of the configs dict to detect modifications. |
Example
Plans are normally produced by ConfigurationAnalyzer.analyze():
from pictologics.deduplication import ConfigurationAnalyzer
configs = {
"fbn_32": [
{"step": "resample", "params": {"spacing": (1.0, 1.0, 1.0)}},
{"step": "discretise", "params": {"n_bins": 32}},
{"step": "extract_features", "params": {"families": ["morphology"]}},
],
"fbn_64": [
{"step": "resample", "params": {"spacing": (1.0, 1.0, 1.0)}},
{"step": "discretise", "params": {"n_bins": 64}},
{"step": "extract_features", "params": {"families": ["morphology"]}},
],
}
plan = ConfigurationAnalyzer(configs).analyze()
print(plan.get_summary())
# {'computed': 1, 'reused': 1, 'total': 2}
Source code in pictologics/deduplication.py
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 | |
should_compute(config_name, family)
Check if this config/family should be computed fresh.
Returns True if this is the first occurrence of this signature, False if it can be copied from another config.
Source code in pictologics/deduplication.py
get_source(config_name, family)
Get the source config to copy from, or None if should compute.
is_stale(current_configs)
Check if this plan is stale due to config modifications.
get_summary()
Get a summary of the deduplication plan.
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Dict with counts of computed vs reused families. |
Source code in pictologics/deduplication.py
to_dict()
Serialize the plan to a dictionary.
Source code in pictologics/deduplication.py
from_dict(data)
classmethod
Deserialize a plan from a dictionary.
Source code in pictologics/deduplication.py
Rules Registry
The RULES_REGISTRY provides versioned deduplication rules for reproducibility:
pictologics.deduplication.RULES_REGISTRY = {'1.0.0': DEDUPLICATION_RULES_V1_0_0}
module-attribute
Available Versions
| Version | Description |
|---|---|
"1.0.0" |
Initial rules defining feature family dependencies |
Helper Functions
pictologics.deduplication.get_default_rules()
Get the current default deduplication rules.
Example
Source code in pictologics/deduplication.py
Feature Family Dependencies
The deduplication system understands which preprocessing steps affect which feature families:
| Feature Family | Relevant Preprocessing Steps |
|---|---|
morphology |
resample, resegment, filter_outliers, binarize_mask, keep_largest_component |
intensity |
resample, resegment, filter_outliers, filter |
spatial_intensity |
Same as intensity |
local_intensity |
Same as intensity |
histogram |
resample, resegment, filter_outliers, filter, binarize_mask, keep_largest_component, discretise |
ivh |
Same as histogram (unless ivh_use_continuous=True, which removes discretise dependency) |
texture (all subfamilies) |
Same as histogram |
Filters Affect Intensity Features
When using image filters (LoG, Gabor, Wavelets, Laws, etc.), intensity features are computed from the filtered response map, not the original image. Therefore, different filter configurations will produce different intensity features and cannot be deduplicated.
Morphology features are not affected by response-map filters since they are computed from
mask geometry, not filtered intensities. Morphology is affected by mask-narrowing preprocessing
such as resegment and filter_outliers when those steps target the morphology mask.
When two configurations share identical values for the relevant preprocessing steps of a feature family in the same order, that family is computed once and the result is reused. Cache reuse is scoped by both feature family and preprocessing signature, so families such as texture, histogram, and ivh never reuse each other's cached values even when their relevant preprocessing steps are identical.
Integration with RadiomicsPipeline
The RadiomicsPipeline class integrates deduplication through these parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
deduplicate |
bool |
True |
Enable/disable deduplication |
deduplication_rules |
str, DeduplicationRules, or None |
None |
Rules version for reproducibility (None resolves to the current default rules, "1.0.0") |
These settings are preserved during serialization (to_dict(), save_configs(), etc.) and restored during deserialization.