# Path: concert/coroutines/base.py
# def background(coroutine):
#     """Same as :func:`.start`, just meant to be used as a decorator."""
#     @functools.wraps(coroutine)
#     def inner(*args, **kwargs):
#         return start(coroutine(*args, **kwargs))
# 
#     return inner
# 
# def run_in_executor(func, *args):
#     """Run a blocking function *func* with signature func(\*args) in an executor."""
#     # Leave this here for now, if there are problems with the normal run_in_executor returning a
#     # future then let's re-visit
#     # """Run a blocking function *func* with signature func(*args) in an executor, wrap the future
#     # into a real coroutine and return it.
#     # """
#     # async def make_coro():
#     #     loop = asyncio.get_running_loop()
#     #     future = loop.run_in_executor(None, func, *args)
#     #
#     #     try:
#     #         return await future
#     #     except asyncio.CancelledError:
#     #         # Wait for the future to finish even if we are cancelled.
#     #         LOG.log(AIODEBUG, f'{func.__name__} waiting for concurrent.Future')
#     #         return await future
#     # return make_coro()
#     loop = asyncio.get_event_loop()
# 
#     return loop.run_in_executor(None, func, *args)
# 
# Path: concert/quantities.py



import asyncio
import numpy as np
import logging
from scipy.signal import fftconvolve
from concert.coroutines.base import background, run_in_executor
from concert.quantities import q
        from skimage.filters import threshold_otsu
        from skimage.morphology import dilation, disk, convex_hull_image, label
    from scipy.ndimage import gaussian_filter1d
    from numpy.fft import fft2, ifft2, fftshift
        from skimage.filters import sobel
        from skimage.transform import resize
    async for image in producer:
        # start forces the coroutine to start immediately
        coros.append(run_in_executor(find_needle_tip, image))

    tips = [tip for tip in await asyncio.gather(*coros) if tip is not None]
    LOG.debug('Needle tips: %s', np.array(tips).tolist())

    if len(tips) == 0:
        raise ValueError("No sample tip points found.")

    return tips


def find_needle_tip(image):
    """Extract needle tip from *image*."""
    mask = segment_convex_object(image)
    if mask is None:
        return None
    coords = np.array(list(zip(*np.where(mask))))
    min_y = np.min(coords[:, 0])
    indices = np.where(coords[:, 0] == min_y)[0]
    coords = coords[indices]
    if coords[:, 1].max() - coords[:, 1].min() > image.shape[1] // 4:
        # Needle tip cannot be width / 4 broad, we have probably segmented just noise
        return None
    coords = [_find_peak_subpix(pos, image) for pos in coords]

    return np.mean(coords, axis=0) if coords else None



# Path: concert/coroutines/base.py
# def background(coroutine):
#     """Same as :func:`.start`, just meant to be used as a decorator."""
#     @functools.wraps(coroutine)
#     def inner(*args, **kwargs):
#         return start(coroutine(*args, **kwargs))
# 
#     return inner
# 
# def run_in_executor(func, *args):
#     """Run a blocking function *func* with signature func(\*args) in an executor."""
#     # Leave this here for now, if there are problems with the normal run_in_executor returning a
#     # future then let's re-visit
#     # """Run a blocking function *func* with signature func(*args) in an executor, wrap the future
#     # into a real coroutine and return it.
#     # """
#     # async def make_coro():
#     #     loop = asyncio.get_running_loop()
#     #     future = loop.run_in_executor(None, func, *args)
#     #
#     #     try:
#     #         return await future
#     #     except asyncio.CancelledError:
#     #         # Wait for the future to finish even if we are cancelled.
#     #         LOG.log(AIODEBUG, f'{func.__name__} waiting for concurrent.Future')
#     #         return await future
#     # return make_coro()
#     loop = asyncio.get_event_loop()
# 
#     return loop.run_in_executor(None, func, *args)
# 
# Path: concert/quantities.py



# Path: concert/imageprocessing.py
import asyncio
import numpy as np
import logging
from scipy.signal import fftconvolve
from concert.coroutines.base import background, run_in_executor
from concert.quantities import q
        from skimage.filters import threshold_otsu
        from skimage.morphology import dilation, disk, convex_hull_image, label
    from scipy.ndimage import gaussian_filter1d
    from numpy.fft import fft2, ifft2, fftshift
        from skimage.filters import sobel
        from skimage.transform import resize

    async for image in producer:
        # start forces the coroutine to start immediately
        coros.append(run_in_executor(find_needle_tip, image))

    tips = [tip for tip in await asyncio.gather(*coros) if tip is not None]
    LOG.debug('Needle tips: %s', np.array(tips).tolist())

    if len(tips) == 0:
        raise ValueError("No sample tip points found.")

    return tips


def find_needle_tip(image):
    """Extract needle tip from *image*."""
    mask = segment_convex_object(image)
    if mask is None:
        return None
    coords = np.array(list(zip(*np.where(mask))))
    min_y = np.min(coords[:, 0])
    indices = np.where(coords[:, 0] == min_y)[0]
    coords = coords[indices]
    if coords[:, 1].max() - coords[:, 1].min() > image.shape[1] // 4:
        # Needle tip cannot be width / 4 broad, we have probably segmented just noise
        return None
    coords = [_find_peak_subpix(pos, image) for pos in coords]

    return np.mean(coords, axis=0) if coords else None



@background