micress-micpy 0.2.16b0__py3-none-any.whl → 0.3.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
micpy/bin.py CHANGED
@@ -1,12 +1,11 @@
1
- """This module provides methods to read and write binary files."""
1
+ """The `micpy.bin` module provides methods to read and write binary files."""
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Callable, IO, List, Tuple, Generator
4
+ from typing import Callable, Generator, IO, List, Optional, Tuple, Union
5
5
 
6
6
  import gzip
7
7
  import os
8
8
  import sys
9
- import warnings
10
9
  import zlib
11
10
 
12
11
  import numpy as np
@@ -16,7 +15,7 @@ from micpy import utils
16
15
  from micpy.matplotlib import matplotlib, pyplot
17
16
 
18
17
 
19
- __all__ = ["File"]
18
+ __all__ = ["File", "Field", "Series", "plot", "PlotArgs"]
20
19
 
21
20
 
22
21
  @dataclass
@@ -263,9 +262,9 @@ class Index(List[Position]):
263
262
  class Field(np.ndarray):
264
263
  """A field."""
265
264
 
266
- def __new__(cls, data, time: float, spacing: float = None):
265
+ def __new__(cls, data, time: float, spacing: Tuple[float, float, float]):
267
266
  obj = np.asarray(data).view(cls)
268
- obj.time = round(float(time), 7)
267
+ obj.time = time
269
268
  obj.spacing = spacing
270
269
  return obj
271
270
 
@@ -353,221 +352,100 @@ class Field(np.ndarray):
353
352
 
354
353
  return Field.from_bytes(field_data, shape=shape, spacing=spacing)
355
354
 
356
- def write(self, file: IO[bytes]):
357
- """Write the field to a binary file."""
358
- file.write(self.to_bytes())
355
+ def to_file(self, file: IO[bytes], geometry: bool = True):
356
+ """Write the field to a binary file.
359
357
 
360
- def get_slice(self, plane: str, slice_id: int):
361
- """Get a slice of the field."""
358
+ Args:
359
+ file (IO[bytes]): Binary file.
360
+ geometry (bool, optional): `True` if geometry should be written, `False`
361
+ otherwise. Defaults to `True`.
362
+ """
362
363
 
363
- if plane not in ["xy", "xz", "yz", "zy", "zx", "yx"]:
364
- raise ValueError("Invalid plane")
364
+ file.write(self.to_bytes())
365
365
 
366
- dimensions = Field.dimensions(self.shape)
366
+ if geometry:
367
+ geo_filename, geo_data = geo.build(file.name, self.shape, self.spacing)
368
+ geo.write(geo_filename, geo_data, geo.Type.BASIC)
367
369
 
