comparison image_math.py @ 0:e0beec1d4bf1 draft default tip

planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/image_math commit cddd14dcfe71bb7a7e77aaf09bd2c5535e1dd643
author imgteam
date Fri, 06 Mar 2026 17:31:48 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:e0beec1d4bf1
1 import argparse
2 import ast
3 import operator
4
5 import giatools
6 import numpy as np
7
8
9 supported_operators = {
10 ast.Add: operator.add,
11 ast.Sub: operator.sub,
12 ast.Mult: operator.mul,
13 ast.Div: operator.truediv,
14 ast.FloorDiv: operator.floordiv,
15 ast.Pow: operator.pow,
16 ast.USub: operator.neg,
17 }
18
19
20 supported_functions = {
21 'sqrt': np.sqrt,
22 'abs': abs,
23 }
24
25
26 def eval_ast_node(node, inputs):
27 """
28 Evaluates a node of the syntax tree.
29 """
30
31 # Numeric constants evaluate to numeric values.
32 if isinstance(node, ast.Constant):
33 assert type(node.value) in (int, float)
34 return node.value
35
36 # Variables are looked up from the inputs and resolved.
37 if isinstance(node, ast.Name):
38 assert node.id in inputs.keys()
39 return inputs[node.id]
40
41 # Binary operators are evaluated based on the `supported_operators` dictionary.
42 if isinstance(node, ast.BinOp):
43 assert type(node.op) in supported_operators.keys(), node.op
44 op = supported_operators[type(node.op)]
45 return op(eval_ast_node(node.left, inputs), eval_ast_node(node.right, inputs))
46
47 # Unary operators are evaluated based on the `supported_operators` dictionary.
48 if isinstance(node, ast.UnaryOp):
49 assert type(node.op) in supported_operators.keys(), node.op
50 op = supported_operators[type(node.op)]
51 return op(eval_ast_node(node.operand, inputs))
52
53 # Function calls are evaluated based on the `supported_functions` dictionary.
54 if isinstance(node, ast.Call):
55 assert len(node.args) == 1 and len(node.keywords) == 0
56 assert node.func.id in supported_functions.keys(), node.func.id
57 func = supported_functions[node.func.id]
58 return func(eval_ast_node(node.args[0], inputs))
59
60 # The node is unsupported and could not be evaluated.
61 raise TypeError(f'Unsupported node type: "{node}"')
62
63
64 def eval_expression(expr, inputs):
65 return eval_ast_node(ast.parse(expr, mode='eval').body, inputs)
66
67
68 if __name__ == '__main__':
69
70 parser = argparse.ArgumentParser()
71 parser.add_argument('--expression', type=str, required=True)
72 parser.add_argument('--dtype', type=str, default=None)
73 parser.add_argument('--output', type=str, required=True)
74 parser.add_argument('--input', default=list(), action='append', required=True)
75 args = parser.parse_args()
76
77 inputs = dict()
78 im_shape = None
79 for input in args.input:
80 name, filepath = input.split(':')
81 im = giatools.Image.read(filepath)
82 assert name not in inputs, 'Input name "{name}" is ambiguous.'
83 inputs[name] = im.data
84 if im_shape is None:
85 im_shape = im.shape
86 else:
87 assert im.shape == im_shape, 'Input images differ in size and/or number of channels.'
88
89 result = eval_expression(args.expression, inputs)
90
91 # Perform explicit `dtype` conversion
92 if args.dtype:
93 if args.dtype.startswith('uint'):
94 result = result.clip(0, np.inf)
95 result = result.astype(args.dtype)
96
97 # Write result image (preserve metadata from last input image)
98 im.data = result
99 im.normalize_axes_like(
100 im.original_axes,
101 ).write(
102 args.output,
103 )