| | |
| | """ |
| | Backward compatibility of configs. |
| | |
| | Instructions to bump version: |
| | + It's not needed to bump version if new keys are added. |
| | It's only needed when backward-incompatible changes happen |
| | (i.e., some existing keys disappear, or the meaning of a key changes) |
| | + To bump version, do the following: |
| | 1. Increment _C.VERSION in defaults.py |
| | 2. Add a converter in this file. |
| | |
| | Each ConverterVX has a function "upgrade" which in-place upgrades config from X-1 to X, |
| | and a function "downgrade" which in-place downgrades config from X to X-1 |
| | |
| | In each function, VERSION is left unchanged. |
| | |
| | Each converter assumes that its input has the relevant keys |
| | (i.e., the input is not a partial config). |
| | 3. Run the tests (test_config.py) to make sure the upgrade & downgrade |
| | functions are consistent. |
| | """ |
| |
|
| | import logging |
| | from typing import List, Optional, Tuple |
| |
|
| | from .config import CfgNode as CN |
| | from .defaults import _C |
| |
|
| | __all__ = ["upgrade_config", "downgrade_config"] |
| |
|
| |
|
| | def upgrade_config(cfg: CN, to_version: Optional[int] = None) -> CN: |
| | """ |
| | Upgrade a config from its current version to a newer version. |
| | |
| | Args: |
| | cfg (CfgNode): |
| | to_version (int): defaults to the latest version. |
| | """ |
| | cfg = cfg.clone() |
| | if to_version is None: |
| | to_version = _C.VERSION |
| |
|
| | assert cfg.VERSION <= to_version, "Cannot upgrade from v{} to v{}!".format( |
| | cfg.VERSION, to_version |
| | ) |
| | for k in range(cfg.VERSION, to_version): |
| | converter = globals()["ConverterV" + str(k + 1)] |
| | converter.upgrade(cfg) |
| | cfg.VERSION = k + 1 |
| | return cfg |
| |
|
| |
|
| | def downgrade_config(cfg: CN, to_version: int) -> CN: |
| | """ |
| | Downgrade a config from its current version to an older version. |
| | |
| | Args: |
| | cfg (CfgNode): |
| | to_version (int): |
| | |
| | Note: |
| | A general downgrade of arbitrary configs is not always possible due to the |
| | different functionalities in different versions. |
| | The purpose of downgrade is only to recover the defaults in old versions, |
| | allowing it to load an old partial yaml config. |
| | Therefore, the implementation only needs to fill in the default values |
| | in the old version when a general downgrade is not possible. |
| | """ |
| | cfg = cfg.clone() |
| | assert cfg.VERSION >= to_version, "Cannot downgrade from v{} to v{}!".format( |
| | cfg.VERSION, to_version |
| | ) |
| | for k in range(cfg.VERSION, to_version, -1): |
| | converter = globals()["ConverterV" + str(k)] |
| | converter.downgrade(cfg) |
| | cfg.VERSION = k - 1 |
| | return cfg |
| |
|
| |
|
| | def guess_version(cfg: CN, filename: str) -> int: |
| | """ |
| | Guess the version of a partial config where the VERSION field is not specified. |
| | Returns the version, or the latest if cannot make a guess. |
| | |
| | This makes it easier for users to migrate. |
| | """ |
| | logger = logging.getLogger(__name__) |
| |
|
| | def _has(name: str) -> bool: |
| | cur = cfg |
| | for n in name.split("."): |
| | if n not in cur: |
| | return False |
| | cur = cur[n] |
| | return True |
| |
|
| | |
| | ret = None |
| | if _has("MODEL.WEIGHT") or _has("TEST.AUG_ON"): |
| | ret = 1 |
| |
|
| | if ret is not None: |
| | logger.warning("Config '{}' has no VERSION. Assuming it to be v{}.".format(filename, ret)) |
| | else: |
| | ret = _C.VERSION |
| | logger.warning( |
| | "Config '{}' has no VERSION. Assuming it to be compatible with latest v{}.".format( |
| | filename, ret |
| | ) |
| | ) |
| | return ret |
| |
|
| |
|
| | def _rename(cfg: CN, old: str, new: str) -> None: |
| | old_keys = old.split(".") |
| | new_keys = new.split(".") |
| |
|
| | def _set(key_seq: List[str], val: str) -> None: |
| | cur = cfg |
| | for k in key_seq[:-1]: |
| | if k not in cur: |
| | cur[k] = CN() |
| | cur = cur[k] |
| | cur[key_seq[-1]] = val |
| |
|
| | def _get(key_seq: List[str]) -> CN: |
| | cur = cfg |
| | for k in key_seq: |
| | cur = cur[k] |
| | return cur |
| |
|
| | def _del(key_seq: List[str]) -> None: |
| | cur = cfg |
| | for k in key_seq[:-1]: |
| | cur = cur[k] |
| | del cur[key_seq[-1]] |
| | if len(cur) == 0 and len(key_seq) > 1: |
| | _del(key_seq[:-1]) |
| |
|
| | _set(new_keys, _get(old_keys)) |
| | _del(old_keys) |
| |
|
| |
|
| | class _RenameConverter: |
| | """ |
| | A converter that handles simple rename. |
| | """ |
| |
|
| | RENAME: List[Tuple[str, str]] = [] |
| |
|
| | @classmethod |
| | def upgrade(cls, cfg: CN) -> None: |
| | for old, new in cls.RENAME: |
| | _rename(cfg, old, new) |
| |
|
| | @classmethod |
| | def downgrade(cls, cfg: CN) -> None: |
| | for old, new in cls.RENAME[::-1]: |
| | _rename(cfg, new, old) |
| |
|
| |
|
| | class ConverterV1(_RenameConverter): |
| | RENAME = [("MODEL.RPN_HEAD.NAME", "MODEL.RPN.HEAD_NAME")] |
| |
|
| |
|
| | class ConverterV2(_RenameConverter): |
| | """ |
| | A large bulk of rename, before public release. |
| | """ |
| |
|
| | RENAME = [ |
| | ("MODEL.WEIGHT", "MODEL.WEIGHTS"), |
| | ("MODEL.PANOPTIC_FPN.SEMANTIC_LOSS_SCALE", "MODEL.SEM_SEG_HEAD.LOSS_WEIGHT"), |
| | ("MODEL.PANOPTIC_FPN.RPN_LOSS_SCALE", "MODEL.RPN.LOSS_WEIGHT"), |
| | ("MODEL.PANOPTIC_FPN.INSTANCE_LOSS_SCALE", "MODEL.PANOPTIC_FPN.INSTANCE_LOSS_WEIGHT"), |
| | ("MODEL.PANOPTIC_FPN.COMBINE_ON", "MODEL.PANOPTIC_FPN.COMBINE.ENABLED"), |
| | ( |
| | "MODEL.PANOPTIC_FPN.COMBINE_OVERLAP_THRESHOLD", |
| | "MODEL.PANOPTIC_FPN.COMBINE.OVERLAP_THRESH", |
| | ), |
| | ( |
| | "MODEL.PANOPTIC_FPN.COMBINE_STUFF_AREA_LIMIT", |
| | "MODEL.PANOPTIC_FPN.COMBINE.STUFF_AREA_LIMIT", |
| | ), |
| | ( |
| | "MODEL.PANOPTIC_FPN.COMBINE_INSTANCES_CONFIDENCE_THRESHOLD", |
| | "MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH", |
| | ), |
| | ("MODEL.ROI_HEADS.SCORE_THRESH", "MODEL.ROI_HEADS.SCORE_THRESH_TEST"), |
| | ("MODEL.ROI_HEADS.NMS", "MODEL.ROI_HEADS.NMS_THRESH_TEST"), |
| | ("MODEL.RETINANET.INFERENCE_SCORE_THRESHOLD", "MODEL.RETINANET.SCORE_THRESH_TEST"), |
| | ("MODEL.RETINANET.INFERENCE_TOPK_CANDIDATES", "MODEL.RETINANET.TOPK_CANDIDATES_TEST"), |
| | ("MODEL.RETINANET.INFERENCE_NMS_THRESHOLD", "MODEL.RETINANET.NMS_THRESH_TEST"), |
| | ("TEST.DETECTIONS_PER_IMG", "TEST.DETECTIONS_PER_IMAGE"), |
| | ("TEST.AUG_ON", "TEST.AUG.ENABLED"), |
| | ("TEST.AUG_MIN_SIZES", "TEST.AUG.MIN_SIZES"), |
| | ("TEST.AUG_MAX_SIZE", "TEST.AUG.MAX_SIZE"), |
| | ("TEST.AUG_FLIP", "TEST.AUG.FLIP"), |
| | ] |
| |
|
| | @classmethod |
| | def upgrade(cls, cfg: CN) -> None: |
| | super().upgrade(cfg) |
| |
|
| | if cfg.MODEL.META_ARCHITECTURE == "RetinaNet": |
| | _rename( |
| | cfg, "MODEL.RETINANET.ANCHOR_ASPECT_RATIOS", "MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS" |
| | ) |
| | _rename(cfg, "MODEL.RETINANET.ANCHOR_SIZES", "MODEL.ANCHOR_GENERATOR.SIZES") |
| | del cfg["MODEL"]["RPN"]["ANCHOR_SIZES"] |
| | del cfg["MODEL"]["RPN"]["ANCHOR_ASPECT_RATIOS"] |
| | else: |
| | _rename(cfg, "MODEL.RPN.ANCHOR_ASPECT_RATIOS", "MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS") |
| | _rename(cfg, "MODEL.RPN.ANCHOR_SIZES", "MODEL.ANCHOR_GENERATOR.SIZES") |
| | del cfg["MODEL"]["RETINANET"]["ANCHOR_SIZES"] |
| | del cfg["MODEL"]["RETINANET"]["ANCHOR_ASPECT_RATIOS"] |
| | del cfg["MODEL"]["RETINANET"]["ANCHOR_STRIDES"] |
| |
|
| | @classmethod |
| | def downgrade(cls, cfg: CN) -> None: |
| | super().downgrade(cfg) |
| |
|
| | _rename(cfg, "MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS", "MODEL.RPN.ANCHOR_ASPECT_RATIOS") |
| | _rename(cfg, "MODEL.ANCHOR_GENERATOR.SIZES", "MODEL.RPN.ANCHOR_SIZES") |
| | cfg.MODEL.RETINANET.ANCHOR_ASPECT_RATIOS = cfg.MODEL.RPN.ANCHOR_ASPECT_RATIOS |
| | cfg.MODEL.RETINANET.ANCHOR_SIZES = cfg.MODEL.RPN.ANCHOR_SIZES |
| | cfg.MODEL.RETINANET.ANCHOR_STRIDES = [] |
| |
|