validators.py 12.8 KB
Newer Older
Pierre Paleo's avatar
Pierre Paleo committed
1
2
import os
path = os.path
Pierre Paleo's avatar
Pierre Paleo committed
3
from ..utils import is_writeable
Pierre Paleo's avatar
Pierre Paleo committed
4
from .params import *
Pierre Paleo's avatar
Pierre Paleo committed
5
6
7

"""
A validator is a function with
Pierre Paleo's avatar
Pierre Paleo committed
8
9
  - input: a value
  - output: the input value, or a modified input value
Pierre Paleo's avatar
Pierre Paleo committed
10
11
12
13
  - possibly raising exceptions in case of invalid value.
"""


Pierre Paleo's avatar
Pierre Paleo committed
14
15


Pierre Paleo's avatar
Pierre Paleo committed
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# ------------------------------------------------------------------------------
# ---------------------------- Utils -------------------------------------------
# ------------------------------------------------------------------------------


def raise_error(section, key, msg=""):
    raise ValueError(
        "Invalid value for %s/%s: %s"
        % (section, key, msg)
    )

def validator(func):
    """
    Common decorator for all validator functions.
    It modifies the signature of the decorated functions !
    """
    def wrapper(section, key, value):
        try:
            res = func(value)
        except AssertionError as e:
            raise_error(section, key, e)
        return res
    return wrapper


def convert_to_int(val):
    val_int = 0
    try:
        val_int = int(val)
        conversion_error = None
    except ValueError as exc:
        conversion_error = exc
    return val_int, conversion_error


def convert_to_float(val):
    val_float = 0.0
    try:
        val_float = float(val)
        conversion_error = None
    except ValueError as exc:
        conversion_error = exc
    return val_float, conversion_error


61
62
def convert_to_bool(val):
    val_int, error = convert_to_int(val)
Pierre Paleo's avatar
Pierre Paleo committed
63
    res = None
64
65
66
67
68
69
70
71
72
73
74
75
    if not error:
        res = (val_int > 0)
    else:
        if val.lower() in ["yes", "true"]:
            res = True
            error = None
        if val.lower() in ["no", "false"]:
            res = False
            error = None
    return res, error


76
77
78
79
80
81
82
def convert_to_bool_noerr(val):
    res, err = convert_to_bool(val)
    if err is not None:
        raise ValueError("Could not convert to boolean: %s" % str(val))
    return res


Pierre Paleo's avatar
Pierre Paleo committed
83
84
def name_range_checker(name, valid_names, descr, replacements=None):
    name = name.strip().lower()
85
    if replacements is not None and name in replacements:
Pierre Paleo's avatar
Pierre Paleo committed
86
        name = replacements[name]
Pierre Paleo's avatar
Pierre Paleo committed
87
88
    valid = (name in valid_names)
    assert valid, "Invalid %s '%s'. Available are %s" % (descr, name, str(valid_names))
Pierre Paleo's avatar
Pierre Paleo committed
89
90
91
92
93
94
95
    return name


# ------------------------------------------------------------------------------
# ---------------------------- Validators --------------------------------------
# ------------------------------------------------------------------------------

96
97
98
99
100
@validator
def optional_string_validator(val):
    if len(val.strip()) == 0:
        return None
    return val
Pierre Paleo's avatar
Pierre Paleo committed
101
102
103
104
105
106
107
108
109
110
111
112
113
114

@validator
def file_name_validator(name):
    assert len(name) >= 1, "Name should be non-empty"
    return name

@validator
def file_location_validator(location):
    assert path.isfile(location), "location must be a file"
    return location

@validator
def optional_file_location_validator(location):
    if len(location.strip()) > 0:
Pierre Paleo's avatar
Pierre Paleo committed
115
116
        assert path.isfile(location), "location must be a file"
        return location
Pierre Paleo's avatar
Pierre Paleo committed
117
    return None
Pierre Paleo's avatar
Pierre Paleo committed
118

Pierre Paleo's avatar
Pierre Paleo committed
119
120
121
122
123
124
125
126
127
128
129
130
131
@validator
def optional_values_file_validator(location):
    if len(location.strip()) == 0:
        return None
    if path.splitext(location)[-1].strip() == "":
        # Assume path to h5 dataset. Validation is done later.
        if "://" not in location:
            location = "silx://" + location
    else:
        # Assume plaintext file
        assert path.isfile(location), "Invalid file path"
    return location

Pierre Paleo's avatar
Pierre Paleo committed
132
133
134
135
136
@validator
def directory_location_validator(location):
    assert path.isdir(location), "location must be a directory"
    return location

137
138
139
@validator
def optional_directory_location_validator(location):
    if len(location.strip()) > 0:
Pierre Paleo's avatar
Pierre Paleo committed
140
        assert is_writeable(location), "Directory must be writeable"
