async def create_camera(new_camera: CameraDTO, reboot_processor: Optional[bool] = True): """ Adds a new camera to the processor. """ config_dict = extract_config() cameras_name = [x for x in config_dict.keys() if x.startswith("Source_")] cameras = [map_camera(x, config_dict) for x in cameras_name] if new_camera.id is None: ids = [camera["id"] for camera in cameras] new_camera.id = str(get_first_unused_id(ids)) elif new_camera.id in [camera["id"] for camera in cameras]: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=bad_request_serializer( "Camera already exists", error_type="config duplicated camera")) camera_dict = map_to_camera_file_format(new_camera) config_dict[f"Source_{len(cameras)}"] = camera_dict success = update_config(config_dict, reboot_processor) if not success: return handle_response(camera_dict, success, status.HTTP_201_CREATED) camera_screenshot_directory = os.path.join( os.environ.get("ScreenshotsDirectory"), new_camera.id) Path(camera_screenshot_directory).mkdir(parents=True, exist_ok=True) heatmap_directory = os.path.join(os.getenv("SourceLogDirectory"), new_camera.id, "objects_log") Path(heatmap_directory).mkdir(parents=True, exist_ok=True) return next( (camera for camera in get_cameras() if camera["id"] == camera_dict["Id"]), None)
async def create_logger(new_logger: SourceLoggerDTO, reboot_processor: Optional[bool] = True): """ Adds a logger. """ config_dict = extract_config() loggers_index = [int(x[-1]) for x in config_dict.keys() if x.startswith("SourceLogger_")] loggers = get_source_loggers() try: validate_logger(new_logger) except ValidationError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=bad_request_serializer(str(e)) ) if new_logger.name in [ps["name"] for ps in loggers]: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=bad_request_serializer("Logger already exists", error_type="config duplicated logger") ) logger_file = map_to_config_file_format(new_logger, True) index = 0 if loggers_index: index = max(loggers_index) + 1 config_dict[f"SourceLogger_{index}"] = logger_file success = update_config(config_dict, reboot_processor) if not success: return handle_response(logger_file, success, status.HTTP_201_CREATED) return next((ps for ps in get_source_loggers() if ps["name"] == logger_file["Name"]), None)
async def delete_area(area_id: str, reboot_processor: Optional[bool] = True): """ Deletes the configuration related to the area <area_id> """ if area_id.upper() == ALL_AREAS: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=bad_request_serializer( "Area with ID: 'ALL' cannot be deleted.", error_type="Invalid ID")) config_dict = extract_config() areas_name = [x for x in config_dict.keys() if x.startswith("Area_")] areas = [map_section_from_config(x, config_dict) for x in areas_name] areas_ids = [area["id"] for area in areas] try: index = areas_ids.index(area_id) except ValueError: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"The area: {area_id} does not exist") config_dict.pop(f"Area_{index}") config_dict = reestructure_areas((config_dict)) success = update_config(config_dict, reboot_processor) area_directory = os.path.join(os.getenv("AreaLogDirectory"), area_id) shutil.rmtree(area_directory) return handle_response(None, success, status.HTTP_204_NO_CONTENT)
async def create_area(new_area: AreaConfigDTO, reboot_processor: Optional[bool] = True): """ Adds a new area to the processor. """ config_dict = extract_config() areas_name = [x for x in config_dict.keys() if x.startswith("Area_")] areas = [map_section_from_config(x, config_dict) for x in areas_name] if new_area.id in [area["id"] for area in areas]: raise HTTPException(status_code=400, detail="Area already exists") cameras = [x for x in config_dict.keys() if x.startswith("Source_")] cameras = [map_camera(x, config_dict, []) for x in cameras] camera_ids = [camera["id"] for camera in cameras] if not all(x in camera_ids for x in new_area.cameras.split(",")): non_existent_cameras = set( new_area.cameras.split(",")) - set(camera_ids) raise HTTPException( status_code=404, detail=f"The cameras: {non_existent_cameras} do not exist") area_dict = map_to_config_file_format(new_area) config_dict[f"Area_{len(areas)}"] = area_dict success = update_config(config_dict, reboot_processor) return handle_response(area_dict, success, status.HTTP_201_CREATED)
async def edit_logger(logger_name: str, edited_logger: SourceLoggerDTO, reboot_processor: Optional[bool] = True): """ Edits the configuration related to the logger <logger_name> """ edited_logger.name = logger_name config_dict = extract_config() edited_logger_section = next(( key for key, value in config_dict.items() if key.startswith("SourceLogger_") and value["Name"] == logger_name ), None) if not edited_logger_section: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"The logger: {logger_name} does not exist") try: validate_logger(edited_logger) except ValidationError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=bad_request_serializer(str(e)) ) logger_file = map_to_config_file_format(edited_logger, True) config_dict[edited_logger_section] = logger_file success = update_config(config_dict, reboot_processor) if not success: return handle_response(logger_file, success) return next((ps for ps in get_source_loggers() if ps["name"] == logger_name), None)
async def edit_area(area_id: str, edited_area: AreaConfigDTO, reboot_processor: Optional[bool] = True): """ Edits the configuration related to the area <area_id> """ edited_area.id = area_id config_dict = extract_config() area_names = [x for x in config_dict.keys() if x.startswith("Area_")] areas = [map_section_from_config(x, config_dict) for x in area_names] areas_ids = [area["id"] for area in areas] try: index = areas_ids.index(area_id) except ValueError: raise HTTPException(status_code=404, detail=f"The area: {area_id} does not exist") cameras = [x for x in config_dict.keys() if x.startswith("Source_")] cameras = [map_camera(x, config_dict, []) for x in cameras] camera_ids = [camera["id"] for camera in cameras] if not all(x in camera_ids for x in edited_area.cameras.split(",")): non_existent_cameras = set( edited_area.cameras.split(",")) - set(camera_ids) raise HTTPException( status_code=404, detail=f"The cameras: {non_existent_cameras} do not exist") area_dict = map_to_config_file_format(edited_area) config_dict[f"Area_{index}"] = area_dict success = update_config(config_dict, reboot_processor) return handle_response(area_dict, success)
async def create_periodic_task(new_periodic_task: PeriodicTaskDTO, reboot_processor: Optional[bool] = True): """ Adds a periodic task. """ config_dict = extract_config() periodic_tasks_index = [ int(x[-1]) for x in config_dict.keys() if x.startswith("PeriodicTask_") ] periodic_tasks = get_periodic_tasks() try: validate_periodic_task(new_periodic_task) except ValidationError as e: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) if new_periodic_task.name in [ps["name"] for ps in periodic_tasks]: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Periodict task already exists") periodic_task_file = map_to_config_file_format(new_periodic_task, True) index = 0 if periodic_tasks_index: index = max(periodic_tasks_index) + 1 config_dict[f"PeriodicTask_{index}"] = periodic_task_file success = update_config(config_dict, reboot_processor) return handle_response(periodic_task_file, success, status.HTTP_201_CREATED)
async def edit_periodic_task(periodic_task_name: str, edited_periodic_task: PeriodicTaskDTO, reboot_processor: Optional[bool] = True): """ Edits the configuration related to the periodic task <periodic_task_name>. """ edited_periodic_task.name = periodic_task_name config_dict = extract_config() edited_periodic_task_section = next( (key for key, value in config_dict.items() if key.startswith("PeriodicTask_") and value["Name"] == periodic_task_name), None) if not edited_periodic_task_section: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"The periodic_task: {periodic_task_name} does not exist") try: validate_periodic_task(edited_periodic_task) except ValidationError as e: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) periodic_task_file = map_to_config_file_format(edited_periodic_task, True) config_dict[edited_periodic_task_section] = periodic_task_file success = update_config(config_dict, reboot_processor) return handle_response(periodic_task_file, success)
async def delete_area(area_id: str, reboot_processor: Optional[bool] = True): """ Deletes the configuration related to the area <area_id> """ if area_id.upper() == ALL_AREAS: delete_area_occupancy_rules(ALL_AREAS) raise HTTPException( status_code=status.HTTP_202_ACCEPTED, detail="Area with ID: 'ALL' cannot be deleted. However, its occupancy rules were deleted." ) config_dict = extract_config() areas_name = [x for x in config_dict.keys() if x.startswith("Area_")] areas = [map_section_from_config(x, config_dict) for x in areas_name] areas_ids = [area["id"] for area in areas] try: index = areas_ids.index(area_id) except ValueError: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"The area: {area_id} does not exist") config_dict.pop(f"Area_{index}") config_dict = reestructure_areas(config_dict) success = update_config(config_dict, reboot_processor) delete_area_occupancy_rules(area_id) area_directory = os.path.join(os.getenv("AreaLogDirectory"), area_id) shutil.rmtree(area_directory) area_config_directory = os.path.join(os.getenv("AreaConfigDirectory"), area_id) shutil.rmtree(area_config_directory) return handle_response(None, success, status.HTTP_204_NO_CONTENT)
def add_slack_channel_to_config(channel, reboot_processor): logger.info("Adding slack's channel on processor's config") config_dict = dict() config_dict["App"] = dict({"SlackChannel": channel}) success = update_config(config_dict, reboot_processor) return handle_response(config_dict, success)
async def create_area(new_area: AreaConfigDTO, reboot_processor: Optional[bool] = True): """ Adds a new area to the processor. """ config_dict = extract_config() areas_name = [x for x in config_dict.keys() if x.startswith("Area_")] areas = [map_section_from_config(x, config_dict) for x in areas_name] if new_area.id in [area["id"] for area in areas]: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=bad_request_serializer( "Area already exists", error_type="config duplicated area")) cameras = [x for x in config_dict.keys() if x.startswith("Source_")] cameras = [map_camera(x, config_dict, []) for x in cameras] camera_ids = [camera["id"] for camera in cameras] if not all(x in camera_ids for x in new_area.cameras.split(",")): non_existent_cameras = set( new_area.cameras.split(",")) - set(camera_ids) raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"The cameras: {non_existent_cameras} do not exist") area_dict = map_to_config_file_format(new_area) config_dict[f"Area_{len(areas)}"] = area_dict success = update_config(config_dict, reboot_processor) if not success: return handle_response(area_dict, success, status.HTTP_201_CREATED) return next( (area for area in get_areas() if area["id"] == area_dict["Id"]), None)
async def update_config_file(config: ConfigDTO, reboot_processor: Optional[bool] = True): """ Overwrites the configuration used by the processor. """ config_dict = map_to_file_format(config) success = update_config(config_dict, reboot_processor) return handle_response(config_dict, success)
def enable_slack(token_config, reboot_processor): write_user_token(token_config.user_token) logger.info("Enabling slack notification on processor's config") config_dict = dict() config_dict["App"] = dict({"SlackChannel": token_config.channel}) success = update_config(config_dict, reboot_processor) return handle_response(config_dict, success)
def update_classifier_config(classifier: ClassifierDTO, reboot_processor: Optional[bool] = True): """ Updates the classifier configuration of the processor """ config_dict = extract_config() classifier_dict = map_to_config_file_format(classifier) config_dict["Classifier"] = classifier_dict success = update_config(config_dict, reboot_processor) return handle_response(classifier_dict, success)
def update_app_config(app: AppDTO, reboot_processor: Optional[bool] = True): """ Updates the app configuration of the processor """ config_dict = extract_config() app_dict = map_to_config_file_format(app) config_dict["App"] = app_dict success = update_config(config_dict, reboot_processor) return handle_response(app_dict, success)
def update_core_config(core: CoreDTO, reboot_processor: Optional[bool] = True): """ Updates the core configuration of the processor """ config_dict = extract_config() core_dict = map_to_config_file_format(core) config_dict["CORE"] = core_dict success = update_config(config_dict, reboot_processor) return handle_response(core_dict, success)
def update_tracker_config(tracker: TrackerDTO, reboot_processor: Optional[bool] = True): """ Updates the tracker configuration of the processor """ config_dict = extract_config() tracker_dict = map_to_config_file_format(tracker) config_dict["Tracker"] = tracker_dict success = update_config(config_dict, reboot_processor) return handle_response(tracker_dict, success)
def update_detector_config(detector: DetectorDTO, reboot_processor: Optional[bool] = True): """ Updates the detector configuration of the processor """ config_dict = extract_config() detector_dict = map_to_config_file_format(detector) config_dict["Detector"] = detector_dict success = update_config(config_dict, reboot_processor) return handle_response(detector_dict, success)
async def update_report_info(global_report_info: GlobalReportingEmailsInfo, reboot_processor: Optional[bool] = True): global_report_info = global_report_info.dict(exclude_unset=True, exclude_none=True) config_dict = extract_config() key_mapping = {"GlobalReportingEmails": "emails", "GlobalReportTime": "time", "DailyGlobalReport": "daily", "WeeklyGlobalReport": "weekly"} for key, value in key_mapping.items(): if value in global_report_info: config_dict["App"][key] = str(global_report_info[value]) success = update_config(config_dict, reboot_processor) return handle_response(config_dict, success)
async def delete_camera(camera_id: str, reboot_processor: Optional[bool] = True): """ Deletes the configuration related to the camera <camera_id> """ config_dict = extract_config() index = get_camera_index(config_dict, camera_id) config_dict = delete_camera_from_areas(camera_id, config_dict) config_dict.pop(f"Source_{index}") config_dict = reestructure_cameras((config_dict)) success = update_config(config_dict, reboot_processor) return handle_response(None, success, status.HTTP_204_NO_CONTENT)
async def remove_in_out_boundaries(camera_id: str, reboot_processor: Optional[bool] = True): """ Delete the defined In/Out boundaries for a camera. """ validate_camera_existence(camera_id) in_out_file_path = InOutMetric.get_in_out_file_path(camera_id, settings.config) if not validate_file_exists_and_is_not_empty(in_out_file_path): detail = f"There is no defined In/Out Boundary for {camera_id}" raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=detail) os.remove(in_out_file_path) success = restart_processor() if reboot_processor else True return handle_response(None, success, status.HTTP_204_NO_CONTENT)
async def remove_roi_contour(camera_id: str, reboot_processor: Optional[bool] = True): """ Delete the defined RoI for a camera. """ validate_camera_existence(camera_id) roi_file_path = ObjectsFilteringPostProcessor.get_roi_file_path(camera_id, settings.config) if not validate_file_exists_and_is_not_empty(roi_file_path): detail = f"There is no defined RoI for {camera_id}" raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=detail) os.remove(roi_file_path) success = restart_processor() if reboot_processor else True return handle_response(None, success, status.HTTP_204_NO_CONTENT)
def delete_area_occupancy_rules(area_id: str): area_config_path = get_config().get_area_config_path(area_id) if os.path.exists(area_config_path): with open(area_config_path, "r") as area_file: data = json.load(area_file) else: return handle_response(None, False) with open(area_config_path, "w") as area_file: if data.get("occupancy_rules") is not None: del data["occupancy_rules"] json.dump(data, area_file)
async def edit_camera(camera_id: str, edited_camera: CameraDTO, reboot_processor: Optional[bool] = True): """ Edits the configuration related to the camera <camera_id> """ edited_camera.id = camera_id config_dict = extract_config() index = get_camera_index(config_dict, camera_id) camera_dict = map_to_camera_file_format(edited_camera) config_dict[f"Source_{index}"] = map_to_camera_file_format(edited_camera) success = update_config(config_dict, reboot_processor) if not success: return handle_response(camera_dict, success) return next((camera for camera in get_cameras(["withImage"]) if camera["id"] == camera_id), None)
async def enable_video_live_feed(camera_id: str, disable_other_cameras: Optional[bool] = True): """ Enables the video live feed for the camera <camera_id>. By default, the video live feed for the other cameras will be disabled. You can change that behavior sending the *disable_other_cameras* parameter in *False*. """ config_dict = extract_config() index = get_camera_index(config_dict, camera_id) if disable_other_cameras: for camera_section in [x for x in config_dict.keys() if x.startswith("Source_")]: config_dict[camera_section]["LiveFeedEnabled"] = "False" config_dict[f"Source_{index}"]["LiveFeedEnabled"] = "True" success = update_config(config_dict, True) return handle_response(None, success, status.HTTP_204_NO_CONTENT)
async def create_camera(new_camera: CameraDTO, reboot_processor: Optional[bool] = True): """ Adds a new camera to the processor. """ config_dict = extract_config() cameras_name = [x for x in config_dict.keys() if x.startswith("Source_")] cameras = [map_camera(x, config_dict) for x in cameras_name] if new_camera.id in [camera["id"] for camera in cameras]: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Camera already exists") camera_dict = map_to_camera_file_format(new_camera) config_dict[f"Source_{len(cameras)}"] = camera_dict success = update_config(config_dict, reboot_processor) return handle_response(camera_dict, success, status.HTTP_201_CREATED)
async def edit_area(area_id: str, edited_area: AreaConfigDTO, reboot_processor: Optional[bool] = True): """ Edits the configuration related to the area <area_id> """ if area_id.upper() == ALL_AREAS: area = modify_area_all(edited_area) if edited_area.occupancy_rules: set_occupancy_rules(ALL_AREAS, edited_area.occupancy_rules) else: delete_area_occupancy_rules(ALL_AREAS) area["occupancy_rules"] = get_area_occupancy_rules(ALL_AREAS) return area edited_area.id = area_id config_dict = extract_config() area_names = [x for x in config_dict.keys() if x.startswith("Area_")] areas = [map_section_from_config(x, config_dict) for x in area_names] areas_ids = [area["id"] for area in areas] try: index = areas_ids.index(area_id) except ValueError: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"The area: {area_id} does not exist") cameras = [x for x in config_dict.keys() if x.startswith("Source_")] cameras = [map_camera(x, config_dict, []) for x in cameras] camera_ids = [camera["id"] for camera in cameras] if not all(x in camera_ids for x in edited_area.cameras.split(",")): non_existent_cameras = set(edited_area.cameras.split(",")) - set(camera_ids) raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"The cameras: {non_existent_cameras}" f"do not exist") occupancy_rules = edited_area.occupancy_rules del edited_area.occupancy_rules area_dict = map_to_config_file_format(edited_area) config_dict[f"Area_{index}"] = area_dict success = update_config(config_dict, reboot_processor) if occupancy_rules: set_occupancy_rules(edited_area.id, occupancy_rules) else: delete_area_occupancy_rules(area_id) if not success: return handle_response(area_dict, success) area = next((area for area in get_areas() if area["id"] == area_id), None) area["occupancy_rules"] = get_area_occupancy_rules(area["id"]) return area
async def delete_camera(camera_id: str, reboot_processor: Optional[bool] = True): """ Deletes the configuration related to the camera <camera_id> """ config_dict = extract_config() index = get_camera_index(config_dict, camera_id) config_dict = delete_camera_from_areas(camera_id, config_dict) config_dict.pop(f"Source_{index}") config_dict = reestructure_cameras((config_dict)) success = update_config(config_dict, reboot_processor) # Deletes the camera screenshots directory and all its content. camera_screenshot_directory = os.path.join(os.environ.get("ScreenshotsDirectory"), camera_id) shutil.rmtree(camera_screenshot_directory) return handle_response(None, success, status.HTTP_204_NO_CONTENT)
async def config_calibrated_distance(camera_id: str, body: ConfigHomographyMatrix, reboot_processor: Optional[bool] = True): """ Calibrates the camera <camera_id> receiving as input the coordinates of a square of size 3ft 3" by 3ft 3" (1m by 1m). """ dir_source = next((source for source in settings.config.get_video_sources() if source["id"] == camera_id), None) if not dir_source: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"The camera: {camera_id} does not exist") dir_path = get_camera_calibration_path(settings.config, camera_id) compute_and_save_inv_homography_matrix(points=body, destination=dir_path) sections = settings.config.get_sections() config_dict = {} for section in sections: config_dict[section] = settings.config.get_section_dict(section) config_dict[dir_source["section"]]["DistMethod"] = "CalibratedDistance" success = update_config(config_dict, reboot_processor) return handle_response(None, success, status.HTTP_204_NO_CONTENT)
async def delete_post_processor(post_processor_name: str, reboot_processor: Optional[bool] = True): """ Deletes the configuration related to the post processor <post_processor_name> """ config_dict = extract_config() post_processor_section = next(( key for key, value in config_dict.items() if key.startswith("SourcePostProcessor_") and value["Name"] == post_processor_name ), None) if not post_processor_section: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"The post processor: {post_processor_name} does not exist") config_dict.pop(post_processor_section) success = update_config(config_dict, reboot_processor) return handle_response(None, success, status.HTTP_204_NO_CONTENT)