Пример #1
0
def main():
    side = "r"
    all_connectors = get_all_connectors(OUTPUT_ROOT / "all_connectors.csv")
    centers = get_antennal_lobe_centers()
    center_p = centers[side]
    target_syn = 1000

    center_arr = np.array([center_p[dim] for dim in DIMS])

    def fn(radius):
        in_roi = np.abs(all_connectors[["zp", "yp", "xp"]] - center_arr).max(1) < radius
        return np.abs(target_syn - in_roi.sum())

    result = minimize_scalar(fn, bracket=(50, 1000, 10000))
    radius = result.x

    offset_p, shape_p = center_radius_to_offset_shape(center_p, radius)
    coord_trans = CoordinateTransformer.from_catmaid(catmaid, STACK_ID)
    coord_trans.project_to_stack(offset_p)
    d = {
        "project": {"offset": offset_p, "shape": shape_p},
        "stack": {
            "offset": {
                dim: int(val)
                for dim, val in coord_trans.project_to_stack(offset_p).items()
            },
            "shape": {
                dim: ceil(val / coord_trans.resolution[dim])
                for dim, val in shape_p.items()
            },
        },
        "contains_approx": target_syn,
    }
    with open(ANTENNAL_LOBE_OUTPUT / "roi_{}.json".format(side), "w") as f:
        json.dump(d, f, sort_keys=True, indent=2)
Пример #2
0
def get_connectors_in_volume(catmaid, stack_id, roi_zyx_s):
    coord_trans = CoordinateTransformer.from_catmaid(catmaid, stack_id)
    roi_zyx_p = coord_trans.stack_to_project_array(roi_zyx_s, ORDER)

    data = {
        "left": roi_zyx_p[0, 2],
        "top": roi_zyx_p[0, 1],
        "z1": roi_zyx_p[0, 0],
        "right": roi_zyx_p[1, 2],
        "bottom": roi_zyx_p[1, 1],
        "z2": roi_zyx_p[1, 0],
    }
    response = catmaid.post((catmaid.project_id, "/node/list"), data)

    vol = np.empty(np.diff(roi_zyx_s, axis=0).squeeze().astype(int),
                   dtype=np.int64)
    vol.fill(-1)

    for connector_row in response[1]:
        connector_id, xp, yp, zp, _, _, _, _ = connector_row
        zyx_p = np.array([zp, yp, xp])
        if not in_roi(roi_zyx_p, zyx_p):
            continue
        z_idx, y_idx, x_idx = (
            coord_trans.project_to_stack_array(zyx_p, ORDER) -
            roi_zyx_s[0, :]).astype(int)
        vol[z_idx, y_idx, x_idx] = connector_id

    return vol
Пример #3
0
def plot_near_node(node_id, cred_path=None):
    if cred_path is None:
        cred_path = '../skeleton_synapses/credentials_dev.json'
    catmaid = CatmaidClient.from_json(cred_path)
    transformer = CoordinateTransformer.from_catmaid(catmaid, 1)
    tnid, x, y, z = catmaid.post((catmaid.project_id, 'node', 'get_location'), {'tnid': node_id})
    assert tnid == node_id
    coords = transformer.project_to_stack({'x': x, 'y': y, 'z': z})

    return plot_near_point([int(coords[dim]) for dim in 'zyx'], mode='middle')
Пример #4
0
def main(parsed_args):
    catmaid = CatmaidClient.from_json(parsed_args.credentials)
    coord_trans = CoordinateTransformer.from_catmaid(catmaid,
                                                     parsed_args.stack_id)

    try:
        volume_id = int(parsed_args.volume)
        bbox, mesh = get_volume_by_id(catmaid, volume_id)
    except ValueError:
        bbox, mesh = get_volume_by_name(catmaid, parsed_args.volume)

    bbox_voxels = bbox_to_voxels(coord_trans, bbox)
    dump_mesh(mesh, parsed_args.output)

    return bbox_voxels, mesh
Пример #5
0
def test_from_catmaid(default_coord_transformer, catmaid_mock):
    assert (CoordinateTransformer.from_catmaid(
        catmaid_mock, None) == default_coord_transformer)
