mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 21:44: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,
|
settings,
|
||||||
)
|
)
|
||||||
else:
|
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)
|
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(
|
request.app.config_publisher.publisher.publish(
|
||||||
body.update_topic, settings
|
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.const import CLIPS_DIR, FACE_DIR, MODEL_CACHE_DIR
|
||||||
from frigate.embeddings import EmbeddingsContext
|
from frigate.embeddings import EmbeddingsContext
|
||||||
from frigate.models import Event
|
from frigate.models import Event
|
||||||
from frigate.util.builtin import update_yaml_file_bulk
|
|
||||||
from frigate.util.classification import (
|
from frigate.util.classification import (
|
||||||
collect_object_classification_examples,
|
collect_object_classification_examples,
|
||||||
collect_state_classification_examples,
|
collect_state_classification_examples,
|
||||||
)
|
)
|
||||||
from frigate.util.config import find_config_file
|
|
||||||
from frigate.util.path import get_event_snapshot
|
from frigate.util.path import get_event_snapshot
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -840,24 +838,6 @@ def delete_classification_model(request: Request, name: str):
|
|||||||
if os.path.exists(model_dir):
|
if os.path.exists(model_dir):
|
||||||
shutil.rmtree(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(
|
return JSONResponse(
|
||||||
content=(
|
content=(
|
||||||
{
|
{
|
||||||
|
|||||||
@ -330,6 +330,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
|
|||||||
def handle_request(self, topic, request_data) -> dict[str, Any] | None:
|
def handle_request(self, topic, request_data) -> dict[str, Any] | None:
|
||||||
if topic == EmbeddingsRequestEnum.clear_face_classifier.value:
|
if topic == EmbeddingsRequestEnum.clear_face_classifier.value:
|
||||||
self.recognizer.clear()
|
self.recognizer.clear()
|
||||||
|
return {"success": True, "message": "Face classifier cleared."}
|
||||||
elif topic == EmbeddingsRequestEnum.recognize_face.value:
|
elif topic == EmbeddingsRequestEnum.recognize_face.value:
|
||||||
img = cv2.imdecode(
|
img = cv2.imdecode(
|
||||||
np.frombuffer(base64.b64decode(request_data["image"]), dtype=np.uint8),
|
np.frombuffer(base64.b64decode(request_data["image"]), dtype=np.uint8),
|
||||||
|
|||||||
@ -283,11 +283,32 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
logger.info("Exiting embeddings maintenance...")
|
logger.info("Exiting embeddings maintenance...")
|
||||||
|
|
||||||
def _check_classification_config_updates(self) -> None:
|
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()
|
topic, model_config = self.classification_config_subscriber.check_for_update()
|
||||||
|
|
||||||
if topic and model_config:
|
if topic:
|
||||||
model_name = topic.split("/")[-1]
|
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
|
self.config.classification.custom[model_name] = model_config
|
||||||
|
|
||||||
# Check if processor already exists
|
# Check if processor already exists
|
||||||
|
|||||||
@ -213,17 +213,31 @@ function ModelCard({ config, onClick, onDelete }: ModelCardProps) {
|
|||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
|
||||||
const handleDelete = useCallback(async () => {
|
const handleDelete = useCallback(async () => {
|
||||||
await axios
|
try {
|
||||||
.delete(`classification/${config.name}`)
|
// First, remove from config to stop the processor
|
||||||
.then((resp) => {
|
await axios.put("/config/set", {
|
||||||
if (resp.status == 200) {
|
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 }), {
|
toast.success(t("toast.success.deletedModel", { count: 1 }), {
|
||||||
position: "top-center",
|
position: "top-center",
|
||||||
});
|
});
|
||||||
onDelete();
|
onDelete();
|
||||||
}
|
} catch (err) {
|
||||||
})
|
const error = err as {
|
||||||
.catch((error) => {
|
response?: { data?: { message?: string; detail?: string } };
|
||||||
|
};
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
error.response?.data?.message ||
|
error.response?.data?.message ||
|
||||||
error.response?.data?.detail ||
|
error.response?.data?.detail ||
|
||||||
@ -231,7 +245,7 @@ function ModelCard({ config, onClick, onDelete }: ModelCardProps) {
|
|||||||
toast.error(t("toast.error.deleteModelFailed", { errorMessage }), {
|
toast.error(t("toast.error.deleteModelFailed", { errorMessage }), {
|
||||||
position: "top-center",
|
position: "top-center",
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}, [config, onDelete, t]);
|
}, [config, onDelete, t]);
|
||||||
|
|
||||||
const handleDeleteClick = useCallback((e: React.MouseEvent) => {
|
const handleDeleteClick = useCallback((e: React.MouseEvent) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user