|
10 | 10 | Functions: |
11 | 11 |
|
12 | 12 | .. autofunction:: neo.io.get_io |
| 13 | +.. autofunction:: neo.io.list_candidate_ios |
13 | 14 |
|
14 | 15 |
|
15 | 16 | Classes: |
|
262 | 263 |
|
263 | 264 | """ |
264 | 265 |
|
265 | | -import os.path |
| 266 | +import pathlib |
| 267 | +from collections import Counter |
266 | 268 |
|
267 | 269 | # try to import the neuroshare library. |
268 | 270 | # if it is present, use the neuroshareapiio to load neuroshare files |
|
350 | 352 | CedIO, |
351 | 353 | EDFIO, |
352 | 354 | ElanIO, |
353 | | - # ElphyIO, |
| 355 | + ElphyIO, |
354 | 356 | ExampleIO, |
355 | 357 | IgorIO, |
356 | 358 | IntanIO, |
|
359 | 361 | MEArecIO, |
360 | 362 | MaxwellIO, |
361 | 363 | MicromedIO, |
362 | | - NixIO, # place NixIO before other IOs that use HDF5 to make it the default for .h5 files |
| 364 | + NixIO, |
| 365 | + NixIOFr, |
363 | 366 | NeoMatlabIO, |
364 | 367 | NestIO, |
365 | 368 | NeuralynxIO, |
|
384 | 387 | WinWcpIO |
385 | 388 | ] |
386 | 389 |
|
| 390 | +# for each supported extension list the ios supporting it |
| 391 | +io_by_extension = {} |
| 392 | +for current_io in iolist: # do not use `io` as variable name here as this overwrites the module io |
| 393 | + for extension in current_io.extensions: |
| 394 | + extension = extension.lower() |
| 395 | + # extension handling should not be case sensitive |
| 396 | + io_by_extension.setdefault(extension, []).append(current_io) |
387 | 397 |
|
388 | | -def get_io(filename, *args, **kwargs): |
| 398 | + |
| 399 | +def get_io(file_or_folder, *args, **kwargs): |
389 | 400 | """ |
390 | 401 | Return a Neo IO instance, guessing the type based on the filename suffix. |
391 | 402 | """ |
392 | | - extension = os.path.splitext(filename)[1][1:] |
393 | | - for io in iolist: |
394 | | - if extension in io.extensions: |
395 | | - return io(filename, *args, **kwargs) |
| 403 | + ios = list_candidate_ios(file_or_folder) |
| 404 | + for io in ios: |
| 405 | + try: |
| 406 | + return io(file_or_folder, *args, **kwargs) |
| 407 | + except: |
| 408 | + continue |
| 409 | + |
| 410 | + raise IOError(f"Could not identify IO for {file_or_folder}") |
| 411 | + |
396 | 412 |
|
397 | | - raise IOError("File extension %s not registered" % extension) |
| 413 | +def list_candidate_ios(file_or_folder, ignore_patterns=['*.ini', 'README.txt', 'README.md']): |
| 414 | + """ |
| 415 | + Identify neo IO that can potentially load data in the file or folder |
| 416 | +
|
| 417 | + Parameters |
| 418 | + ---------- |
| 419 | + file_or_folder (str, pathlib.Path) |
| 420 | + Path to the file or folder to load |
| 421 | + ignore_patterns (list) |
| 422 | + List of patterns to ignore when scanning for known formats. See pathlib.PurePath.match(). |
| 423 | + Default: ['ini'] |
| 424 | +
|
| 425 | + Returns |
| 426 | + ------- |
| 427 | + list |
| 428 | + List of neo io classes that are associated with the file extensions detected |
| 429 | + """ |
| 430 | + file_or_folder = pathlib.Path(file_or_folder) |
| 431 | + |
| 432 | + if file_or_folder.is_file(): |
| 433 | + suffix = file_or_folder.suffix[1:].lower() |
| 434 | + if suffix not in io_by_extension: |
| 435 | + raise ValueError(f'{suffix} is not a supported format of any IO.') |
| 436 | + return io_by_extension[suffix] |
| 437 | + |
| 438 | + elif file_or_folder.is_dir(): |
| 439 | + # scan files in folder to determine io type |
| 440 | + filenames = [f for f in file_or_folder.glob('*') if f.is_file()] |
| 441 | + # keep only relevant filenames |
| 442 | + filenames = [f for f in filenames if f.suffix and not any([f.match(p) for p in ignore_patterns])] |
| 443 | + |
| 444 | + # if no files are found in the folder, check subfolders |
| 445 | + # this is necessary for nested-folder based formats like spikeglx |
| 446 | + if not filenames: |
| 447 | + filenames = [f for f in file_or_folder.glob('**/*') if f.is_file()] |
| 448 | + # keep only relevant filenames |
| 449 | + filenames = [f for f in filenames if f.suffix and not any([f.match(p) for p in ignore_patterns])] |
| 450 | + |
| 451 | + # if only file prefix was provided, e.g /mydatafolder/session1- |
| 452 | + # to select all files sharing the `session1-` prefix |
| 453 | + elif file_or_folder.parent.exists(): |
| 454 | + filenames = file_or_folder.parent.glob(file_or_folder.name + '*') |
| 455 | + |
| 456 | + else: |
| 457 | + raise ValueError(f'{file_or_folder} does not contain data files of a supported format') |
| 458 | + |
| 459 | + # find the io that fits the best with the files contained in the folder |
| 460 | + potential_ios = [] |
| 461 | + for filename in filenames: |
| 462 | + for suffix in filename.suffixes: |
| 463 | + suffix = suffix[1:].lower() |
| 464 | + if suffix in io_by_extension: |
| 465 | + potential_ios.extend(io_by_extension[suffix]) |
| 466 | + |
| 467 | + if not potential_ios: |
| 468 | + raise ValueError(f'Could not determine IO to load {file_or_folder}') |
| 469 | + |
| 470 | + # return ios ordered by number of files supported |
| 471 | + counter = Counter(potential_ios).most_common() |
| 472 | + return [io for io, count in counter] |
0 commit comments