Пример #6
0
def test_instantiate(default_res, default_trans, has_res, has_trans):
    if not has_res:
        default_res = None
    if not has_trans:
        default_trans = None
    assert CoordinateTransformer(default_res, default_trans)
Пример #7
0
def default_coord_transformer(default_res, default_trans):
    return CoordinateTransformer(default_res, default_trans)
Пример #8
0
def test_project_to_stack_orientation_xy(orientation, direction, expected):
    coord_trans = CoordinateTransformer(orientation=orientation)
    result = getattr(coord_trans, direction)({"z": 0, "y": 1, "x": 2})
    assert result == expected
Пример #9
0
def test_can_validate_orientation_invalid(orientation, expected_exception):
    with pytest.raises(expected_exception):
        CoordinateTransformer(orientation=orientation)
Пример #10
0
def test_can_validate_orientation_valid(orientation):
    trans = CoordinateTransformer(orientation=orientation)
    assert trans.orientation == StackOrientation.XY
    assert trans.depth_dim == "z"
Пример #11
0
    def __init__(self,
                 stack,
                 output_orientation=DEFAULT_3D_ORIENTATION,
                 preferred_mirror=None,
                 timeout=1,
                 cache_items=DEFAULT_CACHE_ITEMS,
                 cache_bytes=DEFAULT_CACHE_BYTES,
                 broken_slice_handling=DEFAULT_BROKEN_SLICE_HANDLING,
                 cval=0,
                 auth=None):
        """

        Parameters
        ----------
        stack : Stack
        output_orientation : str or Orientation3D
            default Orientation3D.ZYX
        preferred_mirror : int or str or StackMirror, optional
            default None
        timeout : float, optional
            default 1
        cache_items : int, optional
            default 10
        cache_bytes : int, optional
            default None
        broken_slice_handling : str or BrokenSliceHandling
            default BrokenSliceHandling.FILL
        cval : int, optional
            default 0
        auth : (str, str), optional
            Tuple of (username, password) for basic HTTP authentication, to be used if the selected mirror has no
            defined ``auth``. Default None
        """
        self.stack = stack
        self.depth_dimension = 'z'
        self.source_orientation = self.depth_dimension + 'yx'

        self.broken_slice_handling = BrokenSliceHandling(broken_slice_handling)

        if self.broken_slice_handling == BrokenSliceHandling.FILL:
            self.cval = cval
        else:
            self.cval = None

        self.target_orientation = str(output_orientation)

        self._dimension_mappings = self._map_dimensions()

        self.timeout = timeout

        self.coord_trans = CoordinateTransformer(*[
            getattr(self.stack, name, None)
            for name in ('resolution', 'translation', 'orientation')
        ])

        self.tqdm = tqdm if self.show_progress else DummyTqdm

        self._tile_cache = TileCache(cache_items, cache_bytes)

        self._session = requests.Session()
        self._auth = auth

        self._mirror = None
        self.mirror = preferred_mirror