368
- x, _, z = self.shape
369
-
370
- reshape_operations = {
371
- 1: lambda data: data.reshape((z, 1)),
372
- 2: lambda data: data.reshape((z, x)),
373
- 3: lambda data: data,
374
- }
375
-
376
- data = reshape_operations[dimensions](self)
377
-
378
- slice_operations = {
379
- 1: {
380
- "xz": lambda data: data,
381
- "zx": lambda data: data.T,
382
- "xy": lambda data: data[slice_id : slice_id + 1],
383
- "yx": lambda data: data[slice_id : slice_id + 1],
384
- "zy": lambda data: data.T,
385
- "yz": lambda data: data,
386
- },
387
- 2: {
388
- "xz": lambda data: data,
389
- "zx": lambda data: data.T,
390
- "xy": lambda data: data[slice_id : slice_id + 1],
391
- "yx": lambda data: data[slice_id : slice_id + 1].T,
392
- "zy": lambda data: data.T[slice_id : slice_id + 1],
393
- "yz": lambda data: data.T[slice_id : slice_id + 1].T,
394
- },
395
- 3: {
396
- "xy": lambda data: data[slice_id, :, :],
397
- "yx": lambda data: data[slice_id, :, :].T,
398
- "xz": lambda data: data[:, slice_id, :],
399
- "zx": lambda data: data[:, slice_id, :].T,
400
- "yz": lambda data: data[:, :, slice_id],
401
- "zy": lambda data: data[:, :, slice_id].T,
402
- },
403
- }
404
-
405
- return slice_operations[dimensions][plane](data)
406
-
407
- # pylint: disable=too-many-arguments
408
- def plot(
370
+ def write(
409
371
  self,
410
- plane: str = "xz",
411
- slice_id: int = 0,
412
- title: str = None,
413
- xlabel: str = None,
414
- ylabel: str = None,
415
- figsize: Tuple[float, float] = None,
416
- dpi: int = None,
417
- aspect: str = "equal",
418
- ax: "matplotlib.Axes" = None,
419
- cax: "matplotlib.Axes" = None,
420
- vmin: float = None,
421
- vmax: float = None,
422
- cmap: str = "micpy",
372
+ filename: str,
373
+ compressed: bool = True,
374
+ geometry: bool = True,
423
375
  ):
424
- """Plot a slice of the field.
376
+ """Write the field to a binary file.
425
377
 
426
378
  Args:
427
- plane (str, optional): Plane of the slice. Defaults to "xz".
428
- slice_id (int, optional): Slice ID. Defaults to 0.
429
- title (str, optional): Title of the plot. Defaults to None.
430
- xlabel (str, optional): Label of the x-axis. Defaults to None (auto).
431
- ylabel (str, optional): Label of the y-axis. Defaults to None (auto).
432
- figsize (Tuple[float, float], optional): Figure size. Defaults to None (auto).
433
- dpi (int, optional): Figure DPI. Defaults to None (auto).
434
- aspect (str, optional): Aspect ratio. Defaults to "equal".
435
- ax (Axes, optional): Axes to plot on. Defaults to None.
436
- cax (Axes, optional): Axes to plot color bar on. Defaults to None.
437
- vmin (float, optional): Minimum value of the color bar. Defaults to None.
438
- vmax (float, optional): Maximum value of the color bar. Defaults to None.
439
- cmap (str, optional): Color map. Defaults to "micpy".
440
-
441
- Returns:
442
- Tuple[Figure, Axes]: Figure and axes of the plot.
379
+ filename (str): Filename of the binary file.
380
+ compressed (bool, optional): `True` if file should be compressed, `False`
381
+ otherwise. Defaults to `True`.
382
+ geometry (bool, optional): `True` if geometry should be written, `False`
383
+ otherwise. Defaults to `True`.
443
384
  """
444
- if matplotlib is None:
445
- raise ImportError("matplotlib is not installed")
446
-
447
- slice = self.get_slice(plane, slice_id) if self.ndim == 3 else self
448
-
449
- fig, ax = (
450
- pyplot.subplots(figsize=figsize, dpi=dpi)
451
- if ax is None
452
- else (ax.get_figure(), ax)
453
- )
454
-
455
- if title is not None:
456
- ax.set_title(title)
457
- else:
458
- ax.set_title(f"t={np.round(self.time, 7)}s")
459
- if xlabel is not None:
460
- ax.set_xlabel(xlabel)
461
- else:
462
- ax.set_xlabel(plane[0])
463
- if ylabel is not None:
464
- ax.set_ylabel(ylabel)
465
- else:
466
- ax.set_ylabel(plane[1])
467
- if aspect is not None:
468
- ax.set_aspect(aspect)
469
- ax.set_frame_on(False)
470
-
471
- mesh = ax.pcolormesh(slice, cmap=cmap, vmin=vmin, vmax=vmax)
472
-
473
- bar = pyplot.colorbar(mesh, ax=ax, cax=cax)
474
- bar.locator = matplotlib.ticker.MaxNLocator(
475
- integer=np.issubdtype(slice.dtype, np.integer)
476
- )
477
- bar.outline.set_visible(False)
478
- bar.update_ticks()
479
-
480
- return fig, ax
481
-
482
385
 
483
- class FieldList(List[Field]):
484
- """A list of fields."""
485
-
486
- def __init__(self, fields: List[Field] = None):
487
- super().__init__(fields if fields else [])
488
-
489
- def append(self, field: Field):
490
- """Append a field to the list."""
491
- super().append(field)
492
-
493
- def extend(self, fields: List[Field]):
494
- """Extend the list with fields."""
495
- super().extend(fields)
496
-
497
- def write(self, filename: str, compressed: bool = True, write_geo: bool = True):
498
- """Write the fields to a binary file."""
499
386
  file_open = gzip.open if compressed else open
500
387
  with file_open(filename, "wb") as file:
501
- for field in self:
502
- field.write(file)
388
+ self.to_file(file, geometry)
503
389
 
504
- if write_geo:
505
- geo_filename, geo_data, geo_type = geo.get_basic(
506
- filename, self[0].shape, self[0].spacing
507
- )
508
- geo.write(geo_filename, geo_data, geo_type, compressed=compressed)
509
390
 
510
- # pylint: disable=too-many-arguments
511
- def plot(
512
- self,
513
- cols: int = None,
514
- normalize: bool = False,
515
- sharex: bool = False,
516
- sharey: bool = False,
517
- figsize: Tuple[float, float] = None,
518
- dpi: int = None,
519
- **kwargs,
520
- ):
521
- """Plot the fields in a grid.
391
+ class Series:
392
+ def __init__(self, fields: List[Field]):
393
+ self.stack = np.stack(fields)
394
+ self.times = [field.time for field in fields]
395
+ self.spacings = [field.spacing for field in fields]
522
396
 
523
- Args:
524
- cols (int, optional): Number of columns. Defaults to None (auto).
525
- normalize (bool, optional): Normalize the color bar. Defaults to False.
526
- sharex (bool, optional): Share x-axis. Defaults to False.
527
- sharey (bool, optional): Share y-axis. Defaults to False.
528
- figsize (Tuple[float, float], optional): Figure size. Defaults to None (auto).
529
- dpi (int, optional): Figure DPI. Defaults to None (auto).
530
- **kwargs: Keyword arguments passed to Field.plot().
397
+ def __array__(self):
398
+ return self.stack
531
399
 
532
- Returns:
533
- Tuple[Figure, Axes]: Figure and axes of the plot.
534
- """
535
- if matplotlib is None:
536
- raise ImportError("matplotlib is not installed")
400
+ def __len__(self):
401
+ return len(self.stack)
537
402
 
538
- if cols is None:
539
- cols = int(np.ceil(np.sqrt(len(self))))
540
-
541
- rows = (len(self) + cols - 1) // cols
403
+ def __iter__(self):
404
+ for item, time, spacing in zip(self.stack, self.times, self.spacings):
405
+ yield Field(item, time, spacing)
406
+
407
+ def __getitem__(self, index):
408
+ if isinstance(index, int):
409
+ return Field(self.stack[index], self.times[index], self.spacings[index])
410
+ elif isinstance(index, slice):
411
+ return Series(
412
+ [
413
+ Field(item, time, spacing)
414
+ for item, time, spacing in zip(
415
+ self.stack[index], self.times[index], self.spacings[index]
416
+ )
417
+ ]
418
+ )
419
+ raise TypeError("Invalid argument type")
542
420
 
543
- fig, ax = pyplot.subplots(
544
- rows, cols, figsize=figsize, dpi=dpi, sharex=sharex, sharey=sharey
545
- )
421
+ def __getattr__(self, name):
422
+ return getattr(self.stack, name)
546
423
 
547
- vmin = (
548
- min(field.min() for field in self)
549
- if normalize
550
- else kwargs.pop("vmin", None)
551
- )
552
- vmax = (
553
- max(field.max() for field in self)
554
- if normalize
555
- else kwargs.pop("vmax", None)
556
- )
424
+ def __dir__(self) -> List[str]:
425
+ return list(set(dir(type(self)) + dir(self.stack)))
557
426
 
558
- flat_ax = ax.flatten() if isinstance(ax, np.ndarray) else [ax]
427
+ def __repr__(self):
428
+ return repr(self.stack)
559
429
 
560
- for i, field in enumerate(self):
561
- field.plot(ax=flat_ax[i], vmin=vmin, vmax=vmax, **kwargs)
430
+ def __str__(self):
431
+ return str(self.stack)
562
432
 
563
- for i in range(len(self), len(flat_ax)):
564
- fig.delaxes(flat_ax[i])
433
+ def write(self, filename: str, compressed: bool = True, geometry: bool = True):
434
+ """Write the series to a binary file.
565
435
 
566
- with warnings.catch_warnings():
567
- warnings.simplefilter("ignore")
568
- fig.tight_layout()
436
+ Args:
437
+ filename (str): Filename of the binary file.
438
+ compressed (bool, optional): `True` if file should be compressed, `False`
439
+ otherwise. Defaults to `True`.
440
+ geometry (bool, optional): `True` if geometry should be written, `False`
441
+ otherwise. Defaults to `True`.
442
+ """
569
443
 
570
- return fig, ax
444
+ file_open = gzip.open if compressed else open
445
+ with file_open(filename, "wb") as file:
446
+ for field in self:
447
+ field.to_file(file, geometry)
448
+ geometry = False
571
449
 
572
450
 
573
451
  class File:
@@ -583,11 +461,12 @@ class File:
583
461
 
584
462
  Args:
585
463
  filename (str): File name.
586
- chunk_size (int, optional): Chunk size in bytes. Defaults to 8388608 (8 MiB).
587
- verbose (bool, optional): Verbose output. Defaults to True.
464
+ chunk_size (int, optional): Chunk size in bytes. Defaults to `8388608`
465
+ (8 MiB).
466
+ verbose (bool, optional): Verbose output. Defaults to `True`.
588
467
 
589
468
  Raises:
590
- FileNotFoundError: If file is not found.
469
+ `FileNotFoundError`: If file is not found.
591
470
  """
592
471
  if not os.path.isfile(filename):
593
472
  raise FileNotFoundError(f"File not found: {filename}")
@@ -606,12 +485,12 @@ class File:
606
485
  self.spacing: np.ndarray[(3,), np.float32] = None
607
486
 
608
487
  try:
609
- self.find_geo()
488
+ self.find_geometry()
610
489
  except (geo.GeometryFileNotFoundError, geo.MultipleGeometryFilesError):
611
490
  if self._verbose:
612
491
  self._warn("Caution: A geometry file was not found.")
613
492
 
614
- def __getitem__(self, key: int or slice or list or Callable[[Field], bool]):
493
+ def __getitem__(self, key: Union[int, slice, list, Callable[[Field], bool]]):
615
494
  return self.read(key)
616
495
 
617
496
  def __enter__(self):
@@ -697,11 +576,13 @@ class File:
697
576
  """Get the times of the fields in the file.
698
577
 
699
578
  Returns:
700
- List[float]: List of times.
579
+ List of times.
701
580
  """
702
581
  return [position.time for position in self.index()]
703
582
 
704
- def set_geo(self, shape: Tuple[int, int, int], spacing: Tuple[float, float, float]):
583
+ def set_geometry(
584
+ self, shape: Tuple[int, int, int], spacing: Tuple[float, float, float]
585
+ ):
705
586
  """Set the geometry.
706
587
 
707
588
  Args:
@@ -712,46 +593,42 @@ class File:
712
593
  self.shape = np.array(shape)
713
594
  self.spacing = np.array(spacing)
714
595
 
715
- self.print_geo()
596
+ self.print_geometry()
716
597
 
717
- def read_geo(
718
- self, filename: str, type: geo.Type = geo.Type.EXTENDED, compressed: bool = True
719
- ):
598
+ def read_geometry(self, filename: str, compressed: Optional[bool] = None):
720
599
  """Read geometry from a file.
721
600
 
722
601
  Args:
723
602
  filename (str): Filename of a geometry file.
724
- type (Type, optional): Data type to be read. Defaults to Type.EXTENDED.
725
- compressed (bool, optional): True if file is compressed, False otherwise.
726
- Defaults to True.
603
+ compressed (bool, optional): `True` if file is compressed, `False`
604
+ otherwise. Defaults to `None` (auto).
727
605
  """
728
- geo_dict = geo.read(filename, type=type, compressed=compressed)
606
+ if compressed is None:
607
+ compressed = utils.is_compressed(filename)
608
+
609
+ geometry = geo.read(filename, type=geo.Type.BASIC, compressed=compressed)
729
610
 
730
- shape = geo_dict["shape"]
731
- spacing = utils.convert_si(geo_dict["spacing"], "cm", "μm")
611
+ shape = geometry["shape"]
612
+ spacing = geometry["spacing"]
732
613
 
733
- self.set_geo(shape, spacing)
614
+ self.set_geometry(shape, spacing)
734
615
 
735
- def find_geo(self, type: geo.Type = geo.Type.EXTENDED, compressed: bool = None):
616
+ def find_geometry(self, compressed: Optional[bool] = None):
736
617
  """Find geometry file and read it.
737
618
 
738
619
  Args:
739
- type (Type, optional): Data type to be read. Defaults to Type.EXTENDED.
740
620
  compressed (bool, optional): True if file is compressed, False otherwise.
741
- Defaults to None (auto).
621
+ Defaults to `None` (auto).
742
622
 
743
623
  Raises:
744
- GeometryFileNotFoundError: If no geometry file is found.
745
- MultipleGeometryFilesError: If multiple geometry files are found.
624
+ `GeometryFileNotFoundError`: If no geometry file is found.
625
+ `MultipleGeometryFilesError`: If multiple geometry files are found.
746
626
  """
747
627
  filename = geo.find(self._filename)
748
628
 
749
- if compressed is None:
750
- compressed = utils.is_compressed(filename)
751
-
752
- self.read_geo(filename, type=type, compressed=compressed)
629
+ self.read_geometry(filename, compressed=compressed)
753
630
 
754
- def print_geo(self):
631
+ def print_geometry(self):
755
632
  """Get a string representation of the geometry."""
756
633
 
757
634
  if self.shape is None or self.spacing is None:
@@ -760,7 +637,7 @@ class File:
760
637
 
761
638
  dimensions = Field.dimensions(self.shape)
762
639
  cells = self.shape
763
- spacing = np.round(self.spacing, 7)
640
+ spacing = 1e4 * np.round(self.spacing.astype(float), 7)
764
641
  size = cells * spacing
765
642
 
766
643
  self._info(f"Geometry: {dimensions}-Dimensional Grid")
@@ -768,28 +645,30 @@ class File:
768
645
  self._info(f"Grid Shape (Cell Count): {tuple(cells)}")
769
646
  self._info(f"Grid Spacing (Cell Size): {tuple(spacing)}μm³")
770
647
 
771
- def iterate(self):
648
+ def iterate(self) -> Generator[Field, None, None]:
772
649
  """Iterate over fields in the file.
773
650
 
774
651
  Returns:
775
- Field: Field data.
652
+ A generator of fields.
776
653
  """
777
654
  for position in self.index():
778
655
  yield Field.read(position, shape=self.shape, spacing=self.spacing)
779
656
 
780
- def read(self, key: int or slice or list or Callable[[Field], bool] = None):
781
- """Read a field from the file.
657
+ def read(
658
+ self, key: Optional[Union[int, slice, list, Callable[[Field], bool]]] = None
659
+ ) -> Series:
660
+ """Read field data from the file.
782
661
 
783
662
  Args:
784
- key (int or list or slice or Callable[[Field], bool], optional): Field ID,
663
+ key (Union[int, slice, list, Callable[[Field], bool], optional): Key to
785
664
  list of field IDs, a slice object, or a condition function.
786
- Defaults to None.
665
+ Defaults to `None`.
787
666
 
788
667
  Returns:
789
- FieldList: List of fields.
668
+ A series of fields.
790
669
  """
791
670
 
792
- def read_field(self, field_id: int or slice or list):
671
+ def read_field(self, field_id: Union[int, slice, list]):
793
672
  position = self._index[field_id]
794
673
  return Field.read(position, shape=self.shape, spacing=self.spacing)
795
674
 
@@ -816,4 +695,109 @@ class File:
816
695
  else:
817
696
  raise TypeError("Invalid argument type")
818
697
 
819
- return FieldList(fields)
698
+ return Series(fields)
699
+
700
+
701
+ @dataclass
702
+ class PlotArgs:
703
+ """Arguments for plotting a field.
704
+
705
+ Args:
706
+ title (str, optional): Title of the plot. Defaults to `None`.
707
+ xlabel (str, optional): Label of the x-axis. Defaults to `None`.
708
+ ylabel (str, optional): Label of the y-axis. Defaults to `None`.
709
+ figsize (Tuple[float, float], optional): Figure size. Defaults to `None`.
710
+ dpi (int, optional): Figure DPI. Defaults to `None`.
711
+ aspect (str, optional): Aspect ratio. Defaults to `equal`.
712
+ ax (matplotlib.Axes, optional): Axes of the plot. Defaults to `None`.
713
+ cax (matplotlib.Axes, optional): Axes of the color bar. Defaults to `None`.
714
+ vmin (float, optional): Minimum value of the color bar. Defaults to `None`.
715
+ vmax (float, optional): Maximum value of the color bar. Defaults to `None`.
716
+ cmap (str, optional): Colormap. Defaults to `
717
+ """
718
+
719
+ title: Optional[str] = None
720
+ xlabel: Optional[str] = None
721
+ ylabel: Optional[str] = None
722
+ figsize: Optional[Tuple[float, float]] = None
723
+ dpi: Optional[int] = None
724
+ aspect: str = "equal"
725
+ ax: "matplotlib.Axes" = None
726
+ cax: "matplotlib.Axes" = None
727
+ vmin: Optional[float] = None
728
+ vmax: Optional[float] = None
729
+ cmap: str = "micpy"
730
+
731
+
732
+ def plot(
733
+ field: Field,
734
+ axis: str = "y",
735
+ index: int = 0,
736
+ args: Optional[PlotArgs] = None,
737
+ ) -> Tuple["matplotlib.Figure", "matplotlib.Axes"]:
738
+ """Plot a slice of the field.
739
+
740
+ Args:
741
+ field (Field): Field to plot.
742
+ axis (str, optional): Axis to plot. Possible values are `x`, `y`, and `z`.
743
+ Defaults to `y`.
744
+ index (int, optional): Index of the slice. Defaults to `0`.
745
+ args (PlotArgs, optional): Arguments for plotting. Defaults to `None`.
746
+
747
+ Returns:
748
+ Matplotlib figure and axes of the plot.
749
+ """
750
+
751
+ if matplotlib is None:
752
+ raise ImportError("matplotlib is not installed")
753
+
754
+ if axis not in ["x", "y", "z"]:
755
+ raise ValueError("Invalid axis")
756
+
757
+ if args is None:
758
+ args = PlotArgs()
759
+
760
+ if axis == "z":
761
+ x, y = "x", "y"
762
+ slice_2d = field[index, :, :]
763
+ elif axis == "y":
764
+ x, y = "x", "z"
765
+ slice_2d = field[:, index, :]
766
+ if Field.dimensions(field.shape) == 2:
767
+ slice_2d = slice_2d.reshape(slice_2d.shape[1], slice_2d.shape[0])
768
+ elif axis == "x":
769
+ x, y = "y", "z"
770
+ slice_2d = field[:, :, index]
771
+
772
+ fig, ax = (
773
+ pyplot.subplots(figsize=args.figsize, dpi=args.dpi)
774
+ if args.ax is None
775
+ else (args.ax.get_figure(), args.ax)
776
+ )
777
+
778
+ if args.title is not None:
779
+ ax.set_title(args.title)
780
+ else:
781
+ ax.set_title(f"t={np.round(field.time, 7)}s")
782
+ if args.xlabel is not None:
783
+ ax.set_xlabel(args.xlabel)
784
+ else:
785
+ ax.set_xlabel(x)
786
+ if args.ylabel is not None:
787
+ ax.set_ylabel(args.ylabel)
788
+ else:
789
+ ax.set_ylabel(y)
790
+ if args.aspect is not None:
791
+ ax.set_aspect(args.aspect)
792
+ ax.set_frame_on(False)
793
+
794
+ mesh = ax.pcolormesh(slice_2d, cmap=args.cmap, vmin=args.vmin, vmax=args.vmax)
795
+
796
+ bar = pyplot.colorbar(mesh, ax=ax, cax=args.cax)
797
+ bar.locator = matplotlib.ticker.MaxNLocator(
798
+ integer=np.issubdtype(slice_2d.dtype, np.integer)
799
+ )
800
+ bar.outline.set_visible(False)
801
+ bar.update_ticks()
802
+
803
+ return fig, ax
micpy/geo.py CHANGED
@@ -1,10 +1,11 @@
1
- """This module provides methods to read and write geometry files."""
1
+ """The `micpy.geo` modules provides methods to read and write geometry files."""
2
2
 
3
3
  import enum
4
4
  import gzip
5
5
 
6
6
  from typing import Tuple
7
7
  from pathlib import Path
8
+ from typing import Optional
8
9
 
9
10
  import numpy as np
10
11
  from numpy import ndarray
@@ -31,11 +32,11 @@ BASIC_MODEL = [
31
32
 
32
33
  EXTENSION_MODEL = [
33
34
  ("extension_header", np.int32),
34
- ("version", np.string_, 10),
35
- ("compile_date", np.string_, 10),
36
- ("run_date", np.string_, 10),
37
- ("platform", np.string_, 10),
38
- ("precision", np.string_, 10),
35
+ ("version", np.bytes_, 10),
36
+ ("compile_date", np.bytes_, 10),
37
+ ("run_date", np.bytes_, 10),
38
+ ("platform", np.bytes_, 10),
39
+ ("precision", np.bytes_, 10),
39
40
  ("extension_footer", np.int32),
40
41
  ]
41
42
 
@@ -59,7 +60,7 @@ class MultipleGeometryFilesError(GeometryFileError):
59
60
  """Raised when multiple potential geometry files are found."""
60
61
 
61
62
 
62
- def find(filename: str) -> str:
63
+ def find(filename: str) -> Path:
63
64
  """Find the geometry file for a given binary file.
64
65
 
65
66
  This method assumes that (a) both files share a common basename and (b) the
@@ -69,7 +70,7 @@ def find(filename: str) -> str:
69
70
  filename (str): Filename of a binary file.
70
71
 
71
72
  Returns:
72
- Path: Path to the geometry file.
73
+ Path to the geometry file.
73
74
  """
74
75
  basename = _get_basename(Path(filename))
75
76
  matches = list(basename.parent.glob(f"{basename.name}[._]geoF*"))
@@ -89,12 +90,12 @@ def read(filename: str, type: Type = Type.EXTENDED, compressed: bool = True) ->
89
90
 
90
91
  Args:
91
92
  filename (str): Filename of a geometry file.
92
- type (Type, optional): Data type to be read.
93
+ type (Type, optional): Data type to be read. Defaults to `Type.EXTENDED`.
93
94
  compressed (bool, optional): True if file is compressed, False otherwise.
94
- Defaults to True.
95
+ Defaults to `True`.
95
96
 
96
97
  Returns:
97
- dict: Dictionary representation of the geometry file.
98
+ Dictionary representation of the geometry file.
98
99
  """
99
100
  array_data = read_ndarray(filename, type, compressed)
100
101
  return _ndarray_to_dict(array_data)
@@ -107,12 +108,12 @@ def read_ndarray(
107
108
 
108
109
  Args:
109
110
  filename (str): Filename of a geometry file.
110
- type (Type, optional): Data type to be read. Defaults to Type.EXTENDED.
111
- compressed (bool, optional): True if file is compressed, False otherwise.
112
- Defaults to True.
111
+ type (Type, optional): Data type to be read. Defaults to `Type.EXTENDED`.
112
+ compressed (bool, optional): `True` if file is compressed, `False` otherwise.
113
+ Defaults to `True`.
113
114
 
114
115
  Returns:
115
- ndarray: NumPy array representation of the geometry file.
116
+ NumPy array representation of the geometry file.
116
117
  """
117
118
  opener = gzip.open if compressed else open
118
119
  with opener(filename, "rb") as f:
@@ -130,9 +131,9 @@ def write(
130
131
  Args:
131
132
  filename (str): Filename of a geometry file.
132
133
  data (dict): Dictionary representation of a geometry file.
133
- type (Type, optional): Data type to be written. Defaults to Type.EXTENDED.
134
- compressed (bool, optional): True if file should be compressed, False otherwise.
135
- Defaults to True.
134
+ type (Type, optional): Data type to be written. Defaults to `Type.EXTENDED`.
135
+ compressed (bool, optional): `True` if file should be compressed, `False`
136
+ otherwise. Defaults to `True`.
136
137
  """
137
138
  array_data = _dict_to_ndarray(data, type)
138
139
  write_ndarray(filename, array_data, compressed)
@@ -144,8 +145,8 @@ def write_ndarray(filename: str, data: ndarray, compressed: bool = True):
144
145
  Args:
145
146
  filename (str): Filename of a geometry file.
146
147
  data (ndarray): NumPy array representation of a geometry file.
147
- compressed (bool, optional): True if file should be compressed, False otherwise.
148
- Defaults to True.
148
+ compressed (bool, optional): `True` if file should be compressed, `False`
149
+ otherwise. Defaults to `True`.
149
150
  """
150
151
  if not _validate_data_type(data):
151
152
  raise ValueError("Invalid data type")
@@ -157,7 +158,7 @@ def write_ndarray(filename: str, data: ndarray, compressed: bool = True):
157
158
  f.write(data.tobytes())
158
159
 
159
160
 
160
- def get_basic(
161
+ def build(
161
162
  filename: str, shape: Tuple[int, int, int], spacing: Tuple[float, float, float]
162
163
  ) -> Tuple[str, dict, Type]:
163
164
  """Create a basic geometry.
@@ -174,13 +175,13 @@ def get_basic(
174
175
  "spacing": spacing,
175
176
  "basic_footer": 24,
176
177
  }
177
- return geo_filename, geo_data, Type.BASIC
178
+ return geo_filename, geo_data
178
179
 
179
180
 
180
181
  def _get_basename(path: Path) -> Path:
181
182
  """Extract the basename from a binary filename.
182
183
 
183
- A specific check for the '.mcr' extension is added due to historical naming
184
+ A specific check for the `.mcr` extension is added due to historical naming
184
185
  conventions related to the association of binary files and their geometry files.
185
186
  """
186
187
  if not path.name:
@@ -194,7 +195,7 @@ def _get_basename(path: Path) -> Path:
194
195
  return path.with_suffix("")
195
196
 
196
197
 
197
- def _validate_data_type(data: np.ndarray) -> bool:
198
+ def _validate_data_type(data: Optional[np.ndarray]) -> bool:
198
199
  """Validate the data type."""
199
200
  if not isinstance(data, np.ndarray):
200
201
  return False
micpy/matplotlib.py CHANGED
@@ -1,6 +1,4 @@
1
- import os
2
- from distutils.version import LooseVersion
3
-
1
+ from packaging import version
4
2
 
5
3
  try:
6
4
  import matplotlib
@@ -31,14 +29,12 @@ def _register_colormaps():
31
29
  "#fffbb4",
32
30
  ]
33
31
 
34
- version = LooseVersion(matplotlib.__version__) >= LooseVersion("3.7")
35
-
36
32
  create = matplotlib.colors.LinearSegmentedColormap.from_list
37
33
 
38
34
  colormap = create(name="micpy", colors=colors, N=1024)
39
35
  colormap_r = create(name="micpy_r", colors=colors[::-1], N=1024)
40
36
 
41
- if version:
37
+ if version.parse(matplotlib.__version__) >= version.parse("3.7"):
42
38
  register = matplotlib.colormaps.register
43
39
  register(colormap)
44
40
  register(colormap_r)
@@ -48,28 +44,6 @@ def _register_colormaps():
48
44
  register("micpy_r", colormap_r)
49
45
 
50
46
 
51
- def _set_aixvipmap_font():
52
- font = "IBM Plex Sans"
53
- if font in matplotlib.font_manager.get_font_names():
54
- pyplot.rcParams["font.family"] = font
55
-
56
-
57
- def _set_aixvipmap_colors():
58
- colors = [
59
- "#00549F",
60
- "#F6A800",
61
- "#57AB27",
62
- "#CC071E",
63
- "#612158",
64
- "#006165",
65
- "#E30066",
66
- "#0098A1",
67
- "#BDCD00",
68
- "#0098A1",
69
- ]
70
- pyplot.rcParams["axes.prop_cycle"] = pyplot.cycler(color=colors)
71
-
72
-
73
47
  def configure():
74
48
  if not matplotlib:
75
49
  return
@@ -78,7 +52,3 @@ def configure():
78
52
  _register_colormaps()
79
53
  except ValueError:
80
54
  pass
81
-
82
- if "AIXVIPMAP_HOME" in os.environ:
83
- _set_aixvipmap_font()
84
- _set_aixvipmap_colors()
micpy/tab.py CHANGED
@@ -1,4 +1,4 @@
1
- """This module provides methods to read and parse tabular files."""
1
+ """The `micpy.tab` module provides methods to read and parse tabular files."""
2
2
 
3
3
  import io
4
4
  import re
@@ -20,15 +20,15 @@ def parse(
20
20
 
21
21
  Args:
22
22
  string (str): The content of a tabular file as a string.
23
- parse_header (bool, optional): Whether to parse the header. Defaults to True.
23
+ parse_header (bool, optional): Whether to parse the header. Defaults to `True`.
24
24
  ignore_invalid_header (bool, optional): Whether to ignore invalid headers.
25
- Defaults to True.
25
+ Defaults to `True`.
26
26
 
27
27
  Raises:
28
- FormatError: If the number of columns in the header does not match the body.
28
+ `FormatError`: If the number of columns in the header does not match the body.
29
29
 
30
30
  Returns:
31
- DataFrame: The content of the file as a pandas DataFrame.
31
+ The content of the file as a pandas DataFrame.
32
32
  """
33
33
  lines = string.splitlines()
34
34
  return _parse_lines(lines, parse_header, ignore_invalid_header)
@@ -41,15 +41,15 @@ def read(
41
41
 
42
42
  Args:
43
43
  filename (str): Path to the file.
44
- parse_header (bool, optional): Whether to parse the header. Defaults to True.
44
+ parse_header (bool, optional): Whether to parse the header. Defaults to `True`.
45
45
  ignore_invalid_header (bool, optional): Whether to ignore invalid headers.
46
- Defaults to True.
46
+ Defaults to `True`.
47
47
 
48
48
  Raises:
49
- FormatError: If the number of columns in the header does not match the body.
49
+ `FormatError`: If the number of columns in the header does not match the body.
50
50
 
51
51
  Returns:
52
- DataFrame: The content of the file as a pandas DataFrame.
52
+ The content of the file as a pandas DataFrame.
53
53
  """
54
54
  with open(filename, mode="r", encoding="utf8") as file:
55
55
  lines = file.readlines()
micpy/utils.py CHANGED
@@ -4,17 +4,6 @@ import sys
4
4
  import time
5
5
 
6
6
 
7
- def convert_si(value, unit_in, unit_out):
8
- """Convert value from one SI unit to another SI unit."""
9
- SI = {"μm": 0.000001, "mm": 0.001, "cm": 0.01, "m": 1.0}
10
-
11
- if isinstance(value, (float, int)):
12
- return value * SI[unit_in] / SI[unit_out]
13
- elif isinstance(value, (tuple, list)):
14
- return [v * SI[unit_in] / SI[unit_out] for v in value]
15
- raise ValueError("Unsupported value type.")
16
-
17
-
18
7
  def progress_indicator(iterable, description="Progress", unit="Iteration", start=1):
19
8
  """Progress indicator for iterable."""
20
9
 
micpy/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.16b0"
1
+ __version__ = "0.3.0b0"
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.1
2
+ Name: micress-micpy
3
+ Version: 0.3.0b0
4
+ Summary: MicPy is a Python package to facilitate MICRESS workflows.
5
+ Author: Lukas Koschmieder
6
+ Author-email: l.koschmieder@access-technology.de
7
+ License: BSD-3-Clause (Copyright (c) 2024 Access e.V.)
8
+ Keywords: MICRESS
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Natural Language :: English
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: matplotlib
18
+ Requires-Dist: pandas
19
+
20
+ <center>
21
+
22
+ ![MicPy logo](https://docs.micress.de/micpy/images/micpy-logo.png)
23
+
24
+ </center>
25
+
26
+ # MicPy
27
+
28
+ MicPy is a Python package to facilitate [MICRESS](https://www.micress.de) workflows. Whether you aim to visualize, convert, or manipulate MICRESS data, MicPy provides the necessary tools.
29
+
30
+ ## Installation
31
+
32
+ ```
33
+ pip install micress-micpy
34
+ ```
35
+
36
+ ## Dependencies
37
+
38
+ MicPy requires the following dependencies:
39
+
40
+ - Python (>= 3.9)
41
+ - Pandas (>= 1.1)
42
+ - Matplotlib (>= 3) as an optional dependency for plotting
43
+
44
+ ## Documentation
45
+
46
+ https://docs.micress.de/micpy
@@ -0,0 +1,12 @@
1
+ micpy/__init__.py,sha256=7wQUaseppjQYZW1iAVNm2WSDjvBLlqtY8tiHsfDaW5Q,148
2
+ micpy/bin.py,sha256=6CiN1KUrIVDn5pXtf05n-A7D7yOFAUXAbfBUeXpgcGs,25521
3
+ micpy/geo.py,sha256=lVRTtPnTEykkSXNyLm3wnxXOwz72PFu0Spv8ZGHyUHo,7417
4
+ micpy/matplotlib.py,sha256=GF2ghyBORC5RRjW00DdsHu5aSkpMFdI9wqg6d_psPsI,1198
5
+ micpy/tab.py,sha256=QCnfggxRWkKgS9-zGj8kyCjhfUw7QeTgGZWedHh4MTw,3548
6
+ micpy/utils.py,sha256=Kt1AvhMvWer9uftbb88X7N27aXtQdJl26grHmmm2vOQ,859
7
+ micpy/version.py,sha256=v6LLclreYkCiqxCP6QRdhtVAzCVndQ77c0lTMqPZjXk,25
8
+ micress_micpy-0.3.0b0.dist-info/LICENSE,sha256=seHdCiArizUoWZ6bEFg6N3K2ZtfPK35wvOwg0kH-f6o,1488
9
+ micress_micpy-0.3.0b0.dist-info/METADATA,sha256=RbN1IrUFx14nsUJB2fIeLxrxFD49sF1sujNg1vBmlpA,1229
10
+ micress_micpy-0.3.0b0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
11
+ micress_micpy-0.3.0b0.dist-info/top_level.txt,sha256=RiopkpW0AGNYdtOW2eQUWgm3yHGC13q9pWlHb2alhiE,6
12
+ micress_micpy-0.3.0b0.dist-info/RECORD,,
@@ -1,186 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: micress-micpy
3
- Version: 0.2.16b0
4
- Summary: MicPy is a Python package to facilitate MICRESS workflows.
5
- Author: Lukas Koschmieder
6
- Author-email: l.koschmieder@access-technology.de
7
- License: BSD-3-Clause (Copyright (c) 2024 Access e.V.)
8
- Keywords: MICRESS
9
- Classifier: Development Status :: 4 - Beta
10
- Classifier: Intended Audience :: Science/Research
11
- Classifier: Natural Language :: English
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python :: 3.9
14
- Requires-Python: >=3.9
15
- Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
- Requires-Dist: matplotlib
18
- Requires-Dist: pandas
19
-
20
- <center>
21
-
22
- ![MicPy logo](https://cloud.micress.de/index.php/apps/files_sharing/publicpreview/ggEnRBmQEoNFLri?file=/&fileId=8458&x=1920&y=1080&a=true&etag=65c4cbfc544ae)
23
-
24
- </center>
25
-
26
- # MicPy
27
-
28
- MicPy is a Python package to facilitate [MICRESS](https://www.micress.de) workflows. Whether you aim to visualize, convert, or manipulate MICRESS data, MicPy provides the necessary tools.
29
-
30
- ## Installation
31
-
32
- ```
33
- pip install micress-micpy
34
- ```
35
-
36
- ## Dependencies
37
-
38
- MicPy requires the following dependencies:
39
-
40
- - Python (>= 3.9)
41
- - Pandas (>= 1.1)
42
- - Matplotlib (>= 3) as an optional dependency for plotting
43
-
44
- ## Examples
45
-
46
- Below are some examples demonstrating how to use MicPy.
47
-
48
- ### `bin` module
49
-
50
- #### Read one field at a time
51
-
52
- ```python
53
- from micpy import bin
54
-
55
- for field in bin.File("A001_Delta_Gamma.conc1"):
56
- print(field)
57
- ```
58
-
59
- #### Read all fields
60
-
61
- ```python
62
-
63
- from micpy import bin
64
-
65
- with bin.File("A001_Delta_Gamma.conc1") as file:
66
- fields = file.read()
67
-
68
- print(fields)
69
- ```
70
-
71
- #### Read a subset of fields providing a list of indices
72
-
73
- ```python
74
- from micpy import bin
75
-
76
- with bin.File("A001_Delta_Gamma.conc1") as file:
77
- fields = file.read([0, 1, -2, -1])
78
-
79
- print(fields)
80
- ```
81
-
82
- #### Read a subset of fields providing a condition function
83
-
84
- ```python
85
- from micpy import bin
86
-
87
- with bin.File("A001_Delta_Gamma.conc1") as file:
88
- fields = file.read(lambda field: field.time >= 10 and field.time <= 20)
89
-
90
- print(fields)
91
- ```
92
-
93
- #### Read fields opening and closing the file manually
94
-
95
- ```python
96
- from micpy import bin
97
-
98
- file = bin.File("A001_Delta_Gamma.conc1")
99
-
100
- file.open()
101
-
102
- first_field = file.read(0)
103
- second_field = file.read(1)
104
-
105
- file.close()
106
-
107
- print(first_field, second_field)
108
- ```
109
-
110
- #### Plot a field
111
-
112
- ```python
113
- from micpy import bin
114
-
115
- with bin.File("A001_Delta_Gamma.conc1") as file:
116
- field = file[-1]
117
-
118
- fig, ax = field.plot()
119
- ```
120
-
121
- #### Plot a list of fields normalizing the data
122
-
123
- ```python
124
- from micpy import bin
125
-
126
- with bin.File("A001_Delta_Gamma.conc1") as file:
127
- fields = file[[0, 1, -2, -1]]
128
-
129
- fig, ax = fields.plot(normalize=True, figsize=(8, 8))
130
- ```
131
-
132
- #### Plot a specific plane of a field
133
-
134
- ```python
135
- from micpy import bin
136
-
137
- with bin.File("A005_Grain_Growth_Misorientation_3D.korn") as file:
138
- field = file[0]
139
-
140
- fig, ax = field.plot(plane="xy", slice=50)
141
- ```
142
-
143
- ### `tab` module
144
-
145
- #### Read a table
146
-
147
- ```python
148
- from micpy import tab
149
-
150
- table = tab.read("A001_Delta_Gamma.TabF")
151
- ```
152
-
153
- #### Plot a table
154
-
155
- ```python
156
- from micpy import tab
157
-
158
- table = tab.read("A001_Delta_Gamma.TabF")
159
-
160
- fig, ax = table.plot(x=1, y=[2, 3, 4])
161
- ```
162
-
163
- #### Extract a subset of a table
164
-
165
- ```python
166
- from micpy import tab
167
-
168
- table = tab.read("A001_Delta_Gamma.TabF")
169
-
170
- table.loc[
171
- table["Fraction Phase 2 FCC_A1"] > 0,
172
- ["Temperature [K]", "Fraction Phase 1 BCC_A2", "Fraction Phase 2 FCC_A1"]
173
- ]
174
- ```
175
-
176
- #### Convert a table to different file formats
177
-
178
- ```python
179
- from micpy import tab
180
-
181
- table = tab.read("A001_Delta_Gamma.TabF")
182
-
183
- table.to_csv("A001_Delta_Gamma.TabF.csv")
184
- table.to_excel("A001_Delta_Gamma.TabF.xlsx")
185
- table.to_json("A001_Delta_Gamma.TabF.json")
186
- ```
@@ -1,12 +0,0 @@
1
- micpy/__init__.py,sha256=7wQUaseppjQYZW1iAVNm2WSDjvBLlqtY8tiHsfDaW5Q,148
2
- micpy/bin.py,sha256=1eO8nv3j3CNFFx35W2I-MdOTgelxVwOf3AzIv5-cnXY,26586
3
- micpy/geo.py,sha256=dcXcxvAxPpe4Rrs1ImYs8H67c2228brabdefJMoLn0g,7354
4
- micpy/matplotlib.py,sha256=toxFTtTaA-usuWUltMp0XufQee4-n68XH7SN2In2NZY,1845
5
- micpy/tab.py,sha256=ZXhL6bg17W3jqFFX28htCOAV9W_W3op_-GD7iU5a_wY,3547
6
- micpy/utils.py,sha256=-JS5SRqH4QMD6_pXBKPVw5zPNTbaqkcOUu9ej5Gi0QU,1282
7
- micpy/version.py,sha256=MwwJ3Tq2WyMrlg5ARuI934Sn37GJtLQJ8y2GpYXfdw4,26
8
- micress_micpy-0.2.16b0.dist-info/LICENSE,sha256=seHdCiArizUoWZ6bEFg6N3K2ZtfPK35wvOwg0kH-f6o,1488
9
- micress_micpy-0.2.16b0.dist-info/METADATA,sha256=ZtkZWKBm6rCf4Bgwq8hKzO-sEdNkSpCBe7XDC5sF6EY,3824
10
- micress_micpy-0.2.16b0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
11
- micress_micpy-0.2.16b0.dist-info/top_level.txt,sha256=RiopkpW0AGNYdtOW2eQUWgm3yHGC13q9pWlHb2alhiE,6
12
- micress_micpy-0.2.16b0.dist-info/RECORD,,