|
|
| """
|
| 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 = []
|
|
|