Add --resample so Hecate can read the 8.64 µm eligible volumes
#3
by vincentl065 - opened
- hecate.py +22 -3
- requirements.txt +1 -0
hecate.py
CHANGED
|
@@ -411,6 +411,9 @@ def main():
|
|
| 411 |
parser.add_argument('--batch-size', type=int, default=1)
|
| 412 |
parser.add_argument('--stride', type=int, help='XY stride; default half the trained patch width')
|
| 413 |
parser.add_argument('--precision', choices=('fp32','bf16'), default='fp32')
|
|
|
|
|
|
|
|
|
|
| 414 |
args = parser.parse_args()
|
| 415 |
if not args.output and not args.output_3d:
|
| 416 |
parser.error('Specify --output and/or --output-3d')
|
|
@@ -420,21 +423,37 @@ def main():
|
|
| 420 |
if path is not None and (path.suffix.lower()!=suffix or path.exists()):
|
| 421 |
parser.error(f'Choose a new output filename ending in {suffix}: {path}')
|
| 422 |
model = load_model(args.checkpoint,args.device)
|
| 423 |
-
|
| 424 |
-
|
|
|
|
|
|
|
|
|
|
| 425 |
volume = open_volume(args.input,args.array)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
if output is not None:
|
| 427 |
output.parent.mkdir(parents=True,exist_ok=True)
|
| 428 |
with tempfile.TemporaryDirectory(prefix='hecate-') as tmp:
|
| 429 |
result = np.memmap(Path(tmp)/'prediction',mode='w+',dtype='uint8',shape=volume.shape[-2:])
|
| 430 |
predict(model,volume,result,args.reverse,args.stride,args.batch_size,args.precision)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
partial = output.with_suffix('.partial')
|
| 432 |
write_png(result,partial)
|
| 433 |
partial.replace(output)
|
| 434 |
del result
|
| 435 |
metadata = {
|
| 436 |
'checkpoint':Path(args.checkpoint).name,'sampling_um':args.spacing_um,
|
| 437 |
-
'shape_yx':list(
|
|
|
|
| 438 |
'central_depth':model.patch_size[0],'precision':args.precision,
|
| 439 |
'stride':args.stride or model.patch_size[1]//2,
|
| 440 |
'probabilities':'uint8 = round(255 * probability); no min-max scaling'
|
|
|
|
| 411 |
parser.add_argument('--batch-size', type=int, default=1)
|
| 412 |
parser.add_argument('--stride', type=int, help='XY stride; default half the trained patch width')
|
| 413 |
parser.add_argument('--precision', choices=('fp32','bf16'), default='fp32')
|
| 414 |
+
parser.add_argument('--resample', action='store_true',
|
| 415 |
+
help='Rescale the render from --spacing-um to the model spacing before inference (linear), and map the 2D '
|
| 416 |
+
'prediction back to the input YX grid. Not supported with --output-3d.')
|
| 417 |
args = parser.parse_args()
|
| 418 |
if not args.output and not args.output_3d:
|
| 419 |
parser.error('Specify --output and/or --output-3d')
|
|
|
|
| 423 |
if path is not None and (path.suffix.lower()!=suffix or path.exists()):
|
| 424 |
parser.error(f'Choose a new output filename ending in {suffix}: {path}')
|
| 425 |
model = load_model(args.checkpoint,args.device)
|
| 426 |
+
off_scale = abs(args.spacing_um/model.sampling_um-1) > .02
|
| 427 |
+
if off_scale and not args.resample:
|
| 428 |
+
parser.error(f'Resample the render to {model.sampling_um} um first, or pass --resample')
|
| 429 |
+
if args.resample and output3d is not None:
|
| 430 |
+
parser.error('--resample is only supported for the 2D --output')
|
| 431 |
volume = open_volume(args.input,args.array)
|
| 432 |
+
input_yx = volume.shape[-2:]
|
| 433 |
+
if args.resample and off_scale:
|
| 434 |
+
from scipy import ndimage
|
| 435 |
+
factor = args.spacing_um/model.sampling_um
|
| 436 |
+
volume = ndimage.zoom(np.asarray(volume),factor,order=1)
|
| 437 |
+
print(f'resampled {args.spacing_um} um -> {model.sampling_um} um: {input_yx} -> {volume.shape[-2:]}',flush=True)
|
| 438 |
if output is not None:
|
| 439 |
output.parent.mkdir(parents=True,exist_ok=True)
|
| 440 |
with tempfile.TemporaryDirectory(prefix='hecate-') as tmp:
|
| 441 |
result = np.memmap(Path(tmp)/'prediction',mode='w+',dtype='uint8',shape=volume.shape[-2:])
|
| 442 |
predict(model,volume,result,args.reverse,args.stride,args.batch_size,args.precision)
|
| 443 |
+
if tuple(result.shape)!=tuple(input_yx):
|
| 444 |
+
from scipy import ndimage
|
| 445 |
+
back = ndimage.zoom(np.asarray(result,dtype=np.float32),
|
| 446 |
+
(input_yx[0]/result.shape[0],input_yx[1]/result.shape[1]),order=1)
|
| 447 |
+
back = np.clip(np.round(back),0,255).astype('uint8')[:input_yx[0],:input_yx[1]]
|
| 448 |
+
result = np.zeros(input_yx,dtype='uint8'); result[:back.shape[0],:back.shape[1]] = back
|
| 449 |
partial = output.with_suffix('.partial')
|
| 450 |
write_png(result,partial)
|
| 451 |
partial.replace(output)
|
| 452 |
del result
|
| 453 |
metadata = {
|
| 454 |
'checkpoint':Path(args.checkpoint).name,'sampling_um':args.spacing_um,
|
| 455 |
+
'shape_yx':list(input_yx),'reverse_z':args.reverse,
|
| 456 |
+
'resampled_to_um':model.sampling_um if (args.resample and off_scale) else None,
|
| 457 |
'central_depth':model.patch_size[0],'precision':args.precision,
|
| 458 |
'stride':args.stride or model.patch_size[1]//2,
|
| 459 |
'probabilities':'uint8 = round(255 * probability); no min-max scaling'
|
requirements.txt
CHANGED
|
@@ -2,3 +2,4 @@ torch>=2.6,<3
|
|
| 2 |
numpy>=1.26,<3
|
| 3 |
zarr>=2.18,<4
|
| 4 |
numcodecs>=0.12,<1
|
|
|
|
|
|
| 2 |
numpy>=1.26,<3
|
| 3 |
zarr>=2.18,<4
|
| 4 |
numcodecs>=0.12,<1
|
| 5 |
+
scipy>=1.11,<2
|