OIF File Uploading

Read Olympus(r) image files (OIF and OIB).

Copyright (c) 2012-2020, Christoph Gohlke

Oiffile is a Python library to read image and metadata from Olympus Image Format files. OIF is the native file format of the Olympus FluoView(tm) software for confocal microscopy.

There are two variants of the format:

  • OIF (Olympus Image File) is a multi-file format that includes a main setting file (.oif) and an associated directory with data and setting files (.tif, .bmp, .txt, .pyt, .roi, and .lut).

  • OIB (Olympus Image Binary) is a compound document file, storing OIF and associated files within a single file.

Author: Christoph Gohlke <https://www.lfd.uci.edu/~gohlke/>_

Organization: Laboratory for Fluorescence Dynamics. University of California, Irvine

License: BSD 3-Clause

Version: 2020.1.18

Requirements

  • CPython >= 3.6 <https://www.python.org>_
  • Numpy 1.14 <https://www.numpy.org>_
  • Tifffile 2019.1.1 <https://pypi.org/project/tifffile/>_

Revisions

2020.1.18 Fix indentation error. 2020.1.1 Support multiple image series. Parse shape and dtype from settings file. Remove support for Python 2.7 and 3.5. Update copyright.

Notes

The API is not stable yet and might change between revisions.

No specification document is available.

Tested only with files produced on Olympus FV1000 hardware.

Read the image from an OIB file as numpy array:

>>> image = OibImread('test.oib')
>>> image.shape
(3, 256, 256)
>>> image[:, 95, 216]
array([820,  50, 436], dtype=uint16)

Read the image from a single TIFF file in an OIB file:

>>> with OifFile('test.oib') as oib:
...     filename = natural_sorted(oib.glob('*.tif'))[0]
...     image = oib.asarray(filename)
>>> filename
'Storage00001/s_C001.tif'
>>> image[95, 216]
820

Access metadata and the OIB main file:

>>> with OifFile('test.oib') as oib:
...     oib.axes
...     oib.shape
...     oib.dtype
...     dataname = oib.mainfile['File Info']['DataName']
'CYX'
(3, 256, 256)
dtype('uint16')
>>> dataname
'Cell 1 mitoEGFP.oib'

Extract the OIB file content to an OIF file and associated data directory:

>>> tempdir = tempfile.mkdtemp()
>>> oib2oif('test.oib', location=tempdir)
Saving ... done.

Read the image from the extracted OIF file:

>>> image = imread(f'{tempdir}/{dataname[:-4]}.oif')
>>> image[:, 95, 216]
array([820,  50, 436], dtype=uint16)

Read OLE compound file and access the 'OibInfo.txt' settings file:

>>> with CompoundFile('test.oib') as com:
...     info = com.open_file('OibInfo.txt')
...     len(com.files())
14
>>> info = SettingsFile(info, 'OibInfo.txt')
>>> info['OibSaveInfo']['Version']
'2.0.0.0'

CompoundFile

Compound Document File.

A partial implementation of the "[MS-CFB] - v20120705, Compound File Binary File Format" specification by Microsoft Corporation.

This should be able to read Olympus OIB and Zeiss ZVI files.

Initialize instance from file.

