Skip to content

Implement comprehensive Registry Pattern for system-wide modularity and decoupling#29

Merged
wenh06 merged 4 commits into
masterfrom
arch-registry
Mar 14, 2026
Merged

Implement comprehensive Registry Pattern for system-wide modularity and decoupling#29
wenh06 merged 4 commits into
masterfrom
arch-registry

Conversation

@wenh06

@wenh06 wenh06 commented Mar 13, 2026

Copy link
Copy Markdown
Collaborator

This PR introduces a unified Registry system across torch_ecg, eliminating hardcoded if-elif chains in core modules. This architectural upgrade significantly enhances scalability, allowing for dynamic component injection via simple decorators and ensuring a cleaner, more maintainable codebase.

Key Changes

  1. Registry Infrastructure:
    • Implemented a generic Registry class in torch_ecg.utils.registry providing register, get, and build capabilities.
  2. Models & Attention Mechanisms:
    • Established BACKBONES, MODELS, and ATTN_LAYERS registries.
    • Refactored ECG_CRNN and ECG_CRNN_v1 to utilize dynamic building for both CNN backbones and Attention layers.
    • Added @register decorators to all major CNN backbones (ResNet, VGG, RegNet, MobileNet, etc.) and downstream models.
    • Standardized keyword mapping (supporting in_channels, in_features, and embed_dim aliases) to ensure registry compatibility across different attention modules.
  3. Preprocessors & Augmenters:
    • Established PREPROCESSORS and AUGMENTERS registries.
    • Refactored PreprocManager and AugmenterManager to dynamically build processing pipelines directly from configurations.
  4. Trainer Components:
    • Established OPTIMIZERS, SCHEDULERS, and LOSSES registries.
    • Refactored BaseTrainer and setup_criterion to support automatic component discovery from both the local registry and native torch modules (torch.optim, torch.nn).

Copilot AI review requested due to automatic review settings March 13, 2026 09:43
@wenh06 wenh06 enabled auto-merge March 13, 2026 09:43
@wenh06 wenh06 requested a review from kjs11 March 13, 2026 09:43
@wenh06 wenh06 added the enhancement New feature or request label Mar 13, 2026
rnn_input_size = self.cnn.compute_output_shape(None, None)[1]

rnn_input_size = self.cnn.compute_output_shape(2000, 2)[1]
clf_input_size = rnn_input_size # default

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a generic Registry mechanism and refactors multiple subsystems (models/backbones/attention layers, preprocessors, augmenters, trainer components) to construct components dynamically from configuration instead of hardcoded conditionals, aiming to improve modularity and extensibility across torch_ecg.

Changes:

  • Added a reusable Registry utility and introduced registries for preprocessors, augmenters, models/backbones/attention layers, and trainer components (optimizers/schedulers/losses).
  • Refactored ECG_CRNN / ECG_CRNN_v1, PreprocManager, AugmenterManager, and trainer optimizer/scheduler/loss setup to use registry-based building.
  • Registered many existing components via decorators and updated CI/tooling configs.

Reviewed changes

