def get_cameras_to_export(export_info: ExportDTO, areas: List[Tuple[str, str]]) -> List[Tuple[str, str]]: """ Returns the list of cameras (camera_id, camera_list) requested in the <export_info> and the cameras included in the <areas>. """ all_cameras = extract_config("cameras").values() selected_cameras = [] if export_info.all_cameras: selected_cameras = all_cameras else: selected_cameras = [c for c in all_cameras if c["Id"] in export_info.cameras] if len(selected_cameras) != len(export_info.cameras): # Some of the selected cameras don't exist missing_cameras = set(export_info.cameras) - set([c["Id"]for c in selected_cameras]) raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Cameras with ids {missing_cameras} don't exist." ) # Include areas' cameras areas_cameras = [] areas_ids = [a[0] for a in areas] for area in [a for a in extract_config("areas").values() if a['Id'] in areas_ids]: areas_cameras.extend(area["Cameras"].split(",")) selected_cameras.extend( [c for c in all_cameras if c["Id"] in areas_cameras and c["Id"] not in export_info.cameras] ) if selected_cameras: return [(camera["Id"], camera["Name"]) for camera in selected_cameras] return []
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) if not success: return handle_response(classifier_dict, success) return map_section_from_config("Classifier", extract_config())
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) if not success: return handle_response(core_dict, success) return map_section_from_config("CORE", extract_config())
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) if not success: return handle_response(detector_dict, success) return map_section_from_config("Detector", extract_config())
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) if not success: return handle_response(app_dict, success) return map_section_from_config("App", extract_config())
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_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_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 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 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)
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)
def sync_dashboard(): """ Sync the processor data in the cloud dashbord """ config_dict = extract_config() dashboard_url = config_dict["App"]["DashboardURL"] endpoint_url = f"{dashboard_url}api/processor/sync/cameras" authorization_token = config_dict["App"].get("DashboardAuthorizationToken") headers = { "content-type": "application/json", "Authorization": authorization_token } if not authorization_token: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing authorization token") cameras = [{ "processor_camera_id": camera["id"], "name": camera["name"], "has_been_calibrated": camera["has_been_calibrated"] } for camera in get_cameras()] response = requests.put(endpoint_url, json=cameras, headers=headers) if response.status_code != status.HTTP_200_OK: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error syncing processor with dashbord") return response.json()
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)
def get_areas(areas: str) -> Iterator[str]: if ALL_AREAS in areas.upper().split(","): return [ALL_AREAS] if areas: return areas.split(",") config = extract_config(config_type=AREAS) return [x["Id"] for x in config.values()]
def get_cameras_for_areas(areas: Iterator[str]) -> Iterator[str]: config = extract_config(config_type=AREAS) cameras = [] for area_config in config.values(): if area_config["Id"] in areas: cameras.extend(area_config[CAMERAS].split(",")) return cameras
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 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 get_report_info(): app_config = extract_config()["App"] return { "emails": app_config["GlobalReportingEmails"], "time": app_config["GlobalReportTime"], "daily": get_config().get_boolean("App", "DailyGlobalReport"), "weekly": get_config().get_boolean("App", "WeeklyGlobalReport") }
def get_cameras_for_areas(areas: Iterator[str]) -> Iterator[str]: if areas == [ALL_AREAS]: return get_all_cameras() config = extract_config(config_type=AREAS) cameras = [] for area_config in config.values(): if area_config["Id"] in areas: cameras.extend(area_config[CAMERAS.capitalize()].split(",")) return cameras
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) if not success: return handle_response(config_dict, success) return map_config(extract_config(), "")
async def get_video_live_feed_enabled(camera_id: str): """ Returns *True* if the video live feed is enabled for the camera <camera_id> """ config_dict = extract_config() index = get_camera_index(config_dict, camera_id) config = get_config() return { "enabled": config.get_boolean(f"Source_{index}", "LiveFeedEnabled") }
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)
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 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 export_all_data(background_tasks: BackgroundTasks): """ Returns a zip file containing the csv files for all cameras and areas """ # TODO: Improve this function cameras = [(section_dict["Id"], section_dict["Name"]) for section_dict in extract_config("cameras").values()] areas = [(section_dict["Id"], section_dict["Name"]) for section_dict in extract_config("areas").values()] temp_dir = tempfile.mkdtemp() export_filename = f"export-{date.today()}.zip" zip_path = os.path.join(temp_dir, export_filename) with ZipFile(zip_path, 'w', compression=ZIP_DEFLATED) as export_zip: for (cam_id, name) in cameras: object_logs_path = os.path.join(os.getenv("SourceLogDirectory"), cam_id, "objects_log") social_ditancing_reports_folder = f"reports/{SocialDistancingMetric.reports_folder}" social_ditancing_reports_path = os.path.join(os.getenv("SourceLogDirectory"), cam_id, social_ditancing_reports_folder) face_mask_reports_folder = f"reports/{FaceMaskUsageMetric.reports_folder}" face_mask_reports_path = os.path.join(os.getenv("SourceLogDirectory"), cam_id, face_mask_reports_folder) export_folder_into_zip(object_logs_path, os.path.join( "cameras", f"{cam_id}-{name}", "raw_data"), export_zip) export_folder_into_zip(social_ditancing_reports_path, os.path.join( "cameras", f"{cam_id}-{name}", social_ditancing_reports_folder), export_zip) export_folder_into_zip(face_mask_reports_path, os.path.join( "cameras", f"{cam_id}-{name}", face_mask_reports_folder), export_zip) for (area_id, name) in areas: occupancy_logs_path = os.path.join(os.getenv("AreaLogDirectory"), area_id, "occupancy_log") occupancy_report_folder = f"reports/{OccupancyMetric.reports_folder}" occupancy_report_path = os.path.join(os.getenv("AreaLogDirectory"), area_id, occupancy_report_folder) export_folder_into_zip(occupancy_logs_path, os.path.join( "areas", f"{area_id}-{name}", "raw_data"), export_zip) export_folder_into_zip(occupancy_report_path, os.path.join( "areas", f"{area_id}-{name}", occupancy_report_folder), export_zip) background_tasks.add_task(clean_up_file, temp_dir) return FileResponse(zip_path, filename=export_filename)
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