import os
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["NUMEXPR_NUM_THREADS"] = "1"
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["QT_LOGGING_RULES"] = "*.debug=false;qt.gui.icc=false"
import argparse
import codecs
import logging
import multiprocessing
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).resolve().parent.parent))
import yaml
from PyQt6 import QtCore, QtWidgets
from anylabeling.app_info import (
__appname__,
__version__,
__url__,
CLI_HELP_MSG,
)
from anylabeling.config import (
get_config,
set_work_directory,
get_work_directory,
)
from anylabeling import config as anylabeling_config
def main():
multiprocessing.freeze_support()
if sys.stderr is None:
sys.stderr = open(os.devnull, "w")
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(
dest="command", help="available commands"
)
subparsers.add_parser("help", help="show help message")
subparsers.add_parser(
"checks", help="display system and package information"
)
subparsers.add_parser("version", help="show version information")
subparsers.add_parser("config", help="show config file path")
convert_parser = subparsers.add_parser(
"convert", help="run conversion tasks"
)
convert_parser.add_argument(
"--task",
type=str,
help="conversion task name (e.g., yolo2xlabel, xlabel2yolo)",
)
convert_parser.add_argument(
"--images", type=str, help="image directory path"
)
convert_parser.add_argument(
"--labels", type=str, help="label directory path"
)
convert_parser.add_argument(
"--output", type=str, help="output directory path"
)
convert_parser.add_argument(
"--classes", type=str, help="classes file path"
)
convert_parser.add_argument(
"--pose-cfg", type=str, help="pose configuration file path"
)
convert_parser.add_argument("--mode", type=str, help="conversion mode")
convert_parser.add_argument(
"--mapping", type=str, help="mapping table file path"
)
convert_parser.add_argument(
"--skip-empty-files",
action="store_true",
help="skip creating empty output files, only support `xlabel2yolo` and `xlabel2voc` tasks",
)
parser.add_argument(
"--reset-config", action="store_true", help="reset qt config"
)
parser.add_argument(
"--logger-level",
default="info",
choices=["debug", "info", "warning", "fatal", "error"],
help="logger level",
)
parser.add_argument(
"--no-auto-update-check",
action="store_true",
help="disable automatic update check on startup",
)
parser.add_argument(
"--qt-platform",
help=(
"Force Qt platform plugin (e.g., 'xcb', 'wayland'). "
"If not specified, Qt will auto-detect the platform."
),
default=None,
)
parser.add_argument(
"--filename",
nargs="?",
help=(
"image or label filename; "
"If a directory path is passed in, the folder will be loaded automatically"
),
)
parser.add_argument(
"--output",
"-O",
"-o",
help=(
"output file or directory (if it ends with .json it is "
"recognized as file, else as directory)"
),
)
parser.add_argument(
"--config",
dest="config",
help="config file or yaml-format string",
default=None,
)
parser.add_argument(
"--nodata",
dest="store_data",
action="store_false",
help="stop storing image data to JSON file",
default=argparse.SUPPRESS,
)
parser.add_argument(
"--autosave",
dest="auto_save",
action="store_true",
help="auto save",
default=argparse.SUPPRESS,
)
parser.add_argument(
"--nosortlabels",
dest="sort_labels",
action="store_false",
help="stop sorting labels",
default=argparse.SUPPRESS,
)
parser.add_argument(
"--flags",
help="comma separated list of flags OR file containing flags",
default=argparse.SUPPRESS,
)
parser.add_argument(
"--labelflags",
dest="label_flags",
help=r"yaml string of label specific flags OR file containing json "
r"string of label specific flags (ex. {person-\d+: [male, tall], "
r"dog-\d+: [black, brown, white], .*: [occluded]})",
default=argparse.SUPPRESS,
)
parser.add_argument(
"--labels",
help="comma separated list of labels OR file containing labels",
default=argparse.SUPPRESS,
)
parser.add_argument(
"--validatelabel",
dest="validate_label",
choices=["exact"],
help="label validation types",
default=argparse.SUPPRESS,
)
parser.add_argument(
"--keep-prev",
action="store_true",
help="keep annotation of previous frame",
default=argparse.SUPPRESS,
)
parser.add_argument(
"--work-dir",
type=str,
help="working directory for configuration and data files",
default=os.path.expanduser("~"),
)
args = parser.parse_args()
set_work_directory(args.work_dir)
special = {
"help": lambda args: print(CLI_HELP_MSG),
"checks": lambda args: __import__(
"anylabeling.views.common.checks", fromlist=["run_checks"]
).run_checks(),
"version": lambda args: print(__version__),
"config": lambda args: print(
os.path.join(get_work_directory(), ".xanylabelingrc")
),
"convert": lambda args: __import__(
"anylabeling.views.common.converter",
fromlist=["handle_convert_command"],
).handle_convert_command(args),
}
if args.command and args.command in special:
special[args.command](args)
return
from anylabeling.views.mainwindow import MainWindow
from anylabeling.views.labeling.logger import logger
from anylabeling.views.labeling.utils import new_icon, gradient_text
from anylabeling.views.labeling.utils.theme import (
init_theme,
get_app_stylesheet,
get_dark_palette,
)
from anylabeling.views.labeling.utils.update_checker import (
check_for_updates_async,
)
from anylabeling.resources import resources
if hasattr(args, "flags"):
if os.path.isfile(args.flags):
with codecs.open(args.flags, "r", encoding="utf-8") as f:
args.flags = [line.strip() for line in f if line.strip()]
else:
args.flags = [line for line in args.flags.split(",") if line]
if hasattr(args, "labels"):
if os.path.isfile(args.labels):
with codecs.open(args.labels, "r", encoding="utf-8") as f:
args.labels = [line.strip() for line in f if line.strip()]
else:
args.labels = [line for line in args.labels.split(",") if line]
if hasattr(args, "label_flags"):
if os.path.isfile(args.label_flags):
with codecs.open(args.label_flags, "r", encoding="utf-8") as f:
args.label_flags = yaml.safe_load(f)
else:
args.label_flags = yaml.safe_load(args.label_flags)
config_from_args = args.__dict__
config_from_args.pop("command", None)
config_from_args.pop("work_dir")
reset_config = config_from_args.pop("reset_config")
filename = config_from_args.pop("filename")
output = config_from_args.pop("output")
config_file_or_yaml = config_from_args.pop("config")
if config_file_or_yaml is None:
config_file_or_yaml = os.path.join(
get_work_directory(), ".xanylabelingrc"
)
logger_level = config_from_args.pop("logger_level")
no_auto_update_check = config_from_args.pop("no_auto_update_check", False)
qt_platform = config_from_args.pop("qt_platform", None)
logger.setLevel(getattr(logging, logger_level.upper()))
logger.info(
f"🚀 {gradient_text(f'X-AnyLabeling v{__version__} launched!')}"
)
logger.info(f"⭐ If you like it, give us a star: {__url__}")
if qt_platform:
os.environ["QT_QPA_PLATFORM"] = qt_platform
logger.info(f"🖥️ Using Qt platform: {qt_platform}")
anylabeling_config.current_config_file = config_file_or_yaml
config = get_config(config_file_or_yaml, config_from_args, show_msg=True)
if not config["labels"] and config["validate_label"]:
logger.error(
"--labels must be specified with --validatelabel or "
"validate_label: exact in the config file "
"(ex. ~/.xanylabelingrc)."
)
sys.exit(1)
output_file = None
output_dir = None
if output is not None:
if output.endswith(".json"):
output_file = output
else:
output_dir = output
language = config.get("language", QtCore.QLocale.system().name())
translator = QtCore.QTranslator()
loaded_language = translator.load(
":/languages/translations/" + language + ".qm"
)
QtCore.QCoreApplication.setAttribute(
QtCore.Qt.ApplicationAttribute.AA_ShareOpenGLContexts
)
app = QtWidgets.QApplication(sys.argv)
init_theme(config.get("theme", "light"))
_dark_palette = get_dark_palette()
if _dark_palette is not None:
app.setStyle("Fusion")
app.setPalette(_dark_palette)
app.setStyleSheet(get_app_stylesheet())
app.processEvents()
app.setApplicationName(__appname__)
app.setApplicationVersion(__version__)
app.setWindowIcon(new_icon("icon"))
if loaded_language:
app.installTranslator(translator)
else:
logger.warning(
f"Failed to load translation for {language}. "
"Using default language.",
)
if reset_config:
settings = QtCore.QSettings("anylabeling", "anylabeling")
logger.info(f"Resetting Qt config: {settings.fileName()}")
settings.clear()
settings.sync()
return
win = MainWindow(
app,
config=config,
filename=filename,
output_file=output_file,
output_dir=output_dir,
)
if not no_auto_update_check:
def delayed_update_check():
check_for_updates_async(timeout=5)
QtCore.QTimer.singleShot(2000, delayed_update_check)
win.showMaximized()
win.raise_()
sys.exit(app.exec())
if __name__ == "__main__":
main()