141
        return location
142
143
    return None

Pierre Paleo's avatar
Pierre Paleo committed
144
145
146
@validator
def dataset_location_validator(location):
    if not(path.isdir(location)):
147
        assert path.isfile(location) and path.splitext(location)[-1].split(".")[-1].lower() in files_formats, "Dataset location must be a directory or a HDF5 file"
Pierre Paleo's avatar
Pierre Paleo committed
148
149
    return location

Pierre Paleo's avatar
Pierre Paleo committed
150
151
@validator
def directory_writeable_validator(location):
Pierre Paleo's avatar
Pierre Paleo committed
152
    assert is_writeable(location), "Directory must be writeable"
Pierre Paleo's avatar
Pierre Paleo committed
153
154
155
156
157
158
159
    return location

@validator
def optional_output_directory_validator(location):
    if len(location.strip()) > 0:
        return directory_writeable_validator(location)
    return None
160
161
162
163
164
165

@validator
def optional_output_file_path_validator(location):
    if len(location.strip()) > 0:
        dirname, fname = path.split(location)
        assert os.access(dirname, os.W_OK), "Directory must be writeable"
Pierre Paleo's avatar
Pierre Paleo committed
166
        return location
167
    return None
Pierre Paleo's avatar
Pierre Paleo committed
168

Pierre Paleo's avatar
Pierre Paleo committed
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
@validator
def integer_validator(val):
    val_int, error = convert_to_int(val)
    assert error is None, "number must be an integer"
    return val_int

@validator
def nonnegative_integer_validator(val):
    val_int, error = convert_to_int(val)
    assert error is None and val_int >= 0, "number must be a non-negative integer"
    return val_int

@validator
def positive_integer_validator(val):
    val_int, error = convert_to_int(val)
    assert error is None and val_int > 0, "number must be a positive integer"
    return val_int

187
188
189
190
191
192
@validator
def nonzero_integer_validator(val):
    val_int, error = convert_to_int(val)
    assert error is None and val_int != 0, "number must be a non-zero integer"
    return val_int

Pierre Paleo's avatar
Pierre Paleo committed
193
194
@validator
def binning_validator(val):
Pierre Paleo's avatar
Pierre Paleo committed
195
196
    if val == "":
        val = "1"
Pierre Paleo's avatar
Pierre Paleo committed
197
198
199
200
201
202
    val_int, error = convert_to_int(val)
    assert error is None and val_int >= 0, "number must be a non-negative integer"
    return max(1, val_int)

@validator
def optional_file_name_validator(val):
Pierre Paleo's avatar
Pierre Paleo committed
203
204
    if len(val) > 0:
        assert len(val) >= 1, "Name should be non-empty"
205
        assert path.basename(val) == val, "File name should not be a path (no '/')"
Pierre Paleo's avatar
Pierre Paleo committed
206
207
        return val
    return None
Pierre Paleo's avatar
Pierre Paleo committed
208
209
210

@validator
def boolean_validator(val):
211
    res, error = convert_to_bool(val)
Pierre Paleo's avatar
Pierre Paleo committed
212
    assert error is None, "Invalid boolean value"
213
    return res
Pierre Paleo's avatar
Pierre Paleo committed
214

215
216
217
218
219
220
221
222
@validator
def boolean_or_auto_validator(val):
    res, error = convert_to_bool(val)
    if error is not None:
        assert val.lower() == "auto", "Valid values are 0, 1 and auto"
        return val
    return res

Pierre Paleo's avatar
Pierre Paleo committed
223
224
225
226
@validator
def float_validator(val):
    val_float, error = convert_to_float(val)
    assert error is None, "Invalid number"
Pierre Paleo's avatar
Pierre Paleo committed
227
228
229
230
    return val_float

@validator
def optional_float_validator(val):
231
232
233
    if isinstance(val, float):
        return val
    elif len(val.strip()) >= 1:
Pierre Paleo's avatar
Pierre Paleo committed
234
235
236
        val_float, error = convert_to_float(val)
        assert error is None, "Invalid number"
    else:
Pierre Paleo's avatar
Pierre Paleo committed
237
        val_float = None
Pierre Paleo's avatar
Pierre Paleo committed
238
    return val_float
Pierre Paleo's avatar
Pierre Paleo committed
239

240
241
242
243
244
245
246
247
248
249
250
251
252
253
@validator
def optional_nonzero_float_validator(val):
    if isinstance(val, float):
        val_float = val
    elif len(val.strip()) >= 1:
        val_float, error = convert_to_float(val)
        assert error is None, "Invalid number"
    else:
        val_float = None
    if val_float is not None:
        if abs(val_float) < 1e-6:
            val_float = None
    return val_float

