Skip to content

Commit

Permalink
add neighbors property for bounding box (#249)
Browse files Browse the repository at this point in the history
* add neighbors property for bounding box

* fix warnings. replace np.bool with bool. replace np.int with int

* once we have found the plugin file, we should stop searching

* update

* add test bounding box; first version of 62_seg
  • Loading branch information
xiuliren authored Jul 22, 2021
1 parent 4f7a1aa commit 479bd48
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 37 deletions.
19 changes: 9 additions & 10 deletions chunkflow/chunk/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,18 +178,18 @@ def from_h5(cls, file_name: str,
if cutout_start is not None and cutout_size is not None:
cutout_stop = tuple(t+s for t, s in zip(cutout_start, cutout_size))

assert cutout_start is not None
assert cutout_stop is not None
bbox = BoundingBox.from_list([*cutout_start, *cutout_stop])

if not h5py.is_hdf5(file_name):
assert cutout_start is not None
assert cutout_stop is not None
bbox = BoundingBox.from_list([*cutout_start, *cutout_stop])
file_name += f'{bbox.to_filename()}.h5'

if not os.path.exists(file_name) and zero_filling:
# fill with zero
assert dtype is not None
print(f'file do not exist, will fill with zero: {file_name}')
return cls.from_bbox(bbox, dtype=dtype, voxel_size=voxel_size, all_zero=True)
if not os.path.exists(file_name) and zero_filling:
# fill with zero
assert dtype is not None
print(f'file do not exist, will fill with zero: {file_name}')
return cls.from_bbox(bbox, dtype=dtype, voxel_size=voxel_size, all_zero=True)

with h5py.File(file_name, 'r') as f:
if dataset_path is None:
Expand Down Expand Up @@ -398,8 +398,7 @@ def bbox(self) -> BoundingBox:
"""
:getter: the bounding box in the big volume
"""
bbox = Bbox.from_delta(self.voxel_offset, self.array.shape[-3:])
bbox = BoundingBox.from_bbox(bbox, self.voxel_size)
bbox = BoundingBox.from_delta(self.voxel_offset, self.array.shape[-3:])
return bbox

@property
Expand Down
33 changes: 18 additions & 15 deletions chunkflow/flow/flow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python
import os
from time import time
from typing import Generator, Tuple
from typing import Generator
from copy import deepcopy

import numpy as np
import click
Expand All @@ -19,7 +20,6 @@
from chunkflow.chunk.affinity_map import AffinityMap
from chunkflow.chunk.segmentation import Segmentation
from chunkflow.chunk.image.convnet.inferencer import Inferencer
from chunkflow.lib.utils import coordinates2bbox

# import operator functions
from .aggregate_skeleton_fragments import AggregateSkeletonFragmentsOperator
Expand Down Expand Up @@ -161,10 +161,10 @@ def skip_task(tasks: Generator, pre: str, post: str, grow_size: int):
help = 'pre-path of a file. we would like to keep a trace that this task was executed.')
@click.option('--post', '-t', type=str, default=None,
help='post-path of a file. normally include the extention of result file.')
@click.option('--grow-size', '-g', type=int, default=None,
@click.option('--adjust-size', '-g', type=int, default=None,
help='change the bounding box of chunk if it do not match with final result file name.')
@operator
def skip_all_zero(tasks, input_chunk_name: str, pre: str, post: str, grow_size: int):
def skip_all_zero(tasks, input_chunk_name: str, pre: str, post: str, adjust_size: int):
"""if chunk has all zero, skip this task."""
for task in tasks:
if task is not None:
Expand All @@ -173,11 +173,11 @@ def skip_all_zero(tasks, input_chunk_name: str, pre: str, post: str, grow_size:
print('all zero chunk, skip this task')
if pre is not None:
bbox = chunk.bbox.clone()
if grow_size is not None:
if adjust_size is not None:
# bbox.grow(grow_size)
# currently, cloud-volume do not support negative grow size
bbox.minpt -= grow_size
bbox.maxpt += grow_size
bbox.minpt -= adjust_size
bbox.maxpt += adjust_size
fname = os.path.join(pre, f'{bbox.to_filename()}{post}')
print('create an empty file as mark: ', fname)
with open(fname, 'a'):
Expand Down Expand Up @@ -1153,19 +1153,22 @@ def connected_components(tasks, name, input_chunk_name, output_chunk_name,


@main.command('copy-var')
@click.option('--name', type=str, default='copy-var-1', help='name of step')
@click.option('--from-name',
@click.option('--from-name', '-f',
type=str,
default='chunk',
help='Variable to be (shallow) copied/"renamed"')
@click.option('--to-name', type=str, default='chunk', help='New variable name')
help='Variable to be copied')
@click.option('--to-name', '-t', type=str, default='chunk', help='New variable name')
@click.option('--deep-copy/--shallow-copy', type=bool, default=True,
help='really copy data or just create a new name or reference.')
@operator
def copy_var(tasks, name, from_name, to_name):
"""Copy a variable to a new name.
"""
def copy_var(tasks, from_name: str, to_name: str, deep_copy: bool):
"""Deep or shallow copy a variable."""
for task in tasks:
if task is not None:
task[to_name] = task[from_name]
if deep_copy:
task[to_name] = deepcopy(task[from_name])
else:
task[to_name] = task[from_name]
yield task


Expand Down
2 changes: 1 addition & 1 deletion chunkflow/flow/mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def _read_mask_in_high_mip(self, chunk_bbox, factor):
# which will make np.alltrue(mask_in_high_mip == 0) to be
# VolumeCutout(False) rather than False, so we need to transform it
# to numpy
mask = mask.astype(np.bool)
mask = mask.astype(bool)
mask = np.asarray(mask)
mask = np.transpose(mask)
mask = np.squeeze(mask, axis=0)
Expand Down
7 changes: 4 additions & 3 deletions chunkflow/flow/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ def __init__(self, plugin_file_name: str,

for plugin_dir in plugin_dirs:
fname = path.join(plugin_dir, plugin_file_name)
print(f'looking for {fname}')
if path.exists(fname):
print(f'loading plugin {fname}')
program = load_source(fname)
# assuming this is a func / static functor for now, maybe make it a class?
self.execute = program.execute
break
assert hasattr(self, 'execute')

def __call__(self, inputs: list, args: str = None):
Expand All @@ -58,7 +58,7 @@ def __call__(self, inputs: list, args: str = None):
voxel_size = inp.voxel_size
shape = inp.shape
break

if len(inputs) == 0 and args is None:
outputs = self.execute()
elif len(inputs) == 0 and args is not None:
Expand All @@ -67,7 +67,8 @@ def __call__(self, inputs: list, args: str = None):
outputs = self.execute(*inputs)
else:
outputs = self.execute(*inputs, args=args)
assert isinstance(outputs, list) or isinstance(outputs, tuple)
assert isinstance(outputs, list) or isinstance(outputs, tuple) or outputs is None


# automatically convert the ndarrays to Chunks
if voxel_offset is not None and outputs is not None:
Expand Down
4 changes: 2 additions & 2 deletions chunkflow/flow/write_precomputed.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ def __call__(self, chunk, log=None):
if self.intensity_threshold is not None and np.all(chunk.array < self.intensity_threshold):
print('the voxel intensity in this chunk are all below intensity threshold, return directly without saving anything.')
return

chunk = self._auto_convert_dtype(chunk, self.volume)

# transpose czyx to xyzc order
arr = np.transpose(chunk.array)
self.volume[chunk.slices[::-1]] = arr
Expand Down
30 changes: 29 additions & 1 deletion chunkflow/lib/bounding_boxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from collections import UserList
from math import ceil
from typing import Union
from copy import deepcopy

import numpy as np
import h5py
Expand All @@ -21,15 +22,42 @@ def __init__(self, min_corner: list, max_corner: list, dtype=None, voxel_size: t
def from_bbox(cls, bbox: Bbox, voxel_size: tuple = None):
return cls(bbox.minpt, bbox.maxpt, voxel_size=voxel_size)

@classmethod
def from_delta(cls, minpt, plus):
bbox = super().from_delta(minpt, plus)
return cls.from_bbox(bbox)

@classmethod
def from_list(cls, x: list):
bbox = Bbox.from_list(x)
return cls.from_bbox(bbox)


def clone(self):
bbox = Bbox(self.minpt, self.maxpt, dtype=self.dtype)
bbox = bbox.clone()
return BoundingBox.from_bbox(bbox, voxel_size=self.voxel_size)

@property
def voxel_size(self):
return self._voxel_size

@property
def left_neighbors(self):
sz = self.size3()

minpt = deepcopy(self.minpt)
minpt[0] -= sz[0]
bbox_z = self.from_delta(minpt, sz)

minpt = deepcopy(self.minpt)
minpt[1] -= sz[1]
bbox_y = self.from_delta(minpt, sz)

minpt = deepcopy(self.minpt)
minpt[2] -= sz[2]
bbox_x = self.from_delta(minpt, sz)
return bbox_z, bbox_y, bbox_x

@voxel_size.setter
def voxel_size(self, vs: tuple):
self._voxel_size = vs
Expand Down
2 changes: 1 addition & 1 deletion chunkflow/lib/igneous/downsample.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def downsample_with_averaging(array, factor):
output_shape = tuple(
int(math.ceil(s / f)) for s, f in zip(array.shape, factor))
temp = np.zeros(output_shape, dtype=np.float32)
counts = np.zeros(output_shape, np.int)
counts = np.zeros(output_shape, int)
for offset in np.ndindex(factor):
part = array[tuple(np.s_[o::f] for o, f in zip(offset, factor))]
indexing_expr = tuple(np.s_[:s] for s in part.shape)
Expand Down
8 changes: 4 additions & 4 deletions tests/flow/test_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ def test_inference_pipeline():
vol_path=input_volume_path)

# create input mask volume
input_mask = np.ones(input_size, dtype=np.bool)
input_mask = np.ones(input_size, dtype=bool)
input_mask_volume_path = 'file:///tmp/input-mask/' + generate_random_string()
# the max_mip in CloudVolume is actually mip_stop
# it is not inclusive, so we need to +1 here
CloudVolume.from_numpy(np.transpose(input_mask),
vol_path=input_mask_volume_path,
max_mip=input_mask_mip+1)
input_mask = np.ones(input_mask_size, dtype=np.bool)
input_mask = np.ones(input_mask_size, dtype=bool)
# will mask out the [:2, :8, :8] since it is in mip 1
input_mask[:(4 + 2), :(64 // 2 + 8 // 2), :(64 // 2 + 8 // 2)] = False
input_mask_vol = CloudVolume(input_mask_volume_path,
Expand All @@ -71,15 +71,15 @@ def test_inference_pipeline():
# this is the mip 0 size, so the size should be the same with output
# it was only used to create the volume
# TODO: delete this step by creating a mip parameter in from_numpy function
output_mask = np.ones(output_size, dtype=np.bool)
output_mask = np.ones(output_size, dtype=bool)
output_mask_volume_path = 'file:///tmp/output-mask/' + generate_random_string(
)
CloudVolume.from_numpy(np.transpose(output_mask),
vol_path=output_mask_volume_path,
max_mip=output_mask_mip + 1,
voxel_offset=cropping_margin_size[::-1])
# this is the higher mip level mask, so this time we are using the real size
output_mask = np.ones(output_mask_size, dtype=np.bool)
output_mask = np.ones(output_mask_size, dtype=bool)
# will mask out the [-2:, -8:, -8:] since it is in mip 2
output_mask[-2:, -8 // 4:, -8 // 4:] = False
output_mask_vol = CloudVolume(output_mask_volume_path,
Expand Down
13 changes: 13 additions & 0 deletions tests/lib/test_bounding_box.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from cloudvolume.lib import Bbox

from chunkflow.lib.bounding_boxes import BoundingBox


def test_bounding_box():
bbox = Bbox.from_delta((1,3,2), (64, 32, 8))
bbox = BoundingBox.from_bbox(bbox)

bbox = bbox.clone()
assert isinstance(bbox, BoundingBox)


0 comments on commit 479bd48

Please sign in to comment.