mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 13:34:13 +03:00
Correctly remove classification model from config (#20772)
* Correctly remove classification model from config * Undo * fix * Use existing config update API and dynamically remove models that were running * Set update message for face
This commit is contained in:
parent
740c618240
commit
31fa87ce73
@ -403,9 +403,10 @@ def config_set(request: Request, body: AppConfigSetBody):
|
||||
settings,
|
||||
)
|
||||
else:
|
||||
# Handle nested config updates (e.g., config/classification/custom/{name})
|
||||
# Generic handling for global config updates
|
||||
settings = config.get_nested_object(body.update_topic)
|
||||
if settings:
|
||||
|
||||
# Publish None for removal, actual config for add/update
|
||||
request.app.config_publisher.publisher.publish(
|
||||
body.update_topic, settings
|
||||
)
|
||||
|
||||
@ -34,12 +34,10 @@ from frigate.config.camera import DetectConfig
|
||||
from frigate.const import CLIPS_DIR, FACE_DIR, MODEL_CACHE_DIR
|
||||
from frigate.embeddings import EmbeddingsContext
|
||||
from frigate.models import Event
|
||||
from frigate.util.builtin import update_yaml_file_bulk
|
||||
from frigate.util.classification import (
|
||||
collect_object_classification_examples,
|
||||
collect_state_classification_examples,
|
||||
)
|
||||
from frigate.util.config import find_config_file
|
||||
from frigate.util.path import get_event_snapshot
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -840,24 +838,6 @@ def delete_classification_model(request: Request, name: str):
|
||||
if os.path.exists(model_dir):
|
||||
shutil.rmtree(model_dir)
|
||||
|
||||
# Remove the model from the config file
|
||||
config_file = find_config_file()
|
||||
try:
|
||||
# Setting value to empty string deletes the key
|
||||
updates = {f"classification.custom.{name}": None}
|
||||
update_yaml_file_bulk(config_file, updates)
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating config file: {e}")
|
||||
return JSONResponse(
|
||||
content=(
|
||||
{
|
||||
"success": False,
|
||||
"message": "Failed to update config file.",
|
||||
}
|
||||
),
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
content=(
|
||||
{
|
||||
|
||||
@ -330,6 +330,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
|
||||
def handle_request(self, topic, request_data) -> dict[str, Any] | None:
|
||||
if topic == EmbeddingsRequestEnum.clear_face_classifier.value:
|
||||
self.recognizer.clear()
|
||||
return {"success": True, "message": "Face classifier cleared."}
|
||||
elif topic == EmbeddingsRequestEnum.recognize_face.value:
|
||||
img = cv2.imdecode(
|
||||
np.frombuffer(base64.b64decode(request_data["image"]), dtype=np.uint8),
|
||||
|
||||
@ -283,11 +283,32 @@ class EmbeddingMaintainer(threading.Thread):
|
||||
logger.info("Exiting embeddings maintenance...")
|
||||
|
||||
def _check_classification_config_updates(self) -> None:
|
||||
"""Check for classification config updates and add new processors."""
|
||||
"""Check for classification config updates and add/remove processors."""
|
||||
topic, model_config = self.classification_config_subscriber.check_for_update()
|
||||
|
||||
if topic and model_config:
|
||||
if topic:
|
||||
model_name = topic.split("/")[-1]
|
||||
|
||||
if model_config is None:
|
||||
self.realtime_processors = [
|
||||
processor
|
||||
for processor in self.realtime_processors
|
||||
if not (
|
||||
isinstance(
|
||||
processor,
|
||||
(
|
||||
CustomStateClassificationProcessor,
|
||||
CustomObjectClassificationProcessor,
|
||||
),
|
||||
)
|
||||
and processor.model_config.name == model_name
|
||||
)
|
||||
]
|
||||
|
||||
logger.info(
|
||||
f"Successfully removed classification processor for model: {model_name}"
|
||||
)
|
||||
else:
|
||||
self.config.classification.custom[model_name] = model_config
|
||||
|
||||
# Check if processor already exists
|
||||
|
||||
@ -213,17 +213,31 @@ function ModelCard({ config, onClick, onDelete }: ModelCardProps) {
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
await axios
|
||||
.delete(`classification/${config.name}`)
|
||||
.then((resp) => {
|
||||
if (resp.status == 200) {
|
||||
try {
|
||||
// First, remove from config to stop the processor
|
||||
await axios.put("/config/set", {
|
||||
requires_restart: 0,
|
||||
update_topic: `config/classification/custom/${config.name}`,
|
||||
config_data: {
|
||||
classification: {
|
||||
custom: {
|
||||
[config.name]: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Then, delete the model data and files
|
||||
await axios.delete(`classification/${config.name}`);
|
||||
|
||||
toast.success(t("toast.success.deletedModel", { count: 1 }), {
|
||||
position: "top-center",
|
||||
});
|
||||
onDelete();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
} catch (err) {
|
||||
const error = err as {
|
||||
response?: { data?: { message?: string; detail?: string } };
|
||||
};
|
||||
const errorMessage =
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
@ -231,7 +245,7 @@ function ModelCard({ config, onClick, onDelete }: ModelCardProps) {
|
||||
toast.error(t("toast.error.deleteModelFailed", { errorMessage }), {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [config, onDelete, t]);
|
||||
|
||||
const handleDeleteClick = useCallback((e: React.MouseEvent) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user