Beispiel #1
0
            async def send_chunks():
                while True:
                    chunk = await chunk_queue.get()
                    if chunk is None:
                        chunk_queue.task_done()
                        break

                    # Determine action and request id
                    action = determine_action(chunk.chunk_number, chunk.number_chunks)

                    # Add data
                    await dfxapi.Measurements.ws_add_data(ws, generate_reqid(), app.measurement_id, chunk.chunk_number,
                                                          action, chunk.start_time_s, chunk.end_time_s,
                                                          chunk.duration_s, chunk.metadata, chunk.payload_data)
                    print(f"Sent chunk {chunk.chunk_number}")
                    renderer.set_sent(chunk.chunk_number)

                    # Save chunk (for debugging purposes)
                    if "debug_save_chunks_folder" in args and args.debug_save_chunks_folder:
                        DfxSdkHelpers.save_chunk(copy.copy(chunk), args.debug_save_chunks_folder)
                        print(f"Saved chunk {chunk.chunk_number} in '{args.debug_save_chunks_folder}'")

                    chunk_queue.task_done()

                app.step = MeasurementStep.WAITING_RESULTS
                print("Extraction complete, waiting for results")
Beispiel #2
0
            async def receive_results():
                num_results_received = 0
                async for msg in ws:
                    status, request_id, payload = dfxapi.Measurements.ws_decode(msg)
                    if request_id == results_request_id and len(payload) > 0:
                        sdk_result = collector.decodeMeasurementResult(payload)
                        result = DfxSdkHelpers.sdk_result_to_dict(sdk_result)
                        renderer.set_results(result.copy())
                        PP.print_sdk_result(result)
                        num_results_received += 1
                    if num_results_received == results_expected:
                        await ws.close()
                        break

                app.step = MeasurementStep.COMPLETED
                print("Measurement complete")
Beispiel #3
0
async def extract_from_imgs(chunk_queue, imreader, tracker, collector, renderer, app):
    # Read frames from the image source, track faces and extract using collector
    while True:
        # Grab a frame
        read, image, frame_number, frame_timestamp_ns = await imreader.read_next_frame()
        if not read or image is None:
            # Video ended, so grab what should be the last, possibly truncated chunk
            collector.forceComplete()
            chunk_data = collector.getChunkData()
            if chunk_data is not None:
                chunk = chunk_data.getChunkPayload()
                await chunk_queue.put(chunk)
                break

        # Start the DFX SDK collection if we received a start command
        if app.step == MeasurementStep.USER_STARTED:
            collector.startCollection()
            app.step = MeasurementStep.MEASURING
            if app.is_camera:
                app.begin_frame = frame_number
                app.end_frame = frame_number + app.end_frame

        # Track faces
        tracked_faces = tracker.trackFaces(image)

        # Create a DFX VideoFrame, then a DFX Frame from the DFX VideoFrame and add DFX faces to it
        dfx_video_frame = dfxsdk.VideoFrame(image, frame_number, frame_timestamp_ns,
                                            dfxsdk.ChannelOrder.CHANNEL_ORDER_BGR)
        dfx_frame = collector.createFrame(dfx_video_frame)
        if len(tracked_faces) > 0:
            tracked_face = next(iter(tracked_faces.values()))  # We only care about the first face in this demo
            dfx_face = DfxSdkHelpers.dfx_face_from_json(collector, tracked_face)
            dfx_frame.addFace(dfx_face)

        # For cameras, check constraints and provide users actionable feedback
        if app.is_camera:
            c_result, c_details = collector.checkConstraints(dfx_frame)

            # Change renderer state
            renderer.set_constraints_feedback(DfxSdkHelpers.user_feedback_from_constraints(c_details))

            # Change the app step
            if app.step in [MeasurementStep.NOT_READY, MeasurementStep.READY]:
                if c_result == dfxsdk.ConstraintResult.GOOD:
                    app.step = MeasurementStep.READY
                else:
                    app.step = MeasurementStep.NOT_READY
            elif app.step == MeasurementStep.MEASURING:
                if c_result == dfxsdk.ConstraintResult.ERROR:
                    app.step = MeasurementStep.FAILED
                    reasons = DfxSdkHelpers.failure_causes_from_constraints(c_details)
                    print(reasons)

        # Extract bloodflow if the measurement has started
        if app.step == MeasurementStep.MEASURING:
            collector.defineRegions(dfx_frame)
            result = collector.extractChannels(dfx_frame)

            # Grab a chunk and check if we are finished
            if result == dfxsdk.CollectorState.CHUNKREADY or result == dfxsdk.CollectorState.COMPLETED:
                chunk_data = collector.getChunkData()
                if chunk_data is not None:
                    chunk = chunk_data.getChunkPayload()
                    await chunk_queue.put(chunk)
                if result == dfxsdk.CollectorState.COMPLETED:
                    if app.is_camera:
                        imreader.stop()
                    break

        await renderer.put_nowait((image, (dfx_frame, frame_number, frame_timestamp_ns)))

    # Stop the tracker
    tracker.stop()

    # Close the camera
    imreader.close()

    # Signal to send_chunks that we are done
    await chunk_queue.put(None)

    # Signal to render_queue that we are done
    renderer.keep_render_last_frame()
