Skip to content

Commit b6d40dc

Browse files
committed
Merge branch 'master' of github.com:seung-lab/chunkflow
2 parents b140595 + 5206571 commit b6d40dc

14 files changed

+290
-254
lines changed

chunkflow/chunk/base.py

Lines changed: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222
# Offset = Tuple[int, int, int]
2323
from .validate import validate_by_template_matching
2424

25-
def type_is_valid(type: str):
25+
def layer_type_is_valid(type: str):
2626
return type in set([None, 'image', 'segmentation', 'probability_map', 'affinity_map', 'unknown'])
2727

2828

2929
class Chunk(NDArrayOperatorsMixin):
3030
def __init__(self, array: np.ndarray,
3131
voxel_offset: Cartesian = None,
3232
voxel_size: Cartesian = None,
33-
type: str = None):
33+
layer_type: str = None):
3434
"""chunk of a volume
3535
3636
a chunk of big array with offset
@@ -49,7 +49,7 @@ def __init__(self, array: np.ndarray,
4949
"""
5050
assert array.ndim >= 3 and array.ndim <= 4
5151
assert isinstance(array, np.ndarray) or isinstance(array, Chunk)
52-
assert type_is_valid(type)
52+
assert layer_type_is_valid(layer_type), f'layer type: {layer_type} is unsupported!'
5353

5454
self.array = array
5555
if voxel_offset is None:
@@ -76,20 +76,20 @@ def __init__(self, array: np.ndarray,
7676
assert len(voxel_size) == 3
7777
assert np.alltrue([vs > 0 for vs in voxel_size])
7878

79-
if type is not None:
80-
self.type = type
79+
if layer_type is not None:
80+
self.layer_type = layer_type
8181
else:
8282
# best guess
8383
if self.is_image:
84-
self.type = 'image'
84+
self.layer_type = 'image'
8585
elif self.is_segmentation:
86-
self.type = 'segmentation'
86+
self.layer_type = 'segmentation'
8787
elif self.is_probability_map:
88-
self.type = 'probability_map'
88+
self.layer_type = 'probability_map'
8989
elif self.is_affinity_map:
90-
self.type = 'affinity_map'
90+
self.layer_type = 'affinity_map'
9191
else:
92-
self.type = 'unknown'
92+
self.layer_type = 'unknown'
9393

9494
# One might also consider adding the built-in list type to this
9595
# list, to support operations like np.add(array_like, list)
@@ -268,7 +268,7 @@ def from_h5(cls, file_name: str,
268268
cutout_stop: tuple = None,
269269
cutout_size: tuple = None,
270270
dtype: str = None,
271-
type: str = None):
271+
layer_type: str = None):
272272