Copilot reviewed 43 out of 43 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
torch_ecg/utils/utils_nn.py Adjust safetensors vs torch-save selection
torch_ecg/utils/registry.py Add generic Registry implementation
torch_ecg/utils/init.py Export Registry from utils
torch_ecg/preprocessors/resample.py Register Resample in PREPROCESSORS
torch_ecg/preprocessors/registry.py Add PREPROCESSORS registry
torch_ecg/preprocessors/preproc_manager.py Build preprocessors via registry
torch_ecg/preprocessors/normalize.py Register normalization preprocessors
torch_ecg/preprocessors/baseline_remove.py Register BaselineRemove
torch_ecg/preprocessors/bandpass.py Register BandPass
torch_ecg/preprocessors/init.py Export PREPROCESSORS
torch_ecg/models/unets/ecg_unet.py Register ECG_UNET in MODELS
torch_ecg/models/unets/ecg_subtract_unet.py Register ECG_SUBTRACT_UNET in MODELS
torch_ecg/models/transformers/transformer.py Register Transformer in ATTN_LAYERS
torch_ecg/models/rr_lstm.py Register RR_LSTM in MODELS
torch_ecg/models/registry.py Add BACKBONES/MODELS/ATTN_LAYERS registries
torch_ecg/models/loss.py Register losses + use LOSSES in setup
torch_ecg/models/ecg_seq_lab_net.py Register ECG_SEQ_LAB_NET in MODELS
torch_ecg/models/ecg_crnn.py Refactor backbone/attn selection via registries
torch_ecg/models/cnn/xception.py Register Xception backbone
torch_ecg/models/cnn/vgg.py Register VGG16 backbone
torch_ecg/models/cnn/resnet.py Register ResNet/ResNeXt backbones
torch_ecg/models/cnn/regnet.py Register RegNet backbone
torch_ecg/models/cnn/multi_scopic.py Register MultiScopicCNN backbone
torch_ecg/models/cnn/mobilenet.py Register MobileNet backbones
torch_ecg/models/cnn/densenet.py Register DenseNet backbone
torch_ecg/models/_nets.py Register attention blocks in ATTN_LAYERS
torch_ecg/models/init.py Export registries from models package
torch_ecg/components/trainer.py Registry-based optimizer/scheduler resolution
torch_ecg/components/registry.py Add OPTIMIZERS/SCHEDULERS/LOSSES registries
torch_ecg/augmenters/stretch_compress.py Register StretchCompress augmenter
torch_ecg/augmenters/registry.py Add AUGMENTERS registry
torch_ecg/augmenters/random_renormalize.py Register RandomRenormalize augmenter
torch_ecg/augmenters/random_masking.py Register RandomMasking augmenter
torch_ecg/augmenters/random_flip.py Register RandomFlip augmenter
torch_ecg/augmenters/mixup.py Register Mixup augmenter
torch_ecg/augmenters/label_smooth.py Register LabelSmooth augmenter
torch_ecg/augmenters/cutmix.py Register CutMix augmenter
torch_ecg/augmenters/baseline_wander.py Register BaselineWanderAugmenter
torch_ecg/augmenters/augmenter_manager.py Build augmenters via registry
torch_ecg/augmenters/init.py Export AUGMENTERS
.pre-commit-config.yaml Update flake8 ignore list
.github/workflows/test-models.yml Update Python test matrix
.github/workflows/test-databases.yml Update Python test matrix

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 10 to 12
from ..utils.misc import ReprMixin
from .bandpass import BandPass
from .baseline_remove import BaselineRemove
from .normalize import Normalize
from .resample import Resample
from .registry import PREPROCESSORS

from .random_renormalize import RandomRenormalize
from .stretch_compress import StretchCompress
from .registry import AUGMENTERS

Comment on lines +662 to +663
for name in dir(optim.lr_scheduler):
if name.lower() == lrs_name.replace("_", ""):
Comment thread torch_ecg/models/loss.py
if name in LOSSES:
return LOSSES.build(name, **kwargs)
if name.startswith("nn."):
criterion = eval(name)(**kwargs)
Comment on lines +81 to 92
self.cnn = None
# order by length descending to match the most specific name first
for name in sorted(BACKBONES.list_all(), key=len, reverse=True):
if name.lower() in cnn_choice:
try:
self.cnn = BACKBONES.build(name, in_channels=self.n_leads, **cnn_config)
except TypeError:
self.cnn = BACKBONES.build(name, n_leads=self.n_leads, **cnn_config)
break

if self.cnn is None:
raise NotImplementedError(f"CNN \042{cnn_choice}\042 not implemented yet")
Comment on lines +541 to +548
for name in sorted(ATTN_LAYERS.list_all(), key=len, reverse=True):
if name.lower() in attn_choice:
if name.lower() in ["transformer", "transformer_encoder"]:
self.attn = ATTN_LAYERS.build(name, input_size=attn_input_size, **attn_config)
elif name.lower() in ["sa", "self_attention", "multi_head_attention", "attentive_pooling"]:
self.attn = ATTN_LAYERS.build(name, in_features=attn_input_size, **attn_config)
else:
self.attn = ATTN_LAYERS.build(name, in_channels=attn_input_size, **attn_config)
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.12", "3.13"]
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.12", "3.13"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test of 3.11 got stuck for an unknown reason. Currently, there's no time to resolve it.