Beispiel #4
0
async def main(args):
    # Load config
    config = load_config(args.config_file)

    # Check API status
    async with aiohttp.ClientSession(raise_for_status=True) as session:
        _, api_status = await dfxapi.General.api_status(session)
        if not api_status["StatusID"] == "ACTIVE":
            print(f"DFX API Status: {api_status['StatusID']} ({dfxapi.Settings.rest_url})")

            return

    # Handle various command line subcommands

    # Handle "orgs" (Organizations) commands - "register" and "unregister"
    if args.command in ["o", "org", "orgs"]:
        if args.subcommand == "unregister":
            success = await unregister(config)
        else:
            success = await register(config, args.license_key)

        if success:
            save_config(config, args.config_file)
        return

    # Handle "users" commands - "login" and "logout"
    if args.command in ["u", "user", "users"]:
        if args.subcommand == "logout":
            success = logout(config)
        else:
            success = await login(config, args.email, args.password)

        if success:
            save_config(config, args.config_file)
        return

    # The commands below need a token, so make sure we are registered and/or logged in
    if not dfxapi.Settings.device_token and not dfxapi.Settings.user_token:
        print("Please register and/or login first to obtain a token")
        return

    # Use the token to create the headers
    token = dfxapi.Settings.user_token if dfxapi.Settings.user_token else dfxapi.Settings.device_token
    headers = {"Authorization": f"Bearer {token}"}

    # Handle "studies" commands - "get", "list" and "select"
    if args.command in ["s", "study", "studies"]:
        async with aiohttp.ClientSession(headers=headers, raise_for_status=True) as session:
            if args.subcommand == "get":
                study_id = config["selected_study"] if args.study_id is None else args.study_id
                if not study_id or study_id.isspace():
                    print("Please select a study or pass a study id")
                    return
                _, study = await dfxapi.Studies.retrieve(session, study_id, raise_for_status=False)
                print(json.dumps(study)) if args.json else PP.print_pretty(study, args.csv)
            elif args.subcommand == "list":
                _, studies = await dfxapi.Studies.list(session)
                print(json.dumps(studies)) if args.json else PP.print_pretty(studies, args.csv)
            elif args.subcommand == "select":
                status, response = await dfxapi.Studies.retrieve(session, args.study_id, raise_for_status=False)
                if status >= 400:
                    PP.print_pretty(response)
                    return
                config["selected_study"] = args.study_id
                save_config(config, args.config_file)
        return

    # Handle "measure" (Measurements) commands - "get" and "list"
    if args.command in ["m", "measure", "measurements"] and "make" not in args.subcommand:
        async with aiohttp.ClientSession(headers=headers, raise_for_status=True) as session:
            if args.subcommand == "get":
                measurement_id = config["last_measurement"] if args.measurement_id is None else args.measurement_id
                if not measurement_id or measurement_id.isspace():
                    print("Please complete a measurement first or pass a measurement id")
                    return
                _, results = await dfxapi.Measurements.retrieve(session, measurement_id)
                print(json.dumps(results)) if args.json else PP.print_result(results, args.csv)
            elif args.subcommand == "list":
                _, measurements = await dfxapi.Measurements.list(session, limit=args.limit)
                print(json.dumps(measurements)) if args.json else PP.print_pretty(measurements, args.csv)
        return

    # Handle "measure" (Measurements) commands - "make" and "debug_make_from_chunks"
    assert args.command in ["m", "measure", "measurements"] and "make" in args.subcommand

    # Verify preconditions
    if not config["selected_study"]:
        print("Please select a study first using 'study select'")
        return

    # Prepare to make a measurement..
    app = AppState()

    if args.subcommand == "make" or args.subcommand == "make_camera":
        # ..using a video or camera
        app.is_camera = args.subcommand == "make_camera"
        image_src_name = f"Camera {args.camera}" if app.is_camera else os.path.basename(args.video_path)
        try:
            # Open the camera or video
            imreader = CameraReader(args.camera, mirror=True) if app.is_camera else VideoReader(
                args.video_path, args.start_time, args.end_time, rotation=args.rotation, fps=args.fps)

            # Create a face tracker
            tracker = DlibTracker()

            # Create DFX SDK factory
            factory = dfxsdk.Factory()
            print("Created DFX Factory:", factory.getVersion())
            sdk_id = factory.getSdkId()

            # Get study config data..
            if args.debug_study_cfg_file is None:
                # ..from API required to initialize DFX SDK collector (or FAIL)
                study_cfg_bytes = await retrieve_sdk_config(headers, config, args.config_file, sdk_id)
            else:
                # .. or from a file
                with open(args.debug_study_cfg_file, 'rb') as f:
                    study_cfg_bytes = f.read()
        except Exception as e:
            print(e)
            return

        # Create DFX SDK collector (or FAIL)
        if not factory.initializeStudy(study_cfg_bytes):
            print(f"DFX factory creation failed: {factory.getLastErrorMessage()}")
            return
        factory.setMode("discrete")
        collector = factory.createCollector()
        if collector.getCollectorState() == dfxsdk.CollectorState.ERROR:
            print(f"DFX collector creation failed: {collector.getLastErrorMessage()}")
            return

        print("Created DFX Collector:")
        chunk_duration_s = float(args.chunk_duration_s)
        frames_per_chunk = math.ceil(chunk_duration_s * imreader.fps)
        if app.is_camera:
            app.number_chunks = math.ceil(args.measurement_duration_s / args.chunk_duration_s)
            app.end_frame = math.ceil(args.measurement_duration_s * imreader.fps)
        else:
            app.number_chunks = math.ceil(imreader.frames_to_process / frames_per_chunk)
            app.begin_frame = imreader.start_frame
            app.end_frame = imreader.stop_frame

        # Set collector config
        collector.setTargetFPS(imreader.fps)
        collector.setChunkDurationSeconds(chunk_duration_s)
        collector.setNumberChunks(app.number_chunks)
        print(f"    mode: {factory.getMode()}")
        print(f"    number chunks: {collector.getNumberChunks()}")
        print(f"    chunk duration: {collector.getChunkDurationSeconds()}s")
        for constraint in collector.getEnabledConstraints():
            print(f"    enabled constraint: {constraint}")

        # Set the collector constraints config
        if app.is_camera:
            app.constraints_cfg = DfxSdkHelpers.ConstraintsConfig(collector.getConstraintsConfig("json"))
            app.constraints_cfg.minimumFps = 10
            collector.setConstraintsConfig("json", str(app.constraints_cfg))

        # If it's a video file, we immediately start extraction
        app.step = MeasurementStep.USER_STARTED if not app.is_camera else MeasurementStep.NOT_READY

    elif args.subcommand == "debug_make_from_chunks":
        # .. or using previously saved chunks
        payload_files = sorted(glob.glob(os.path.join(args.debug_chunks_folder, "payload*.bin")))
        meta_files = sorted(glob.glob(os.path.join(args.debug_chunks_folder, "metadata*.bin")))
        prop_files = sorted(glob.glob(os.path.join(args.debug_chunks_folder, "properties*.json")))
        number_files = min(len(payload_files), len(meta_files), len(prop_files))
        if number_files <= 0:
            print(f"No payload files found in {args.debug_chunks_folder}")
            return
        with open(prop_files[0], 'r') as pr:
            props = json.load(pr)
            app.number_chunks = props["number_chunks"]
            duration_pr = props["duration_s"]
        if app.number_chunks != number_files:
            print(f"Number of chunks in properties.json {app.number_chunks} != Number of payload files {number_files}")
            return
        if duration_pr * app.number_chunks > 120:
            print(f"Total payload duration {duration_pr * app.number_chunks} seconds is more than 120 seconds")
            return

        # Create DFX SDK factory (just so we can have a collector for decoding results)
        factory = dfxsdk.Factory()
        print("Created DFX Factory:", factory.getVersion())
        collector = factory.createCollector()
        print("Created DFX Collector for results decoding only")

        app.step = MeasurementStep.USER_STARTED
    else:
        print("Unknown subcommand to 'meas'. This should never happen")
        return

    # Make a measurement
    async with aiohttp.ClientSession(headers=headers, raise_for_status=True) as session:
        # Create a measurement on the API and get the measurement ID
        _, response = await dfxapi.Measurements.create(session, config["selected_study"])
        app.measurement_id = response["ID"]
        print(f"Created measurement {app.measurement_id}")

        # Use the session to connect to the WebSocket
        async with session.ws_connect(dfxapi.Settings.ws_url) as ws:
            # Subscribe to results
            results_request_id = generate_reqid()
            await dfxapi.Measurements.ws_subscribe_to_results(ws, results_request_id, app.measurement_id)

            # Queue to pass chunks between coroutines
            chunk_queue = asyncio.Queue(app.number_chunks)

            # When we receive `results_expected` results, we close the WebSocket in `receive_results`
            results_expected = app.number_chunks

            # Coroutine to produce chunks and put then in chunk_queue
            if args.subcommand == "make" or args.subcommand == "make_camera":
                renderer = Renderer(
                    _version,
                    image_src_name,
                    imreader.fps,
                    app,
                    0.5 if imreader.height >= 720 else 1.0,
                ) if app.is_camera or not args.no_render else NullRenderer()
                if not app.is_camera:
                    print("Extraction started")
                else:
                    print("Waiting to start")
                produce_chunks_coro = extract_from_imgs(
                    chunk_queue,  # Chunks will be put into this queue
                    imreader,  # Image reader
                    tracker,  # Face tracker
                    collector,  # DFX SDK collector needed to create chunks
                    renderer,  # Rendering
                    app)  # App
            else:  # args.subcommand == "debug_make_from_chunks":
                renderer = NullRenderer()
                produce_chunks_coro = read_folder_chunks(chunk_queue, payload_files, meta_files, prop_files)

            # Coroutine to get chunks from chunk_queue and send chunk using WebSocket
            async def send_chunks():
                while True:
                    chunk = await chunk_queue.get()
                    if chunk is None:
                        chunk_queue.task_done()
                        break

                    # Determine action and request id
                    action = determine_action(chunk.chunk_number, chunk.number_chunks)

                    # Add data
                    await dfxapi.Measurements.ws_add_data(ws, generate_reqid(), app.measurement_id, chunk.chunk_number,
                                                          action, chunk.start_time_s, chunk.end_time_s,
                                                          chunk.duration_s, chunk.metadata, chunk.payload_data)
                    print(f"Sent chunk {chunk.chunk_number}")
                    renderer.set_sent(chunk.chunk_number)

                    # Save chunk (for debugging purposes)
                    if "debug_save_chunks_folder" in args and args.debug_save_chunks_folder:
                        DfxSdkHelpers.save_chunk(copy.copy(chunk), args.debug_save_chunks_folder)
                        print(f"Saved chunk {chunk.chunk_number} in '{args.debug_save_chunks_folder}'")

                    chunk_queue.task_done()

                app.step = MeasurementStep.WAITING_RESULTS
                print("Extraction complete, waiting for results")

            # Coroutine to receive responses using the Websocket
            async def receive_results():
                num_results_received = 0
                async for msg in ws:
                    status, request_id, payload = dfxapi.Measurements.ws_decode(msg)
                    if request_id == results_request_id and len(payload) > 0:
                        sdk_result = collector.decodeMeasurementResult(payload)
                        result = DfxSdkHelpers.sdk_result_to_dict(sdk_result)
                        renderer.set_results(result.copy())
                        PP.print_sdk_result(result)
                        num_results_received += 1
                    if num_results_received == results_expected:
                        await ws.close()
                        break

                app.step = MeasurementStep.COMPLETED
                print("Measurement complete")

            # Coroutine for rendering
            async def render():
                if type(renderer) == NullRenderer:
                    return

                cancelled = await renderer.render()
                cv2.destroyAllWindows()
                if cancelled:
                    tracker.stop()
                    raise ValueError("Measurement was cancelled by user.")

            # Start the coroutines and wait till they finish
            coroutines = [produce_chunks_coro, send_chunks(), receive_results(), render()]
            done, pending = await asyncio.wait(coroutines, return_when=asyncio.FIRST_EXCEPTION)
            for p in pending:  # If there were any pending coroutines, cancel them here...
                p.cancel()
            if len(pending) > 0:  # If we had pending coroutines, it means something went wrong in the 'done' ones
                for d in done:
                    e = d.exception()
                    if type(e) != asyncio.CancelledError:
                        print(e)
                print(f"Measurement {app.measurement_id} failed")
            else:
                config["last_measurement"] = app.measurement_id
                save_config(config, args.config_file)
                print(f"Measurement {app.measurement_id} completed")
                print(f"Use 'python {os.path.basename(__file__)} measure get' to get comprehensive results")