Пример #12
0
class ImageFetcher(object):
    show_progress = imported_tqdm

    def __init__(self,
                 stack,
                 output_orientation=DEFAULT_3D_ORIENTATION,
                 preferred_mirror=None,
                 timeout=1,
                 cache_items=DEFAULT_CACHE_ITEMS,
                 cache_bytes=DEFAULT_CACHE_BYTES,
                 broken_slice_handling=DEFAULT_BROKEN_SLICE_HANDLING,
                 cval=0,
                 auth=None):
        """

        Parameters
        ----------
        stack : Stack
        output_orientation : str or Orientation3D
            default Orientation3D.ZYX
        preferred_mirror : int or str or StackMirror, optional
            default None
        timeout : float, optional
            default 1
        cache_items : int, optional
            default 10
        cache_bytes : int, optional
            default None
        broken_slice_handling : str or BrokenSliceHandling
            default BrokenSliceHandling.FILL
        cval : int, optional
            default 0
        auth : (str, str), optional
            Tuple of (username, password) for basic HTTP authentication, to be used if the selected mirror has no
            defined ``auth``. Default None
        """
        self.stack = stack
        self.depth_dimension = 'z'
        self.source_orientation = self.depth_dimension + 'yx'

        self.broken_slice_handling = BrokenSliceHandling(broken_slice_handling)

        if self.broken_slice_handling == BrokenSliceHandling.FILL:
            self.cval = cval
        else:
            self.cval = None

        self.target_orientation = str(output_orientation)

        self._dimension_mappings = self._map_dimensions()

        self.timeout = timeout

        self.coord_trans = CoordinateTransformer(*[
            getattr(self.stack, name, None)
            for name in ('resolution', 'translation', 'orientation')
        ])

        self.tqdm = tqdm if self.show_progress else DummyTqdm

        self._tile_cache = TileCache(cache_items, cache_bytes)

        self._session = requests.Session()
        self._auth = auth

        self._mirror = None
        self.mirror = preferred_mirror

    @property
    def auth(self):
        return self._auth

    @auth.setter
    def auth(self, name_pass):
        self._auth = name_pass
        if self._mirror and not self._mirror.auth:
            self._session.auth = name_pass

    @property
    def mirror(self):
        if not self._mirror:
            warn(
                'No mirror set: falling back to {}, which may not be accessible.'
                'You might want to run set_fastest_mirror.'.format(
                    self.stack.mirrors[0].title))
            m = self.stack.mirrors[0]
            self._session.auth = m.auth or self.auth
            return m
        return self._mirror

    @mirror.setter
    def mirror(self, preferred_mirror):
        """
        Set mirror by its string name, its position attribute, or the object itself

        Parameters
        ----------
        preferred_mirror : str or int or StackMirror
        """
        if preferred_mirror is None:
            self._mirror = None

        elif isinstance(preferred_mirror, StackMirror):

            if preferred_mirror not in self.stack.mirrors:
                raise ValueError("Selected mirror is not in stack's mirrors")
            self._mirror = preferred_mirror

        else:

            try:

                pos = int(preferred_mirror)
                matching_mirrors = [
                    m for m in self.stack.mirrors if m.position == pos
                ]

                if not matching_mirrors:
                    warn(
                        'Preferred mirror position {} does not exist, choose from {}'
                        .format(
                            pos, ', '.join(
                                str(m.position) for m in self.stack.mirrors)))
                    return
                elif len(matching_mirrors) > 1:
                    warn(
                        'More than one mirror found for position {}, picking {}'
                        .format(pos, matching_mirrors[0].title))
                self._mirror = matching_mirrors[0]

            except (ValueError, TypeError):

                if isinstance(preferred_mirror, string_types):
                    matching_mirrors = [
                        m for m in self.stack.mirrors
                        if m.title == preferred_mirror
                    ]
                    if not matching_mirrors:
                        warn(
                            'Preferred mirror called {} does not exist, choose from {}'
                            .format(
                                preferred_mirror,
                                ', '.join(m.title
                                          for m in self.stack.mirrors)))
                        return
                    elif len(matching_mirrors) > 1:
                        warn(
                            'More than one mirror found for title {}, picking first'
                            .format(preferred_mirror))
                    self._mirror = matching_mirrors[0]

        if self._mirror is not None and self._mirror.auth:
            self._session.auth = self._mirror.auth
        else:
            self._session.auth = self.auth

    def clear_cache(self):
        self._tile_cache.clear()

    def _map_dimensions(self):
        """
        Find the indices of the target dimensions in the source dimension order

        Returns
        -------
        tuple of int

        Examples
        --------
        >>> self.source_orientation = 'xyz'
        >>> self.target_orientation = 'yzx'
        >>> self._map_dimensions()
        (1, 2, 0)
        """
        mapping = {dim: idx for idx, dim in enumerate(self.source_orientation)}
        return tuple(mapping[dim] for dim in self.target_orientation)

    def _reorient_volume_src_to_tgt(self, volume):
        arr = np.asarray(volume)
        if len(arr.shape) == 2:
            arr = np.expand_dims(arr, 0)
        if len(arr.shape) != 3:
            raise ValueError('Unknown dimension of volume: should be 2D or 3D')
        return np.moveaxis(arr, (0, 1, 2), self._dimension_mappings)

    def _make_empty_tile(self, width, height=None):
        height = height or width
        tile = np.empty((height, width), dtype=np.uint8)
        tile.fill(self.cval)
        return tile

    def _get_tile(self, tile_index):
        """
        Get the tile from the cache, handle broken slices, or fetch.

        Parameters
        ----------
        tile_index : TileIndex

        Returns
        -------
        Future
        """
        try:
            return self._tile_cache[tile_index]
        except KeyError:
            pass

        if tile_index.depth in self.stack.broken_slices:
            if self.broken_slice_handling == BrokenSliceHandling.FILL and self.cval is not None:
                return self._make_empty_tile(tile_index.width,
                                             tile_index.height)
            else:
                raise NotImplementedError(
                    "'fill' with a non-None cval is the only implemented broken slice handling mode"
                )

        return self._fetch(tile_index)

    def _roi_to_tiles(self, roi_src, zoom_level):
        """

        Parameters
        ----------
        roi_src : array-like
            2 x 3 array where the rows are the half-closed interval of which pixels to select in the given dimension
            and at the given zoom level, and the columns are the 3 dimensions in the source orientation
        zoom_level : int
            Zoom level at which roi is scaled and which images will be fetched

        Returns
        -------
        set of TileIndex
            Set of tile indices to fetch
        dict of {str to dict of {str to int}}
            {'min': {}, 'max': {}} with values {'x': int, 'y': int, 'z': int}
            Pixel offsets of the minimum maximum pixels from the shallow-top-left corner of the tile which they are on
        """
        closed_roi = np.array(roi_src)
        closed_roi[1, :] -= 1
        min_pixel = dict(zip(self.source_orientation, closed_roi[0, :]))
        max_pixel = dict(zip(self.source_orientation, closed_roi[1, :]))

        min_tile, min_offset = self.mirror.get_tile_index(
            min_pixel, zoom_level)
        max_tile, max_offset = self.mirror.get_tile_index(
            max_pixel, zoom_level)

        tile_indices = fill_tiled_cuboid(min_tile, max_tile)
        src_inner_slicing = {'min': min_offset, 'max': max_offset}

        return tile_indices, src_inner_slicing

    def _insert_tile_into_arr(self, tile_index, src_tile, min_tile, max_tile,
                              src_inner_slicing, out):
        min_col = tile_index.col == min_tile.col
        max_col = tile_index.col == max_tile.col

        min_row = tile_index.row == min_tile.row
        max_row = tile_index.row == max_tile.row

        tile_slicing_dict = {
            'z':
            slice(None),
            'y':
            slice(src_inner_slicing['min']['y'] if min_row else None,
                  src_inner_slicing['max']['y'] + 1 if max_row else None),
            'x':
            slice(src_inner_slicing['min']['x'] if min_col else None,
                  src_inner_slicing['max']['x'] + 1 if max_col else None),
        }

        tile_slicing = tuple(tile_slicing_dict[dim]
                             for dim in self.source_orientation if dim in 'xy')

        tgt_tile = self._reorient_volume_src_to_tgt(src_tile[tile_slicing])

        untrimmed_topleft = dict_subtract(tile_index.coords, min_tile.coords)
        # location of the top left of the tile in out
        topleft_dict = {
            'z':
            untrimmed_topleft['z'],  # we don't trim in Z
            'y':
            0 if min_row else untrimmed_topleft['y'] -
            src_inner_slicing['min']['y'],
            'x':
            0 if min_col else untrimmed_topleft['x'] -
            src_inner_slicing['min']['x'],
        }
        topleft = tuple(topleft_dict[dim] for dim in self.target_orientation)

        arr_slice = tuple(
            slice(tl, tl + s) for tl, s in zip(topleft, tgt_tile.shape))
        out[arr_slice] = tgt_tile

    def _iter_tiles(self, tile_indices):
        for tile_idx in tile_indices:
            yield self._get_tile(tile_idx), tile_idx

    def _assemble_tiles(self, tile_indices, src_inner_slicing, out):
        """

        Parameters
        ----------
        tile_indices : list of TileIndex
            tiles to be got, reoriented, and compiled.
        src_inner_slicing : dict of str to {dict of str to int}
            {'min': {}, 'max': {}} with values {'x': int, 'y': int, 'z': int}
        out : array-like
            target-spaced, into which the tiles will be written

        Returns
        -------
        np.ndarray
        """
        min_tile = min(tile_indices,
                       key=lambda idx: (idx.depth, idx.row, idx.col))
        max_tile = max(tile_indices,
                       key=lambda idx: (idx.depth, idx.row, idx.col))

        tqdm_kwargs = {
            'total': len(tile_indices),
            'ncols': 80,
            'unit': 'tiles',
            'desc': 'Downloading tiles'
        }
        for src_tile, tile_index in self.tqdm(self._iter_tiles(tile_indices),
                                              **tqdm_kwargs):
            self._tile_cache[tile_index] = src_tile
            self._insert_tile_into_arr(tile_index, src_tile, min_tile,
                                       max_tile, src_inner_slicing, out)

        return out

    def _fetch(self, tile_index):
        """

        Parameters
        ----------
        tile_index : TileIndex

        Returns
        -------
        Future of np.ndarray in source orientation
        """
        url = self.mirror.generate_url(tile_index)
        try:
            return response_to_array(
                self._session.get(url, timeout=self.timeout))
        except HTTPError as e:
            if e.response.status_code == 404:
                logger.warning(
                    "Tile not found at %s (error 404), returning blank tile",
                    url)
                return self._make_empty_tile(tile_index.width,
                                             tile_index.height)
            else:
                raise

    def _reorient_roi_tgt_to_src(self, roi_tgt):
        return roi_tgt[:, self._dimension_mappings]

    def roi_to_scaled(self, roi, roi_mode, zoom_level):
        """
        Convert ROI into scaled stack space, keeping in the target dimension order.

        Parameters
        ----------
        roi : np.ndarray
            ROI as 2x3 array containing half-closed bounds in the target dimension order
        roi_mode : ROIMode or str
            Whether the ROI is in "project", "stack", or "scaled" stack coordinates
        zoom_level : float
            The desired zoom level of the returned data

        Returns
        -------
        np.ndarray
            ROI as 2x3 array containing half-closed bounds in scaled stack space in the target dimension order
        """
        roi_mode = ROIMode(roi_mode)
        roi_tgt = np.asarray(roi)

        if zoom_level != int(zoom_level):
            raise NotImplementedError(
                'Non-integer zoom level is not supported')

        if roi_mode == ROIMode.PROJECT:
            if not isinstance(self.stack, ProjectStack):
                raise ValueError(
                    "ImageFetcher's stack is not related to a project, cannot use ROIMode.PROJECT"
                )
            if self.stack.orientation != StackOrientation.XY:
                warn(
                    "Stack orientation differs from project: returned array's orientation will reflect"
                    "stack orientation, not project orientation")
            roi_tgt = self.coord_trans.project_to_stack_array(
                roi_tgt, dims=self.target_orientation)
            roi_mode = ROIMode.STACK

        if roi_mode == ROIMode.STACK:
            roi_tgt = self.coord_trans.stack_to_scaled_array(
                roi_tgt, zoom_level, dims=self.target_orientation)
            roi_mode = ROIMode.SCALED

        if roi_mode == ROIMode.SCALED:
            roi_tgt = np.array(
                [np.floor(roi_tgt[0, :]),
                 np.ceil(roi_tgt[1, :])], dtype=int)
        else:
            raise ValueError(
                'Mismatch between roi_mode and roi')  # shouldn't be possible

        return roi_tgt

    def get(self, roi, roi_mode=ROIMode.STACK, zoom_level=0, out=None):
        """
        Fetch image data in the ROI in the dimension order of the target orientation.

        ROI modes:
            ROIMode.PROJECT ('project'):
                - `roi` is given in project space
                - `zoom_level` specifies the zoom level of returned data
                - Returned array may overflow desired ROI by < 1 scaled pixel per side
                - Data will be reoriented from stack space/orientation into the `target_orientation` without
                  going via project space: as such, for stacks with orientation other than 'xy', the output
                  data will not be in the same orientation as the project-spaced query.
            ROIMode.STACK ('stack'):
                - Default option
                - `roi` is given in unscaled stack space (i.e. pixels at zoom level 0)
                - `zoom_level` specifies the desired zoom level of returned data
                - Returned array may overflow desired ROI by < 1 scaled pixel per side
                - Equivalent to ROIMode.SCALED if `zoom_level` == 0
            ROIMode.SCALED ('scaled'):
                - `roi` is given in scaled stack space at the given zoom level.
                - `zoom_level` specifies the zoom level of ROI and returned data
                - Returned array treats `roi` as a half-closed interval: i.e. it should have shape np.diff(roi, axis=0)

        Parameters
        ----------
        roi : array-like
            2 x 3 array where the columns are the 3 dimensions in the target orientation, and the rows are the upper
             and lower bounds of the ROI.
        roi_mode : ROIMode or str
            Default ROIMode.STACK
        zoom_level : int
        out : array-like or None
            Anything with array-like __setitem__ handling (e.g. np.ndarray, np.memmap, h5py.File, z5py.File), to which
            the results will be written. Must have the same shape in as the ROI does in scaled pixels. If None
            (default), will create a new np.ndarray.

        Returns
        -------
        array-like
        """
        roi_tgt = self.roi_to_scaled(roi, roi_mode, zoom_level)
        roi_src = self._reorient_roi_tgt_to_src(roi_tgt)
        tile_indices, inner_slicing_src = self._roi_to_tiles(
            roi_src, zoom_level)

        if out is None:
            out = np.zeros(np.diff(roi_tgt, axis=0).squeeze(), dtype=np.uint8)

        return self._assemble_tiles(tile_indices, inner_slicing_src, out)

    def get_project_space(self, roi, zoom_level=0, out=None):
        """
        Equivalent to `get` method with roi_mode=ROIMode.PROJECT
        """
        return self.get(roi, ROIMode.PROJECT, zoom_level, out)

    def get_stack_space(self, roi, zoom_level=0, out=None):
        """
        Equivalent to `get` method with roi_mode=ROIMode.STACK
        """
        return self.get(roi, ROIMode.STACK, zoom_level, out)

    def get_scaled_space(self, roi, zoom_level=0, out=None):
        """
        Equivalent to `get` method with roi_mode=ROIMode.SCALED
        """
        return self.get(roi, ROIMode.SCALED, zoom_level, out)

    def set_fastest_mirror(self, reps=1, normalise_by_tile_size=True):
        """
        Set the ImageFetcher to use the fastest accessible mirror.

        Parameters
        ----------
        reps : int
            How many times to fetch the canary tile, for robustness
        normalise_by_tile_size : bool
            Whether to normalise the fetch time by the tile size used by this mirror (to get per-pixel response time)
        """
        self.mirror = self.stack.get_fastest_mirror(self.timeout, reps,
                                                    normalise_by_tile_size)

    @classmethod
    def from_stack_info(cls, stack_info, *args, **kwargs):
        """

        Parameters
        ----------
        stack_info : dict
        args, kwargs
            See __init__ for arguments beyond stack

        Returns
        -------
        ImageFetcher
        """
        return cls(ProjectStack.from_stack_info(stack_info), *args, **kwargs)

    @classmethod
    def from_catmaid(cls, catmaid, stack_id, *args, **kwargs):
        """

        Parameters
        ----------
        catmaid : catpy.AbstractCatmaidClient
        stack_id : int
        args, kwargs
            See __init__ for arguments beyond stack

        Returns
        -------
        ImageFetcher
        """
        stack_info = catmaid.get(
            (catmaid.project_id, 'stack', stack_id, 'info'))
        return cls.from_stack_info(stack_info, *args, **kwargs)