Pierre Paleo's avatar
Pierre Paleo committed
254
@validator
Pierre Paleo's avatar
Pierre Paleo committed
255
def optional_tuple_of_floats_validator(val):
Pierre Paleo's avatar
Pierre Paleo committed
256
257
258
259
260
261
262
263
264
265
266
    if len(val.strip()) == 0:
        return None
    err_msg = "Expected a tuple of two numbers, but got %s" % val
    try:
        res = tuple(float(x) for x in val.strip("()").split(","))
    except Exception as exc:
        raise ValueError(err_msg)
    if len(res) != 2:
        raise ValueError(err_msg)
    return res

267
268
@validator
def cor_validator(val):
Pierre Paleo's avatar
Pierre Paleo committed
269
270
    val_float, error = convert_to_float(val)
    if error is None:
271
        return val_float
Pierre Paleo's avatar
Pierre Paleo committed
272
    if len(val.strip()) == 0:
273
        return None
Pierre Paleo's avatar
Pierre Paleo committed
274
275
    val = name_range_checker(
        val.lower(),
Pierre Paleo's avatar
Pierre Paleo committed
276
        set(cor_methods.values()),
Pierre Paleo's avatar
Pierre Paleo committed
277
278
279
280
        "center of rotation estimation method",
        replacements=cor_methods
    )
    return val
281

Pierre Paleo's avatar
Pierre Paleo committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
@validator
def tilt_validator(val):
    val_float, error = convert_to_float(val)
    if error is None:
        return val_float
    if len(val.strip()) == 0:
        return None
    val = name_range_checker(
        val.lower(),
        set(tilt_methods.values()),
        "automatic detector tilt estimation method",
        replacements=tilt_methods
    )
    return val

297
298
299
300
301
302
303
304
305
@validator
def slice_num_validator(val):
    val_int, error = convert_to_int(val)
    if error is None:
        return val_int
    else:
        assert val in ["first", "middle", "last"], "Expected start_z and end_z to be either a number or first, middle or last"
        return val

306
307
308
309
310
@validator
def cor_options_validator(val):
    if len(val.strip()) == 0:
        return None
    return val
Pierre Paleo's avatar
Pierre Paleo committed
311

312
313
314
315
316
317
318
319
320
321
322
323
@validator
def cor_slice_validator(val):
    if len(val) == 0:
        return None
    val_int, error = convert_to_int(val)
    if error:
        supported = ["top", "first", "bottom", "last", "middle"]
        assert val in supported, "Invalid value, must be a number or one of %s" % supported
        return val
    else:
        return val_int

Pierre Paleo's avatar
Pierre Paleo committed
324
325
326
327
328
329
330
331
332
333
334
@validator
def flatfield_enabled_validator(val):
    res, error = convert_to_bool(val)
    if error is not None:
        if "force" in val.lower():
            res = "forced"
        else:
            raise ValueError("Invalid value, can be 'yes', 'no' or 'forced'")
    return res


Pierre Paleo's avatar
Pierre Paleo committed
335
336
337
338
@validator
def phase_method_validator(val):
    return name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
339
        set(phase_retrieval_methods.values()),
Pierre Paleo's avatar
Pierre Paleo committed
340
        "phase retrieval method",
Pierre Paleo's avatar
Pierre Paleo committed
341
        replacements=phase_retrieval_methods
Pierre Paleo's avatar
Pierre Paleo committed
342
343
344
345
346
    )


@validator
def padding_mode_validator(val):
Pierre Paleo's avatar
Pierre Paleo committed
347
348
    return name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
349
        set(padding_modes.values()),
Pierre Paleo's avatar
Pierre Paleo committed
350
351
352
        "padding mode",
        replacements=padding_modes
    )
Pierre Paleo's avatar
Pierre Paleo committed
353
354
355

@validator
def reconstruction_method_validator(val):
Pierre Paleo's avatar
Pierre Paleo committed
356
357
    return name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
358
        set(reconstruction_methods.values()),
Pierre Paleo's avatar
Pierre Paleo committed
359
360
361
        "reconstruction method",
        replacements=reconstruction_methods
    )
Pierre Paleo's avatar
Pierre Paleo committed
362
363
364
365
366

@validator
def fbp_filter_name_validator(val):
    return name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
367
        set(fbp_filters.values()),
Pierre Paleo's avatar
Pierre Paleo committed
368
        "FBP filter",
Pierre Paleo's avatar
Pierre Paleo committed
369
        replacements=fbp_filters,
Pierre Paleo's avatar
Pierre Paleo committed
370
371
    )

Pierre Paleo's avatar
Pierre Paleo committed
372

Pierre Paleo's avatar
Pierre Paleo committed
373
374
375
376
@validator
def iterative_method_name_validator(val):
    return name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
377
        set(iterative_methods.values()),
Pierre Paleo's avatar
Pierre Paleo committed
378
        "iterative methods name",