273273
file_name = os.path.expanduser(file_name)
274274
if not os.path.exists(file_name):
@@ -310,17 +310,23 @@ def from_h5(cls, file_name: str,
310310
else:
311311
voxel_size = Cartesian(1, 1, 1)
312312

313-
if type is None:
314-
if 'type' in f.attrs:
315-
type = f.attrs['type']
313+
if layer_type is None:
314+
if 'layer_type' in f.attrs:
315+
layer_type = f.attrs['layer_type']
316316
# type = str(f['type'])
317-
breakpoint()
318-
assert type_is_valid(type)
317+
assert layer_type_is_valid(layer_type)
319318

320319
if cutout_start is None:
321320
cutout_start = voxel_offset
322321
if cutout_size is None:
323322
cutout_size = dset.shape[-3:]
323+
cutout_size = Cartesian.from_collection(cutout_size)
324+
elif np.min(cutout_size) < 0:
325+
cutout_size = [x for x in cutout_size]
326+
for idx in range(-1, -4, -1):
327+
if cutout_size[idx]<0:
328+
cutout_size[idx] = dset.shape[idx]
329+
cutout_size = Cartesian.from_collection(cutout_size)
324330
if cutout_stop is None:
325331
cutout_stop = tuple(t+s for t, s in zip(cutout_start, cutout_size))
326332

@@ -346,7 +352,7 @@ def from_h5(cls, file_name: str,
346352

347353
logging.info(f'new chunk voxel offset: {cutout_start}')
348354

349-
return cls(arr, voxel_offset=cutout_start, voxel_size=voxel_size, type=type)
355+
return cls(arr, voxel_offset=cutout_start, voxel_size=voxel_size, layer_type=layer_type)
350356

351357
def to_h5(self, file_name: str, with_offset: bool=True,
352358
chunk_size: Union[Cartesian, tuple] = (8,8,8),
@@ -378,8 +384,8 @@ def to_h5(self, file_name: str, with_offset: bool=True,
378384
voxel_size = self.voxel_size
379385
if voxel_size is not None:
380386
f.create_dataset('/voxel_size', data=voxel_size)
381-
if self.type is not None:
382-
f.attrs['type'] = self.type
387+
if self.layer_type is not None:
388+
f.attrs['layer_type'] = self.layer_type
383389

384390
if with_offset and self.voxel_offset is not None:
385391
f.create_dataset('/voxel_offset', data=self.voxel_offset)
@@ -493,36 +499,15 @@ def is_affinity_map(self) -> bool:
493499
def is_probability_map(self) -> bool:
494500
return self.array.ndim == 4 and self.array.dtype == np.float32
495501

496-
# @property
497-
# def type(self) -> str:
498-
# if self.data_type is None:
499-
# if self.is_image:
500-
# self.type = 'image'
501-
# elif self.is_segmentation:
502-
# self.type = 'segmentation'
503-
# elif self.is_probability_map:
504-
# self.type = 'probability_map'
505-
# elif self.is_affinity_map:
506-
# self.type = 'affinity_map'
507-
# else:
508-
# self.type = 'unknown'
509-
510-
# return self.type
511-
512-
# @type.setter
513-
# def type(self, value: str):
514-
# assert value in set([None, 'image', 'segmentation', 'probability_map', 'affinity_map', 'unknown'])
515-
# self.type = value
516-
517502
@property
518503
def properties(self) -> dict:
519504
props = dict()
520505
if self.voxel_offset is not None or self.voxel_offset != Cartesian(0, 0, 0):
521506
props['voxel_offset'] = self.voxel_offset
522507
if self.voxel_size is not None or self.voxel_size != Cartesian(1, 1, 1):
523508
props['voxel_size'] = self.voxel_size
524-
if self.type is not None:
525-
props['type'] = self.type
509+
if self.layer_type is not None:
510+
props['layer_type'] = self.layer_type
526511

527512
return props
528513

@@ -533,8 +518,8 @@ def set_properties(self, properties: dict):
533518
if 'voxel_size' in properties:
534519
self.voxel_size = properties['voxel_size']
535520

536-
if 'type' in properties:
537-
self.type = properties['type']
521+
if 'layer_type' in properties:
522+
self.layer_type = properties['layer_type']
538523

539524
@properties.setter
540525
def properties(self, value: dict):
@@ -643,7 +628,7 @@ def channel_voting(self):
643628
return Chunk(out,
644629
voxel_offset=self.voxel_offset,
645630
voxel_size=self.voxel_size,
646-
type='segmentation',
631+
layer_type='segmentation',
647632
)
648633

649634
def mask_using_last_channel(self, threshold: float = 0.3) -> np.ndarray:
@@ -742,7 +727,7 @@ def cutout(self, x: Union[tuple, BoundingBox, Bbox]):
742727
return Chunk(arr,
743728
voxel_offset=voxel_offset,
744729
voxel_size=self.voxel_size,
745-
type=self.type)
730+
layer_type=self.layer_type)
746731

747732
def save(self, patch):
748733
"""

chunkflow/chunk/image/adjust_grey.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def normalize_section_shang(image: np.ndarray, nominalmin: float,
199199
"""
200200
assert nominalmin < nominalmax
201201
assert image.ndim == 3
202-
voxel_offset = image.voxel_offset
202+
# voxel_offset = image.voxel_offset
203203
originaltype = image.dtype
204204
arr = image.astype(np.float32)
205205

@@ -228,8 +228,7 @@ def normalize_section_shang(image: np.ndarray, nominalmin: float,
228228
# cast to original data type if necessary
229229
#arr = np.round(arr)
230230
#arr = arr.astype(originaltype)
231-
232-
return Chunk(arr, voxel_offset=voxel_offset)
231+
return arr
233232

234233

235234
def test1_grey_augment():

chunkflow/chunk/image/base.py

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
__doc__ = """Image chunk class"""
22

3+
from tqdm import tqdm
34
import numpy as np
45

56
from chunkflow.chunk import Chunk
@@ -14,10 +15,119 @@ class Image(Chunk):
1415
def __init__(self, array: np.ndarray, **kwargs):
1516
super().__init__(array, **kwargs)
1617

18+
@classmethod
19+
def from_chunk(cls, chk: Chunk):
20+
return cls(chk.array, **chk.properties)
21+
1722
def inference(self, inferencer: Inferencer):
1823
"""run convolutional net inference for this image chunk"""
1924
return inferencer(self)
2025

2126
def normalize_section_shang(self, nominalmin, nominalmax, clipvalues):
2227
return normalize_section_shang(self.array, nominalmin, nominalmax,
23-
clipvalues)
28+
clipvalues)
29+
30+
def _find_section_clamping_values(self,
31+
hist: np.ndarray, lower_clip_fraction: float, upper_clip_fraction: float):
32+
"""compute the clamping values for each section."""
33+
# remove the np.copy from original code since we only need this once
34+
filtered = hist
35+
36+
# remove pure black from frequency counts as
37+
# it has no information in our images
38+
filtered[0] = 0
39+
40+
cdf = np.zeros(shape=(len(filtered), ), dtype=np.uint64)
41+
cdf[0] = filtered[0]
42+
for i in range(1, len(filtered)):
43+
cdf[i] = cdf[i - 1] + filtered[i]
44+
45+
total = cdf[-1]
46+
47+
if total == 0:
48+
return (0, 0)
49+
50+
lower = 0
51+
for i, val in enumerate(cdf):
52+
if float(val) / float(total) > lower_clip_fraction:
53+
break
54+
lower = i
55+
56+
upper = 0
57+
for i, val in enumerate(cdf):
58+
if float(val) / float(total) > 1 - upper_clip_fraction:
59+
break
60+
upper = i
61+
62+
return lower, upper
63+
64+
def _hist_to_lookup_table(self,
65+
hist: np.ndarray, lower_clip_fraction: float, upper_clip_fraction: float,
66+
minval: int = 1,
67+
maxval: int = 255):
68+
"""histogram to lookup table
69+
70+
Args:
71+
hist (np.ndarray): histogram
72+
73+
Returns:
74+
np.ndarray: lookup table
75+
"""
76+
lower, upper = self._find_section_clamping_values(
77+
hist, lower_clip_fraction, upper_clip_fraction)
78+
79+
if lower == upper:
80+
#lookup_table = np.arange(0, 256, dtype=np.uint8)
81+
# no need to perform any transform
82+
return None
83+
else:
84+
# compute the lookup table
85+
lookup_table = np.arange(0, 256, dtype=np.float32)
86+
lookup_table = (lookup_table - float(lower)) * (
87+
maxval / (float(upper) - float(lower)))
88+
np.clip(lookup_table, minval, maxval, out=lookup_table)
89+
lookup_table = np.round(lookup_table)
90+
lookup_table = lookup_table.astype( np.uint8 )
91+
return lookup_table
92+
93+
94+
def normalize_contrast(self,
95+
lower_clip_fraction: float = 0.01,
96+
upper_clip_fraction: float = 0.01,
97+
minval: int = 1,
98+
maxval: int = 255,
99+
per_section: bool = True
100+
):
101+
102+
def _normalize_array(array: np.ndarray,
103+
lower_clip_fraction: float,
104+
upper_clip_fraction: float,
105+
minval: int = 1,
106+
maxval: int = 255):
107+
hist = np.bincount(array.flatten(), minlength=255)
108+
lookup_table = self._hist_to_lookup_table(
109+
hist, lower_clip_fraction, upper_clip_fraction,
110+
minval=minval, maxval=maxval)
111+
if lookup_table is not None:
112+
array = lookup_table[array]
113+
return array
114+
115+
116+
if per_section:
117+
for z in tqdm(range(self.bbox.start.z, self.bbox.stop.z)):
118+
slices = (slice(z, z+1), *self.slices[-2:])
119+
section = self.cutout(slices)
120+
# section = Image.from_chunk(section)
121+
section.array = _normalize_array(
122+
section.array,
123+
lower_clip_fraction, upper_clip_fraction,
124+
minval=minval, maxval=maxval
125+
)
126+
self.save(section)
127+
else:
128+
# chunk = Image.from_chunk(chunk)
129+
self.array = _normalize_array(
130+
self.array,
131+
lower_clip_fraction, upper_clip_fraction,
132+
minval=minval, maxval=maxval
133+
)

0 commit comments

Comments
 (0)