Source code in src/domb/utils/oiffile.py
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
def __init__(self, filename):
    """Initialize instance from file."""
    self._fh = open(filename, 'rb')
    if self._fh.read(8) != b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1':
        self.close()
        raise ValueError('not a compound document file')
    (
        self.clsid,
        self.version_minor,
        self.version_major,
        byteorder,
        sector_shift,
        mini_sector_shift,
        _,
        _,
        self.dir_len,
        self.fat_len,
        self.dir_start,
        _,
        self.mini_stream_cutof_size,
        self.minifat_start,
        self.minifat_len,
        self.difat_start,
        self.difat_len,
    ) = struct.unpack('<16sHHHHHHIIIIIIIIII', self._fh.read(68))

    self.byteorder = {0xFFFE: '<', 0xFEFF: '>'}[byteorder]
    if self.byteorder != '<':
        raise NotImplementedError('big-endian byte order not supported')

    if self.clsid == b'\x00' * 16:
        self.clsid = None
    if self.clsid is not None:
        raise OifFileError(f'cannot handle clsid {self.clsid}')

    if self.version_minor != 0x3E:
        raise OifFileError(
            f'cannot handle version_minor {self.version_minor}')
    if mini_sector_shift != 0x0006:
        raise OifFileError(
            f'cannot handle mini_sector_shift {mini_sector_shift}')
    if not (
        (self.version_major == 0x4 and sector_shift == 0x000C) or
        (self.version_major == 0x3 and sector_shift == 0x0009 and
         self.dir_len == 0)
    ):
        raise OifFileError(
            f'cannot handle version_major {self.version_major} '
            f'and sector_shift {sector_shift}'
        )

    self.filename = filename
    self.sec_size = 2**sector_shift
    self.short_sec_size = 2**mini_sector_shift

    secfmt = '<' + ('I' * (self.sec_size // 4))
    # read DIFAT
    self._difat = list(
        struct.unpack('<' + ('I' * 109), self._fh.read(436))
    )
    nextsec = self.difat_start
    for i in range(self.difat_len):
        if nextsec >= CompoundFile.MAXREGSID:
            raise OifFileError('nextsec >= CompoundFile.MAXREGSID')
        sec = struct.unpack(secfmt, self._sec_read(nextsec))
        self._difat.extend(sec[:-1])
        nextsec = sec[-1]
    # if nextsec != CompoundFile.ENDOFCHAIN: raise OifFileError()
    self._difat = self._difat[:self.fat_len]
    # read FAT
    self._fat = []
    for secid in self._difat:
        self._fat.extend(struct.unpack(secfmt, self._sec_read(secid)))
    # read mini FAT
    self._minifat = []
    for i, sector in enumerate(self._sec_chain(self.minifat_start)):
        if i >= self.minifat_len:
            break
        self._minifat.extend(struct.unpack(secfmt, sector))
    # read directories
    self._dirs = []
    for sector in self._sec_chain(self.dir_start):
        for i in range(0, self.sec_size, 128):
            direntry = DirectoryEntry(sector[i:i+128], self.version_major)
            self._dirs.append(direntry)
    # read root storage
    if len(self._dirs) <= 0:
        raise OifFileError('no directories found')
    root = self._dirs[0]
    if root.name != 'Root Entry':
        raise OifFileError(
            f"no root directory found, got {root.name}")
    if root.create_time is not None:  # and root.modify_time is None
        raise OifFileError(f'invalid root.create_time {root.create_time}')
    if root.stream_size % self.short_sec_size != 0:
        raise OifFileError(
            f'root.stream_size {root.stream_size} does not match '
            f'short_sec_size {self.short_sec_size}'
        )
    # read mini stream
    self._ministream = b''.join(self._sec_chain(root.sector_start))
    self._ministream = self._ministream[:root.stream_size]
    # map stream/file names to directory entries
    nostream = CompoundFile.NOSTREAM
    join = '/'.join  # os.path.sep.join
    dirs = self._dirs
    visited = [False] * len(self._dirs)

    def parse(dirid, path):
        # return iterator over all file names and their directory entries
        # TODO: replace with red-black tree parser
        if dirid == nostream or visited[dirid]:
            return
        visited[dirid] = True
        de = dirs[dirid]
        if de.is_stream:
            yield join(path + [de.name]), de
        yield from parse(de.left_sibling_id, path)
        yield from parse(de.right_sibling_id, path)
        if de.is_storage:
            yield from parse(de.child_id, path + [de.name])

    self._files = dict(parse(self._dirs[0].child_id, []))

__str__

Return string with information about CompoundFile.

src/domb/utils/oiffile.py
742
743
744
745
746
747
748
749
750
751
752
753
754
def __str__(self):
    """Return string with information about CompoundFile."""
    s = [
        self.__class__.__name__,
        os.path.normpath(os.path.normcase(self.filename)),
    ]
    for attr in (
        'clsid', 'version_minor', 'version_major', 'byteorder',
        'dir_len', 'fat_len', 'dir_start', 'mini_stream_cutof_size',
        'minifat_start', 'minifat_len', 'difat_start', 'difat_len'
    ):
        s.append(f'{attr}: {getattr(self, attr)}')
    return '\n '.join(s)

close

Close file handle.

src/domb/utils/oiffile.py
732
733
734
def close(self):
    """Close file handle."""
    self._fh.close()

direntry

Return DirectoryEntry of filename.

src/domb/utils/oiffile.py
720
721
722
def direntry(self, name):
    """Return DirectoryEntry of filename."""
    return self._files[name]

files

Return sequence of file names.

src/domb/utils/oiffile.py
716
717
718
def files(self):
    """Return sequence of file names."""
    return self._files.keys()

format_tree

Return formatted string with list of all files.

src/domb/utils/oiffile.py
728
729
730
def format_tree(self):
    """Return formatted string with list of all files."""
    return '\n'.join(natural_sorted(self.files()))

open_file

Return stream as file like object.

src/domb/utils/oiffile.py
724
725
726
def open_file(self, filename):
    """Return stream as file like object."""
    return BytesIO(self._read_stream(self._files[filename]))

DirectoryEntry

Compound Document Directory Entry.

Initialize directory entry from 128 bytes.

Source code in src/domb/utils/oiffile.py
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
def __init__(self, data, version_major):
    """Initialize directory entry from 128 bytes."""
    (
        name,
        name_len,
        self.entry_type,
        self.color,
        self.left_sibling_id,
        self.right_sibling_id,
        self.child_id,
        self.clsid,
        self.user_flags,
        create_time,
        modify_time,
        self.sector_start,
        self.stream_size,
    ) = struct.unpack('<64sHBBIII16sIQQIQ', data)

    if version_major == 3:
        self.stream_size = struct.unpack('<I', data[-8:-4])[0]
    if self.clsid == b'\000' * 16:
        self.clsid = None

    if name_len % 2 != 0 or name_len > 64:
        raise OifFileError(f'invalid name_len {name_len}')
    if self.color not in (0, 1):
        raise OifFileError(f'invalid color {self.color}')

    self.name = name[:name_len-2].decode('utf-16')
    self.create_time = filetime(create_time)
    self.modify_time = filetime(modify_time)
    self.is_stream = self.entry_type == 2
    self.is_storage = self.entry_type == 1

__str__

Return string with information about DirectoryEntry.

src/domb/utils/oiffile.py
800
801
802
803
804
805
def __str__(self):
    """Return string with information about DirectoryEntry."""
    s = [self.__class__.__name__]
    for attr in self.__slots__[1:]:
        s.append(f'{attr}: {getattr(self, attr)}')
    return '\n '.join(s)

OibFileSystem

Olympus Image Binary file system.

Open compound document and read OibInfo.txt settings.

Source code in src/domb/utils/oiffile.py
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
def __init__(self, filename):
    """Open compound document and read OibInfo.txt settings."""
    self.filename = filename
    self.com = CompoundFile(filename)
    info = SettingsFile(self.com.open_file('OibInfo.txt'),
                        'OibInfo.txt')['OibSaveInfo']
    self.name = info.get('Name', None)
    self.version = info.get('Version', None)
    self.compression = info.get('Compression', None)
    self.mainfile = info[info['MainFileName']]
    # map OIB file names to CompoundFile file names
    oibfiles = {os.path.split(i)[-1]: i for i in self.com.files()}
    self._files = {v: oibfiles[k] for k, v in info.items()
                   if k.startswith('Stream')}
    # map storage names to directory names
    self._folders = {i[0]: i[1] for i in info.items()
                     if i[0].startswith('Storage')}
    # read main settings file
    self.settings = SettingsFile(self.open_file(self.mainfile),
                                 name=self.mainfile)

__str__

Return string with information about OibFileSystem.

src/domb/utils/oiffile.py
457
458
459
460
461
462
463
464
465
466
def __str__(self):
    """Return string with information about OibFileSystem."""
    return '\n '.join((
        self.__class__.__name__,
        os.path.normpath(os.path.normcase(self.filename)),
        f'name: {self.name}',
        f'version: {self.version}',
        f'mainfile: {self.mainfile}',
        f'compression: {self.compression}',
    ))

close

Close file handle.

src/domb/utils/oiffile.py
447
448
449
def close(self):
    """Close file handle."""
    self.com.close()

files

Return iterator over unsorted files in OIB.

src/domb/utils/oiffile.py
409
410
411
def files(self):
    """Return iterator over unsorted files in OIB."""
    return iter(self._files.keys())

open_file

Return file object from case sensitive path name.

src/domb/utils/oiffile.py
402
403
404
405
406
407
def open_file(self, filename):
    """Return file object from case sensitive path name."""
    try:
        return self.com.open_file(self._files[filename])
    except KeyError as exc:
        raise FileNotFoundError(f'No such file: {filename}') from exc

saveas_oif

Save all streams in OIB file as separate files.

Raise OSError if target files or directories already exist.

The main .oif file name and storage names are determined from the OibInfo.txt settings.

src/domb/utils/oiffile.py
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
def saveas_oif(self, location='', verbose=0):
    """Save all streams in OIB file as separate files.

    Raise OSError if target files or directories already exist.

    The main .oif file name and storage names are determined from the
    OibInfo.txt settings.

    """
    if location and not os.path.exists(location):
        os.makedirs(location)
    mainfile = os.path.join(location, self.mainfile)
    if os.path.exists(mainfile):
        raise FileExistsError(mainfile + ' already exists')
    for folder in self._folders.keys():
        folder = os.path.join(location, self._folders.get(folder, ''))
        if os.path.exists(folder):
            raise FileExistsError(folder + ' already exists')
        os.makedirs(folder)
    if verbose:
        print('Saving', mainfile, end=' ')
    for f in self._files.keys():
        folder, name = os.path.split(f)
        folder = os.path.join(location, self._folders.get(folder, ''))
        path = os.path.join(folder, name)
        if verbose == 1:
            print(end='.')
        elif verbose > 1:
            print(path)
        with open(path, 'w+b') as fh:
            fh.write(self.open_file(f).read())
    if verbose == 1:
        print(' done.')

OifFile

Olympus Image File.

Attributes:
  • mainfile (SettingsFile) –

    The main OIF settings.

  • filesystem (OibFileSystem or OifFileSystem) –

    The underlying file system instance.

  • series (tuple of tifffile.TiffSequence) –

    Sequence of TIFF files. Includes shape, dtype, and axes information.

  • is_oib (bool) –

    True if OIB file.

Open OIF or OIB file and read main settings.

Source code in src/domb/utils/oiffile.py
202
203
204
205
206
207
208
209
210
211
212
213
214
def __init__(self, filename):
    """Open OIF or OIB file and read main settings."""
    self.filename = filename
    if filename.lower().endswith('.oif'):
        self.filesystem = OifFileSystem(filename)
        self.is_oib = False
    else:
        self.filesystem = OibFileSystem(filename)
        self.is_oib = True
    self.mainfile = self.filesystem.settings
    # map file names to storage names (flattened name space)
    self._files_flat = {os.path.basename(f): f
                        for f in self.filesystem.files()}

axes property

Return order of axes in image data from mainfile.

dtype property

Return dtype of image data from mainfile.

shape property

Return shape of image data from mainfile.

tiffs property

Return first TiffSequence.

__str__

Return string with information about OifFile.

src/domb/utils/oiffile.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
def __str__(self):
    """Return string with information about OifFile."""
    # info = self.mainfile['Version Info']
    s = [
        self.__class__.__name__,
        os.path.normpath(os.path.normcase(self.filename)),
        f'axes: {self.axes}',
        'shape: {}'.format(', '.join(str(i) for i in self.shape)),
        f'dtype: {self.dtype}',
        # f'system name: {info.get("SystemName", "None")}',
        # f'system version: {info.get("SystemVersion", "None")}',
        # f'file version: {info.get("FileVersion", "None")}',
    ]
    if len(self.series) > 1:
        s.append(f'series: {len(self.series)}')
    return '\n '.join(s)

asarray

Return image data from TIFF file(s) as numpy array.

By default the data from the TIFF files in the first image series is returned.

The kwargs parameters are passed to the asarray functions of the TiffFile or TiffSequence instances.

src/domb/utils/oiffile.py
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def asarray(self, series=0, **kwargs):
    """Return image data from TIFF file(s) as numpy array.

    By default the data from the TIFF files in the first image series
    is returned.

    The kwargs parameters are passed to the asarray functions of the
    TiffFile or TiffSequence instances.

    """
    if isinstance(series, int):
        return self.series[series].asarray(**kwargs)
    with TiffFile(self.open_file(series), name=series) as tif:
        result = tif.asarray(**kwargs)
    return result

close

Close file handle.

src/domb/utils/oiffile.py
295
296
297
def close(self):
    """Close file handle."""
    self.filesystem.close()

glob

Return iterator over unsorted file names matching pattern.

src/domb/utils/oiffile.py
224
225
226
227
228
229
230
def glob(self, pattern='*'):
    """Return iterator over unsorted file names matching pattern."""
    if pattern == '*':
        return self.filesystem.files()
    pattern = pattern.replace('.', r'\.').replace('*', '.*')
    pattern = re.compile(pattern)
    return (f for f in self.filesystem.files() if pattern.match(f))

open_file

Return open file object from path name.

src/domb/utils/oiffile.py
216
217
218
219
220
221
222
def open_file(self, filename):
    """Return open file object from path name."""
    try:
        return self.filesystem.open_file(
            self._files_flat.get(filename, filename))
    except (KeyError, OSError) as exc:
        raise FileNotFoundError(f'No such file: {filename}') from exc

series

Return tuple of TiffSequence of sorted TIFF files.

src/domb/utils/oiffile.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
@lazyattr
def series(self):
    """Return tuple of TiffSequence of sorted TIFF files."""
    series = {}
    for fname in self.glob('*.tif'):
        key = ''.join(c for c in os.path.split(fname)[-1][:-4]
                      if c.isalpha())
        if key in series:
            series[key].append(fname)
        else:
            series[key] = [fname]
    series = [TiffSequence(natural_sorted(files),
                           imread=self.asarray, pattern='axes')
              for files in series.values()]
    if len(series) > 1:
        series = tuple(reversed(sorted(series,
                                       key=lambda x: len(x.files))))
    return series

OifFileError

Bases: Exception

Exception to raise issues with OIF or OIB structure.

OifFileSystem

Olympus Image File file system.

Open OIF file and read settings.

Source code in src/domb/utils/oiffile.py
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def __init__(self, filename, storage_ext='.files'):
    """Open OIF file and read settings."""
    self.filename = filename
    self._path, self.mainfile = os.path.split(os.path.abspath(filename))
    self.settings = SettingsFile(filename, name=self.mainfile)
    self.name = self.settings['ProfileSaveInfo']['Name']
    self.version = self.settings['ProfileSaveInfo']['Version']
    # check that storage directory exists
    storage = os.path.join(self._path, self.mainfile + storage_ext)
    if not os.path.exists(storage) or not os.path.isdir(storage):
        raise OSError(
            f'OIF storage path not found: {self.mainfile}{storage_ext}')
    # list all files
    pathlen = len(self._path + os.path.sep)
    self._files = [self.mainfile]
    for f in glob(os.path.join(storage, '*')):
        self._files.append(f[pathlen:])

__str__

Return string with information about OifFileSystem.

src/domb/utils/oiffile.py
367
368
369
370
371
372
373
374
375
def __str__(self):
    """Return string with information about OifFileSystem."""
    return '\n '.join((
        self.__class__.__name__,
        os.path.normpath(os.path.normcase(self.filename)),
        f'name: {self.name}',
        f'version: {self.version}',
        f'mainfile: {self.mainfile}',
    ))

close

Close file handle.

src/domb/utils/oiffile.py
358
359
def close(self):
    """Close file handle."""

files

Return iterator over unsorted files in OIF.

src/domb/utils/oiffile.py
348
349
350
def files(self):
    """Return iterator over unsorted files in OIF."""
    return iter(self._files)

glob

Return iterator of path names matching the specified pattern.

src/domb/utils/oiffile.py
352
353
354
355
356
def glob(self, pattern):
    """Return iterator of path names matching the specified pattern."""
    pattern = pattern.replace('.', r'\.').replace('*', '.*')
    pattern = re.compile(pattern)
    return (f for f in self.files() if pattern.match(f))

open_file

Return file object from path name.

src/domb/utils/oiffile.py
344
345
346
def open_file(self, filename):
    """Return file object from path name."""
    return open(os.path.join(self._path, filename), 'rb')

SettingsFile

Bases: dict

Olympus settings file (oif, txt, pyt, roi, lut).

Settings files contain little endian utf-16 encoded strings, except for [ColorLUTData] sections, which contain uint8 binary arrays.

Settings can be accessed as a nested dictionary {section: {key: value}}, except for {'ColorLUTData': numpy array}.

Read settings file and parse into nested dictionaries.

Parameters:
  • arg (str or file object) –

    Name of file or open file containing little endian UTF-16 string. File objects are closed by this function.

  • name (str, default: None ) –

    Human readable label of stream.

Source code in src/domb/utils/oiffile.py
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
def __init__(self, arg, name=None):
    """Read settings file and parse into nested dictionaries.

    Parameters
    ----------
    arg : str or file object
        Name of file or open file containing little endian UTF-16 string.
        File objects are closed by this function.
    name : str
        Human readable label of stream.

    """
    dict.__init__(self)
    if isinstance(arg, str):
        self.name = arg
        stream = open(arg, 'rb')
    else:
        self.name = str(name)
        stream = arg

    try:
        content = stream.read()
    finally:
        stream.close()

    if content[:4] == b'\xFF\xFE\x5B\x00':
        # UTF16 BOM
        content = content.rsplit(
            b'[\x00C\x00o\x00l\x00o\x00r\x00L\x00U\x00T\x00'
            b'D\x00a\x00t\x00a\x00]\x00\x0D\x00\x0A\x00', 1)
        if len(content) > 1:
            self['ColorLUTData'] = numpy.fromstring(
                content[1], 'uint8').reshape(-1, 4)
        content = content[0].decode('utf-16')
    elif content[:1] == b'[':
        # try UTF-8
        content = content.rsplit(b'[ColorLUTData]\r\n', 1)
        if len(content) > 1:
            self['ColorLUTData'] = numpy.fromstring(
                content[1], 'uint8').reshape(-1, 4)
        try:
            content = content[0].decode('utf-8')
        except Exception as exc:
            raise ValueError('not a valid settings file') from exc
    else:
        raise ValueError('not a valid settings file')

    for line in content.splitlines():
        line = line.strip()
        if line.startswith(';'):
            continue
        if line.startswith('[') and line.endswith(']'):
            self[line[1:-1]] = properties = {}
        else:
            key, value = line.split('=')
            properties[key] = astype(value)

__str__

Return string with information about SettingsFile.

src/domb/utils/oiffile.py
537
538
539
def __str__(self):
    """Return string with information about SettingsFile."""
    return '\n '.join((self.__class__.__name__, format_dict(self)))

OibImread

Return image data from OIF or OIB file as numpy array.

'args' and 'kwargs' are arguments to OifFile.asarray().

src/domb/utils/oiffile.py
165
166
167
168
169
170
171
172
173
def OibImread(filename, *args, **kwargs):
    """Return image data from OIF or OIB file as numpy array.

    'args' and 'kwargs' are arguments to OifFile.asarray().

    """
    with OifFile(filename) as oif:
        result = oif.asarray(*args, **kwargs)
    return result

astype

Return argument as one of types if possible.

src/domb/utils/oiffile.py
826
827
828
829
830
831
832
833
834
835
836
837
def astype(value, types=None):
    """Return argument as one of types if possible."""
    if value[0] in '\'"':
        return value[1:-1]
    if types is None:
        types = int, float, str
    for typ in types:
        try:
            return typ(value)
        except (ValueError, TypeError, UnicodeEncodeError):
            pass
    return value

filetime

Return Python datetime from Microsoft FILETIME number.

src/domb/utils/oiffile.py
840
841
842
843
844
845
def filetime(ft):
    """Return Python datetime from Microsoft FILETIME number."""
    if not ft:
        return None
    sec, nsec = divmod(ft - 116444736000000000, 10000000)
    return datetime.utcfromtimestamp(sec).replace(microsecond=(nsec // 10))

format_dict

Return pretty-print of nested dictionary.

src/domb/utils/oiffile.py
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
def format_dict(adict, prefix=' ', indent=' ', bullets=('', ''),
                excludes=('_', ), linelen=79, trim=1):
    """Return pretty-print of nested dictionary."""
    result = []
    for k, v in sorted(adict.items(), key=lambda x: str(x[0]).lower()):
        if any(k.startswith(e) for e in excludes):
            continue
        if isinstance(v, dict):
            v = '\n' + format_dict(v, prefix=prefix+indent, excludes=excludes,
                                   trim=0)
            result.append(f'{prefix}{bullets[1]}{k}: {v}')
        else:
            result.append((f'{prefix}{bullets[0]}{k}: {v}')[:linelen].rstrip())
    if trim > 0:
        result[0] = result[0][trim:]
    return '\n'.join(result)

oib2oif

Convert OIB file to OIF.

src/domb/utils/oiffile.py
176
177
178
179
def oib2oif(filename, location='', verbose=1):
    """Convert OIB file to OIF."""
    with OibFileSystem(filename) as oib:
        oib.saveas_oif(location=location, verbose=verbose)