Comment on lines +153 to +160
for name in sorted(ATTN_LAYERS.list_all(), key=len, reverse=True):
if name.lower() in attn_choice:
if name.lower() in ["transformer", "transformer_encoder"]:
self.attn = ATTN_LAYERS.build(name, input_size=attn_input_size, **attn_config)
elif name.lower() in ["sa", "self_attention", "multi_head_attention", "attentive_pooling"]:
self.attn = ATTN_LAYERS.build(name, in_features=attn_input_size, **attn_config)
else:
self.attn = ATTN_LAYERS.build(name, in_channels=attn_input_size, **attn_config)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1dccf7397e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +160 to +161
else:
am.add_(aug_name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Ignore disabled augmenters in from_config

AugmenterManager.from_config now instantiates any registered augmenter whose config value is non-dict via am.add_(aug_name), so entries set to False are no longer treated as disabled. Existing training configs commonly use label_smooth = False, random_masking = False, etc. (for example in torch_ecg/databases/datasets/cinc2021/cinc2021_cfg.py), so this change silently enables augmenters with default probabilities and alters training behavior/results.

Useful? React with 👍 / 👎.

Comment on lines +145 to +146
else:
ppm.add_(pp_name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Ignore disabled preprocessors in from_config

PreprocManager.from_config has the same non-dict path and now calls ppm.add_(pp_name) even when a preprocessor is explicitly disabled with False. Several dataset configs rely on normalize = False (for example torch_ecg/databases/datasets/cpsc2019/cpsc2019_cfg.py), so this change can unexpectedly add default preprocessors and modify input distributions in experiments that previously skipped them.

Useful? React with 👍 / 👎.

Comment on lines +691 to +693
lrs_kwargs = get_kwargs(lrs_cls)
lrs_kwargs.update({k: self.train_config.get(k, v) for k, v in lrs_kwargs.items()})
self.scheduler = lrs_cls(self.optimizer, **lrs_kwargs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Forward required scheduler args in generic setup

The generic scheduler path only builds kwargs from get_kwargs(lrs_cls) (defaults) and then updates those same keys, so constructor arguments without defaults can never be passed from train_config. As a result, many torch.optim.lr_scheduler classes that require extra parameters beyond optimizer will fail at runtime with missing-argument errors despite being selected by name.

Useful? React with 👍 / 👎.

Comment on lines +157 to +158
elif name.lower() in ["sa", "self_attention", "multi_head_attention", "attentive_pooling"]:
self.attn = ATTN_LAYERS.build(name, in_features=attn_input_size, **attn_config)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Construct attentive_pooling with in_channels

In ECG_CRNN, the registry branch groups attentive_pooling with self-attention and instantiates it using in_features=..., but AttentivePooling expects in_channels. Selecting attn.name = "attentive_pooling" therefore raises an unexpected-keyword TypeError during model initialization.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Mar 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.83051% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.46%. Comparing base (e34c695) to head (a147f34).
⚠️ Report is 5 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
torch_ecg/components/trainer.py 67.56% 12 Missing ⚠️
torch_ecg/utils/registry.py 73.80% 11 Missing ⚠️
torch_ecg/models/ecg_crnn.py 91.39% 8 Missing ⚠️
torch_ecg/augmenters/augmenter_manager.py 86.95% 3 Missing ⚠️
torch_ecg/preprocessors/preproc_manager.py 91.66% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #29      +/-   ##
==========================================
- Coverage   93.52%   93.46%   -0.07%     
==========================================
  Files         134      139       +5     
  Lines       18252    18413     +161     
==========================================
+ Hits        17071    17210     +139     
- Misses       1181     1203      +22     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wenh06 wenh06 added this pull request to the merge queue Mar 14, 2026
Merged via the queue into master with commit 3945f2c Mar 14, 2026
12 of 14 checks passed
@kjs11 kjs11 deleted the arch-registry branch March 14, 2026 00:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants