Deduplication API
When configurations share their preprocessing, the deduplication module finds the feature families that get the same input in more than one configuration. The pipeline computes each such family one time and copies the values.
Overview
The module has four parts:
DeduplicationRules: which preprocessing steps and options change which feature family.PreprocessingSignature: the hashable record of the preprocessing of a family.ConfigurationAnalyzer: finds the families that configurations share.DeduplicationPlan: the plan of what to compute and what to copy.
Quick Start
On by default
Deduplication is on by default (deduplicate=True): make a pipeline and run its configurations.
from pictologics import RadiomicsPipeline
pipeline = RadiomicsPipeline() # deduplicate=True
# add configurations that share their preprocessing: the morphology and intensity
# features are computed one time and copied
results = pipeline.run(image, mask, config_names=["config1", "config2", "config3"])
print(pipeline.deduplication_stats) # reused_families, computed_families, cache_hit_rate
For a complete example, see Many Discretisations in the Cookbook, and Shared Work Between Configurations.
How Results Are Handled
When a configuration reuses the features of an earlier one, the features are copied into its results: they are never empty or missing.
Result Behavior
| Scenario | Behavior |
|---|---|
deduplicate=True (default) |
A family is computed one time, then copied to every configuration with the same family and the same preprocessing. Every configuration gets all its features. |
deduplicate=False |
Each configuration computes each family itself. The same results, more time. |
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
Deduplication never leaves a feature out: each configuration gets all features of its families, computed or copied. When the configurations ask for the same families, a table with one row for each configuration has the same columns in every row:
import pandas as pd
rows = [{"config": name, **results[name].to_dict()} for name in results]
table = pd.DataFrame(rows) # one row for each configuration
When the configurations ask for other families (for example an orig configuration with the morphology and intensity features, and others with the texture features), each row has the columns of its own families, and the other cells are empty (NaN). A feature can also be NaN for its own reason, for example a PCA of too few voxels.
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. Signatures always
include every preprocessing step before extraction; a family whose set
does not list |
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"). |
family_options |
dict[str, frozenset[str]] | None
|
The |
Example
Source code in pictologics/deduplication.py
version
instance-attribute
family_dependencies
instance-attribute
family_options = None
class-attribute
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
|
config_metadata
|
dict[str, dict[str, Any]] | None
|
Optional dict mapping config names to their
|
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
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 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 | |
__init__(configs, rules=None, config_metadata=None)
Source code in pictologics/deduplication.py
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
753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 | |
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
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 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 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 | |
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, '1.1.0': DEDUPLICATION_RULES_V1_1_0}
module-attribute
Available Versions
| Version | Description |
|---|---|
"1.0.0" |
Initial rules defining feature family dependencies |
"1.1.0" |
A family signature holds only the extract_features options that the family reads (the default) |
Helper Functions
pictologics.deduplication.get_default_rules()
Get the current default deduplication rules.
Example
Source code in pictologics/deduplication.py
Feature Family Dependencies
A signature holds the source_mode and sentinel_value, every preprocessing step before extract_features (with its parameters, in order), and the extract_features options that the family reads (rules 1.0.0: all options other than families). The rules also decide which families can ignore a discretise step at the end of the preprocessing: a family whose rules do not list discretise leaves that step out.
| Feature Family | Rules list discretise |
Options in the signature (1.1.0) |
|---|---|---|
morphology |
No | None |
intensity |
No | include_spatial_intensity, include_local_intensity, spatial_intensity_params, local_intensity_params |
spatial_intensity |
No | spatial_intensity_params |
local_intensity |
No | local_intensity_params |
histogram |
Yes | None |
ivh |
Yes, unless ivh_use_continuous=True is set at the top level of the extract_features params |
ivh_params, ivh_use_continuous, ivh_discretisation |
texture (all subfamilies) |
Yes | texture_matrix_params |
A filter changes the intensity and morphology features
After a filter step, the intensity features read the response map, not the image. The
intensity-weighted morphology features (integrated_intensity_99N0 and
center_of_mass_shift_KLMA) read it too. So configurations with other filters never share
these families.
When two configurations have the same signature for a family, the family is computed one time and reused. The reuse is by family and signature, so texture, histogram and ivh never reuse the values of each other, also when their signatures are the same. A configuration with more than one extract_features step is always computed on its own.
Integration with RadiomicsPipeline
RadiomicsPipeline takes two parameters for the deduplication:
| Parameter | Type | Default | Description |
|---|---|---|---|
deduplicate |
bool |
True |
Deduplication on or off |
deduplication_rules |
str, DeduplicationRules or None |
None |
The rules version, for reproducibility (None: the current default, "1.1.0") |
to_dict() and save_configs() write these settings, and the loaders read them.