Pierre Paleo's avatar
Pierre Paleo committed
379
        replacements=iterative_methods
Pierre Paleo's avatar
Pierre Paleo committed
380
381
382
383
384
385
    )

@validator
def optimization_algorithm_name_validator(val):
    return name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
386
        set(optim_algorithms.values()),
Pierre Paleo's avatar
Pierre Paleo committed
387
        "optimization algorithm name",
Pierre Paleo's avatar
Pierre Paleo committed
388
        replacements=iterative_methods
Pierre Paleo's avatar
Pierre Paleo committed
389
390
391
392
393
394
    )

@validator
def output_file_format_validator(val):
    return name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
395
        set(files_formats.values()),
Pierre Paleo's avatar
Pierre Paleo committed
396
        "output file format",
Pierre Paleo's avatar
Pierre Paleo committed
397
        replacements=files_formats
Pierre Paleo's avatar
Pierre Paleo committed
398
399
400
401
    )

@validator
def distribution_method_validator(val):
402
    val = name_range_checker(
Pierre Paleo's avatar
Pierre Paleo committed
403
        val,
Pierre Paleo's avatar
Pierre Paleo committed
404
        set(distribution_methods.values()),
Pierre Paleo's avatar
Pierre Paleo committed
405
        "workload distribution method",
Pierre Paleo's avatar
Pierre Paleo committed
406
        replacements=distribution_methods
Pierre Paleo's avatar
Pierre Paleo committed
407
    )
408
409
410
411
    if val != "local":
        raise NotImplementedError("Computation method '%s' is not implemented yet" % val)
    return val

412
413
414
415
@validator
def sino_normalization_validator(val):
    val = name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
416
        set(sino_normalizations.values()),
417
418
419
420
        "sinogram normalization method",
        replacements=sino_normalizations
    )
    return val
421

422
@validator
Pierre Paleo's avatar
Pierre Paleo committed
423
def sino_deringer_methods(val):
424
425
    val = name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
426
        set(rings_methods.values()),
427
428
429
430
431
        "sinogram rings artefacts correction method",
        replacements=rings_methods,
    )
    return val

432
433
434
435
436
437
438
439
440
441
@validator
def list_of_int_validator(val):
    ids = val.replace(",", " ").split()
    res = list(map(convert_to_int, ids))
    err = list(filter(lambda x: x[1] is not None or x[0] < 0, res))
    if err != []:
        raise ValueError("Could not convert to a list of GPU IDs: %s" % val)
    return list(set(map(lambda x: x[0], res)))


Pierre Paleo's avatar
Pierre Paleo committed
442
443
444
@validator
def resources_validator(val):
    val = val.strip()
445
    is_percentage = False
Pierre Paleo's avatar
Pierre Paleo committed
446
    if "%" in val:
447
        is_percentage = True
Pierre Paleo's avatar
Pierre Paleo committed
448
        val = val.replace("%", "")
449
    val_float, conversion_error = convert_to_float(val)
Pierre Paleo's avatar
Typos    
Pierre Paleo committed
450
    assert conversion_error is None, str("Error while converting %s to float" % val)
451
    return (val_float, is_percentage)
Pierre Paleo's avatar
Pierre Paleo committed
452
453
454
455
456
457
458
459
460
461
462
463
464

@validator
def walltime_validator(val):
    # HH:mm:ss
    vals = val.strip().split(":")
    error_msg = "Invalid walltime format, expected HH:mm:ss"
    assert len(vals) == 3, error_msg
    hours, mins, secs = vals
    hours, err1 = convert_to_int(hours)
    mins, err2 = convert_to_int(mins)
    secs, err3 = convert_to_int(secs)
    assert err1 is None and err2 is None and err3 is None, error_msg
    err = (hours < 0 or mins < 0 or mins > 59 or secs < 0 or secs > 59)
465
    assert err is False, error_msg
Pierre Paleo's avatar
Pierre Paleo committed
466
467
468
    return hours, mins, secs


469
470
@validator
def nonempty_string_validator(val):
Pierre Paleo's avatar
Typos    
Pierre Paleo committed
471
    assert val != "", "Value cannot be empty"
472
473
    return val

Pierre Paleo's avatar
Pierre Paleo committed
474
475
@validator
def logging_validator(val):
Pierre Paleo's avatar
Pierre Paleo committed
476
477
    return name_range_checker(
        val,
Pierre Paleo's avatar
Pierre Paleo committed
478
        set(log_levels.values()),
Pierre Paleo's avatar
Pierre Paleo committed
479
480
481
        "logging level",
        replacements=log_levels
    )
Pierre Paleo's avatar
Pierre Paleo committed
482

483
484
485
@validator
def no_validator(val):
    return val
Pierre Paleo's avatar
Pierre Paleo committed
486