if not api_options.get('apiurl') or not api_options.get('portalurl'): g.log.Fatal('Missing API and/or Portal URLs. Your secrets file probably doesn\'t have these values') zmapi = zmapi.ZMApi(options=api_options, logger=g.log) ml_options = {} stream_options = {} if g.config['ml_sequence'] and g.config['use_sequence'] == 'yes': g.log.Debug(2,'using ml_sequence') ml_options = g.config['ml_sequence'] secrets = pyzmutils.read_config(g.config['secrets']) ml_options = pyzmutils.template_fill(input_str=ml_options, config=None, secrets=secrets._sections.get('secrets')) #print (ml_options) ml_options = ast.literal_eval(ml_options) #print (ml_options) else: g.logger.Debug(2,'mapping legacy ml data from config') ml_options = utils.convert_config_to_ml_sequence() g.config['ml_options'] = ml_options # stream options will come from zm_detect #print(ml_options) m = DetectSequence(options=ml_options, logger=g.log)
def main_handler(): # set up logging to syslog # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument('-c', '--config', help='config file with path') ap.add_argument('-e', '--eventid', help='event ID to retrieve') ap.add_argument('-p', '--eventpath', help='path to store object image file', default='') ap.add_argument('-m', '--monitorid', help='monitor id - needed for mask') ap.add_argument('-v', '--version', help='print version and quit', action='store_true') ap.add_argument('-o', '--output-path', help='internal testing use only - path for debug images to be written') ap.add_argument('-f', '--file', help='internal testing use only - skips event download') ap.add_argument('-r', '--reason', help='reason for event (notes field in ZM)') ap.add_argument('-n', '--notes', help='updates notes field in ZM with detections', action='store_true') ap.add_argument('-d', '--debug', help='enables debug on console', action='store_true') args, u = ap.parse_known_args() args = vars(args) if args.get('version'): print('hooks:{} pyzm:{}'.format(hooks_version, pyzm_version)) exit(0) if not args.get('config'): print ('--config required') exit(1) if not args.get('file')and not args.get('eventid'): print ('--eventid required') exit(1) utils.get_pyzm_config(args) if args.get('debug'): g.config['pyzm_overrides']['dump_console'] = True g.config['pyzm_overrides']['log_debug'] = True g.config['pyzm_overrides']['log_level_debug'] = 5 g.config['pyzm_overrides']['log_debug_target'] = None if args.get('monitorid'): log.init(name='zmesdetect_' + 'm' + args.get('monitorid'), override=g.config['pyzm_overrides']) else: log.init(name='zmesdetect',override=g.config['pyzm_overrides']) g.logger = log es_version='(?)' try: es_version=subprocess.check_output(['/usr/bin/zmeventnotification.pl', '--version']).decode('ascii') except: pass try: import cv2 except ImportError as e: g.logger.Fatal (f'{e}: You might not have installed OpenCV as per install instructions. Remember, it is NOT automatically installed') g.logger.Info('---------| pyzm version:{}, hook version:{}, ES version:{} , OpenCV version:{}|------------'.format(pyzm_version, hooks_version, es_version, cv2.__version__)) # load modules that depend on cv2 try: import zmes_hook_helpers.image_manip as img except Exception as e: g.logger.Error (f'{e}') exit(1) g.polygons = [] # process config file g.ctx = ssl.create_default_context() utils.process_config(args, g.ctx) # misc came later, so lets be safe if not os.path.exists(g.config['base_data_path'] + '/misc/'): try: os.makedirs(g.config['base_data_path'] + '/misc/') except FileExistsError: pass # if two detects run together with a race here if not g.config['ml_gateway']: g.logger.Info('Importing local classes for Object/Face') import pyzm.ml.object as object_detection else: g.logger.Info('Importing remote shim classes for Object/Face') from zmes_hook_helpers.apigw import ObjectRemote, FaceRemote, AlprRemote # now download image(s) start = datetime.datetime.now() obj_json = [] import pyzm.api as zmapi api_options = { 'apiurl': g.config['api_portal'], 'portalurl': g.config['portal'], 'user': g.config['user'], 'password': g.config['password'] , 'logger': g.logger, # use none if you don't want to log to ZM, #'disable_ssl_cert_check': True } g.logger.Info('Connecting with ZM APIs') zmapi = zmapi.ZMApi(options=api_options) stream = args.get('eventid') or args.get('file') ml_options = {} stream_options={} secrets = None if g.config['ml_sequence'] and g.config['use_sequence'] == 'yes': g.logger.Debug(2,'using ml_sequence') ml_options = g.config['ml_sequence'] secrets = pyzmutils.read_config(g.config['secrets']) ml_options = pyzmutils.template_fill(input_str=ml_options, config=None, secrets=secrets._sections.get('secrets')) ml_options = ast.literal_eval(ml_options) g.config['ml_sequence'] = ml_options else: g.logger.Debug(2,'mapping legacy ml data from config') ml_options = utils.convert_config_to_ml_sequence() if g.config['stream_sequence'] and g.config['use_sequence'] == 'yes': # new sequence g.logger.Debug(2,'using stream_sequence') stream_options = g.config['stream_sequence'] stream_options = ast.literal_eval(stream_options) g.config['stream_sequence'] = stream_options else: # legacy g.logger.Debug(2,'mapping legacy stream data from config') if g.config['detection_mode'] == 'all': g.config['detection_mode'] = 'most_models' frame_set = g.config['frame_id'] if g.config['frame_id'] == 'bestmatch': if g.config['bestmatch_order'] == 's,a': frame_set = 'snapshot,alarm' else: frame_set = 'alarm,snapshot' stream_options['resize'] =int(g.config['resize']) if g.config['resize'] != 'no' else None stream_options['strategy'] = g.config['detection_mode'] stream_options['frame_set'] = frame_set # These are stream options that need to be set outside of supplied configs stream_options['api'] = zmapi stream_options['polygons'] = g.polygons ''' stream_options = { 'api': zmapi, 'download': False, 'frame_set': frame_set, 'strategy': g.config['detection_mode'], 'polygons': g.polygons, 'resize': int(g.config['resize']) if g.config['resize'] != 'no' else None } ''' m = None matched_data = None all_data = None if not args['file'] and int(g.config['wait']) > 0: g.logger.Info('Sleeping for {} seconds before inferencing'.format( g.config['wait'])) time.sleep(g.config['wait']) if g.config['ml_gateway']: stream_options['api'] = None stream_options['monitorid'] = args.get('monitorid') start = datetime.datetime.now() try: matched_data,all_data = remote_detect(stream=stream, options=stream_options, api=zmapi) diff_time = (datetime.datetime.now() - start) g.logger.Debug(1,'Total remote detection detection took: {}'.format(diff_time)) except Exception as e: g.logger.Error ("Error with remote mlapi:{}".format(e)) g.logger.Debug(2,traceback.format_exc()) if g.config['ml_fallback_local'] == 'yes': g.logger.Debug (1, "Falling back to local detection") stream_options['api'] = zmapi from pyzm.ml.detect_sequence import DetectSequence m = DetectSequence(options=ml_options, logger=g.logger) matched_data,all_data = m.detect_stream(stream=stream, options=stream_options) else: from pyzm.ml.detect_sequence import DetectSequence m = DetectSequence(options=ml_options, logger=g.logger) matched_data,all_data = m.detect_stream(stream=stream, options=stream_options) #print(f'ALL FRAMES: {all_data}\n\n') #print (f"SELECTED FRAME {matched_data['frame_id']}, size {matched_data['image_dimensions']} with LABELS {matched_data['labels']} {matched_data['boxes']} {matched_data['confidences']}") #print (matched_data) ''' matched_data = { 'boxes': matched_b, 'labels': matched_l, 'confidences': matched_c, 'frame_id': matched_frame_id, 'image_dimensions': self.media.image_dimensions(), 'image': matched_frame_img } ''' # let's remove past detections first, if enabled if g.config['match_past_detections'] == 'yes' and args.get('monitorid'): # point detections to post processed data set g.logger.Info('Removing matches to past detections') bbox_t, label_t, conf_t = img.processPastDetection( matched_data['boxes'], matched_data['labels'], matched_data['confidences'], args.get('monitorid')) # save current objects for future comparisons g.logger.Debug(1, 'Saving detections for monitor {} for future match'.format( args.get('monitorid'))) try: mon_file = g.config['image_path'] + '/monitor-' + args.get( 'monitorid') + '-data.pkl' f = open(mon_file, "wb") pickle.dump(matched_data['boxes'], f) pickle.dump(matched_data['labels'], f) pickle.dump(matched_data['confidences'], f) f.close() except Exception as e: g.logger.Error(f'Error writing to {mon_file}, past detections not recorded:{e}') matched_data['boxes'] = bbox_t matched_data['labels'] = label_t matched_data['confidences'] = conf_t obj_json = { 'labels': matched_data['labels'], 'boxes': matched_data['boxes'], 'frame_id': matched_data['frame_id'], 'confidences': matched_data['confidences'], 'image_dimensions': matched_data['image_dimensions'] } # 'confidences': ["{:.2f}%".format(item * 100) for item in matched_data['confidences']], detections = [] seen = {} pred='' prefix = '' if matched_data['frame_id'] == 'snapshot': prefix = '[s] ' elif matched_data['frame_id'] == 'alarm': prefix = '[a] ' else: prefix = '[x] ' #g.logger.Debug (1,'CONFIDENCE ARRAY:{}'.format(conf)) for idx, l in enumerate(matched_data['labels']): if l not in seen: if g.config['show_percent'] == 'no': pred = pred + l + ',' else: pred = pred + l + ':{:.0%}'.format(matched_data['confidences'][idx]) + ' ' seen[l] = 1 if pred != '': pred = pred.rstrip(',') pred = prefix + 'detected:' + pred g.logger.Info('Prediction string:{}'.format(pred)) jos = json.dumps(obj_json) g.logger.Debug(1,'Prediction string JSON:{}'.format(jos)) print(pred + '--SPLIT--' + jos) if (matched_data['image'] is not None) and (g.config['write_image_to_zm'] == 'yes' or g.config['write_debug_image'] == 'yes'): debug_image = pyzmutils.draw_bbox(image=matched_data['image'],boxes=matched_data['boxes'], labels=matched_data['labels'], confidences=matched_data['confidences'], polygons=g.polygons, poly_thickness = g.config['poly_thickness']) if g.config['write_debug_image'] == 'yes': for _b in matched_data['error_boxes']: cv2.rectangle(debug_image, (_b[0], _b[1]), (_b[2], _b[3]), (0,0,255), 1) filename_debug = g.config['image_path']+'/'+os.path.basename(append_suffix(stream, '-{}-debug'.format(matched_data['frame_id']))) g.logger.Debug (1,'Writing bound boxes to debug image: {}'.format(filename_debug)) cv2.imwrite(filename_debug,debug_image) if g.config['write_image_to_zm'] == 'yes' and args.get('eventpath'): g.logger.Debug(1,'Writing detected image to {}/objdetect.jpg'.format( args.get('eventpath'))) cv2.imwrite(args.get('eventpath') + '/objdetect.jpg', debug_image) jf = args.get('eventpath')+ '/objects.json' g.logger.Debug(1,'Writing JSON output to {}'.format(jf)) try: with open(jf, 'w') as jo: json.dump(obj_json, jo) jo.close() except Exception as e: g.logger.Error(f'Error creating {jf}:{e}') if args.get('notes'): url = '{}/events/{}.json'.format(g.config['api_portal'], args['eventid']) try: ev = zmapi._make_request(url=url, type='get') except Exception as e: g.logger.Error ('Error during event notes retrieval: {}'.format(str(e))) g.logger.Debug(2,traceback.format_exc()) exit(0) # Let's continue with zmdetect new_notes = pred if ev.get('event',{}).get('Event',{}).get('Notes'): old_notes = ev['event']['Event']['Notes'] old_notes_split = old_notes.split('Motion:') old_d = old_notes_split[0] # old detection try: old_m = old_notes_split[1] except IndexError: old_m = '' new_notes = pred + 'Motion:'+ old_m g.logger.Debug (1,'Replacing old note:{} with new note:{}'.format(old_notes, new_notes)) payload = {} payload['Event[Notes]'] = new_notes try: ev = zmapi._make_request(url=url, payload=payload, type='put') except Exception as e: g.logger.Error ('Error during notes update: {}'.format(str(e))) g.logger.Debug(2,traceback.format_exc()) if g.config['create_animation'] == 'yes': if not args.get('eventid'): g.logger.Error ('Cannot create animation as you did not pass an event ID') else: g.logger.Debug(1,'animation: Creating burst...') try: img.createAnimation(matched_data['frame_id'], args.get('eventid'), args.get('eventpath')+'/objdetect', g.config['animation_types']) except Exception as e: g.logger.Error('Error creating animation:{}'.format(e)) g.logger.Error('animation: Traceback:{}'.format(traceback.format_exc()))
def post(self): args = parse_args() req = request.get_json() fi = None stream_options = {} stream = None ml_overrides = {} config_copy = None poly_copy = None ml_options = None mid = None if not req: req = {} if req.get('mid') and str(req.get('mid')) in g.monitor_config: mid = str(req.get('mid')) g.logger.Debug( 1, 'Monitor ID {} provided & matching config found in mlapi, ignoring objectconfig.ini' .format(mid)) config_copy = copy.copy(g.config) poly_copy = copy.copy(g.polygons) g.polygons = copy.copy(g.monitor_polygons[mid]) for key in g.monitor_config[mid]: # This will also take care of copying over mid specific stream_options g.logger.Debug( 2, 'Overriding global {} with {}...'.format( key, g.monitor_config[mid][key][:30])) g.config[key] = g.monitor_config[mid][key] # stupid mlapi and zm_detect config incompatibility if not g.config.get('image_path') and g.config.get('images_path'): g.config['image_path'] = g.config['images_path'] # At this stage, polygons has a copy of that monitor polygon set # g.config has overriden values of config from the mid r = req.get('reason') if r and g.config['only_triggered_zm_zones'] == 'yes' and g.config[ 'import_zm_zones'] == 'yes': g.logger.Debug( 2, 'Only filtering polygon names that have {}'.format(r)) g.logger.Debug( 2, 'Original polygons being used: {}'.format(g.polygons)) g.polygons[:] = [ item for item in g.polygons if utils.findWholeWord(item['name'])(r) ] g.logger.Debug( 2, 'Final polygons being used: {}'.format(g.polygons)) if g.config['ml_sequence'] and g.config['use_sequence'] == 'yes': g.log.Debug(2, 'using ml_sequence') ml_options = g.config['ml_sequence'] secrets = pyzmutils.read_config(g.config['secrets']) ml_options = pyzmutils.template_fill( input_str=ml_options, config=None, secrets=secrets._sections.get('secrets')) ml_options = ast.literal_eval(ml_options) #print (ml_options) else: g.logger.Debug(2, 'mapping legacy ml data from config') ml_options = utils.convert_config_to_ml_sequence() g.logger.Debug(2, 'Overwriting ml_sequence of pre loaded model') m.set_ml_options(ml_options) else: g.logger.Debug( 1, 'Monitor ID not specified, or not found in mlapi config, using zm_detect overrides' ) ml_overrides = req.get('ml_overrides', {}) if g.config['ml_sequence'] and g.config['use_sequence'] == 'yes': g.log.Debug(2, 'using ml_sequence') ml_options = g.config['ml_sequence'] secrets = pyzmutils.read_config(g.config['secrets']) ml_options = pyzmutils.template_fill( input_str=ml_options, config=None, secrets=secrets._sections.get('secrets')) ml_options = ast.literal_eval(ml_options) #print (ml_options) else: g.logger.Debug(2, 'mapping legacy ml data from config') ml_options = utils.convert_config_to_ml_sequence() #print (ml_options) if g.config.get('stream_sequence'): g.logger.Debug( 2, 'Found stream_sequence in mlapi config, ignoring objectconfig.ini' ) stream_options = ast.literal_eval(g.config.get('stream_sequence')) else: stream_options = req.get('stream_options') if not stream_options: if config_copy: g.log.Debug(2, 'Restoring global config & ml_options') g.config = config_copy g.polygons = poly_copy abort(400, msg='No stream options found') stream_options['api'] = zmapi stream_options['polygons'] = g.polygons stream = req.get('stream') if args['type'] == 'face': g.log.Debug(1, 'Face Recognition requested') elif args['type'] == 'alpr': g.log.Debug(1, 'ALPR requested') elif args['type'] in [None, 'object']: g.log.Debug(1, 'Object Recognition requested') #m = ObjectDetect.Object() else: if config_copy: g.log.Debug(2, 'Restoring global config & ml_options') g.config = config_copy g.polygons = poly_copy abort(400, msg='Invalid Model:{}'.format(args['type'])) if not stream: g.log.Debug(1, 'Stream info not found, looking at args...') fip, ext = get_file(args) fi = fip + ext stream = fi #image = cv2.imread(fi) #bbox,label,conf = m.detect(image) stream_options['mid'] = mid if not stream_options.get('delay') and g.config.get('wait'): stream_options['delay'] = g.config.get('wait') g.log.Debug(1, 'Calling detect streams') matched_data, all_matches = m.detect_stream(stream=stream, options=stream_options, ml_overrides=ml_overrides) if matched_data['image_dimensions']: oldh = matched_data['image_dimensions']['original'][0] oldw = matched_data['image_dimensions']['original'][1] if config_copy: g.log.Debug(2, 'Restoring global config & ml_options') g.config = config_copy g.polygons = poly_copy matched_data['image'] = None if args.get('response_format') == 'zm_detect': resp_obj = { 'matched_data': matched_data, 'all_matches': all_matches, } g.log.Debug(1, 'Returning {}'.format(resp_obj)) return resp_obj # legacy format bbox = matched_data['boxes'] label = matched_data['labels'] conf = matched_data['confidences'] detections = [] for l, c, b in zip(label, conf, bbox): c = "{:.2f}%".format(c * 100) obj = {'type': 'object', 'label': l, 'confidence': c, 'box': b} detections.append(obj) if args['delete'] and fi: #pass os.remove(fi) return detections