micress-micpy 0.2.17b0__py3-none-any.whl → 0.3.1b0__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
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,223 +352,102 @@ 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",
423
- ) -> Tuple["matplotlib.Figure", "matplotlib.Axes"]:
424
- """Plot a slice of the field.
372
+ filename: str,
373
+ compressed: bool = True,
374
+ geometry: bool = True,
375
+ ):
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`
433
- (auto).
434
- dpi (int, optional): Figure DPI. Defaults to `None` (auto).
435
- aspect (str, optional): Aspect ratio. Defaults to `equal`.
436
- ax (Axes, optional): Axes to plot on. Defaults to `None`.
437
- cax (Axes, optional): Axes to plot color bar on. Defaults to `None`.
438
- vmin (float, optional): Minimum value of the color bar. Defaults to `None`.
439
- vmax (float, optional): Maximum value of the color bar. Defaults to `None`.
440
- cmap (str, optional): Color map. Defaults to `micpy`.
441
-
442
- Returns:
443
- 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`.
444
384
  """
445
- if matplotlib is None:
446
- raise ImportError("matplotlib is not installed")
447
-
448
- slice = self.get_slice(plane, slice_id) if self.ndim == 3 else self
449
-
450
- fig, ax = (
451
- pyplot.subplots(figsize=figsize, dpi=dpi)
452
- if ax is None
453
- else (ax.get_figure(), ax)
454
- )
455
-
456
- if title is not None:
457
- ax.set_title(title)
458
- else:
459
- ax.set_title(f"t={np.round(self.time, 7)}s")
460
- if xlabel is not None:
461
- ax.set_xlabel(xlabel)
462
- else:
463
- ax.set_xlabel(plane[0])
464
- if ylabel is not None:
465
- ax.set_ylabel(ylabel)
466
- else:
467
- ax.set_ylabel(plane[1])
468
- if aspect is not None:
469
- ax.set_aspect(aspect)
470
- ax.set_frame_on(False)
471
-
472
- mesh = ax.pcolormesh(slice, cmap=cmap, vmin=vmin, vmax=vmax)
473
-
474
- bar = pyplot.colorbar(mesh, ax=ax, cax=cax)
475
- bar.locator = matplotlib.ticker.MaxNLocator(
476
- integer=np.issubdtype(slice.dtype, np.integer)
477
- )
478
- bar.outline.set_visible(False)
479
- bar.update_ticks()
480
-
481
- return fig, ax
482
-
483
-
484
- class FieldList(List[Field]):
485
- """A list of fields."""
486
385
 
487
- def __init__(self, fields: List[Field] = None):
488
- super().__init__(fields if fields else [])
386
+ file_open = gzip.open if compressed else open
387
+ with file_open(filename, "wb") as file:
388
+ self.to_file(file, geometry)
489
389
 
490
- def append(self, field: Field):
491
- """Append a field to the list."""
492
- super().append(field)
493
390
 
494
- def extend(self, fields: List[Field]):
495
- """Extend the list with fields."""
496
- super().extend(fields)
391
+ class Series(np.ndarray):
392
+ def __new__(cls, fields: List[Field]):
393
+ obj = np.asarray(fields).view(cls)
394
+ obj.times = [field.time for field in fields]
395
+ obj.spacings = [field.spacing for field in fields]
396
+ return obj
397
+
398
+ def __array_finalize__(self, obj):
399
+ if obj is None:
400
+ return
497
401
 
498
- def write(self, filename: str, compressed: bool = True, write_geo: bool = True):
499
- """Write the fields to a binary file."""
500
- file_open = gzip.open if compressed else open
501
- with file_open(filename, "wb") as file:
502
- for field in self:
503
- field.write(file)
402
+ # pylint: disable=attribute-defined-outside-init
403
+ self.times = getattr(obj, "times", None)
404
+ self.spacings = getattr(obj, "spacings", None)
504
405
 
505
- if write_geo:
506
- geo_filename, geo_data, geo_type = geo.get_basic(
507
- filename, self[0].shape, self[0].spacing
508
- )
509
- geo.write(geo_filename, geo_data, geo_type, compressed=compressed)
406
+ def __iter__(self):
407
+ for item, time, spacing in zip(self, self.times, self.spacings):
408
+ yield Field(item, time, spacing)
510
409
 
511
- # pylint: disable=too-many-arguments
512
- def plot(
513
- self,
514
- cols: int = None,
515
- normalize: bool = False,
516
- sharex: bool = False,
517
- sharey: bool = False,
518
- figsize: Tuple[float, float] = None,
519
- dpi: int = None,
520
- **kwargs,
521
- ) -> Tuple["matplotlib.Figure", "matplotlib.Axes"]:
522
- """Plot the fields in a grid.
410
+ def get(self, index: Union[int, list, slice]) -> Field:
411
+ """Get one or more fields from the series.
523
412
 
524
413
  Args:
525
- cols (int, optional): Number of columns. Defaults to `None` (auto).
526
- normalize (bool, optional): Normalize the color bar. Defaults to `False`.
527
- sharex (bool, optional): Share x-axis. Defaults to `False`.
528
- sharey (bool, optional): Share y-axis. Defaults to `False`.
529
- figsize (Tuple[float, float], optional): Figure size. Defaults to `None`
530
- (auto).
531
- dpi (int, optional): Figure DPI. Defaults to `None` (auto).
532
- **kwargs: Keyword arguments passed to `Field.plot()`.
414
+ index (Union[int, list, slice]): Index of the field.
533
415
 
534
416
  Returns:
535
- Figure and axes of the plot.
417
+ Field.
536
418
  """
537
- if matplotlib is None:
538
- raise ImportError("matplotlib is not installed")
539
419
 
540
- if cols is None:
541
- cols = int(np.ceil(np.sqrt(len(self))))
420
+ if isinstance(index, int):
421
+ return Field(self[index], self.times[index], self.spacings[index])
422
+ elif isinstance(index, slice):
423
+ series = self[index]
424
+ series.times = self.times[index]
425
+ series.spacings = self.spacings[index]
426
+ return series
427
+ elif isinstance(index, list):
428
+ series = self[index]
429
+ series.times = [self.times[i] for i in index]
430
+ series.spacings = [self.spacings[i] for i in index]
431
+ return series
432
+ raise TypeError("Invalid argument type")
542
433
 
543
- rows = (len(self) + cols - 1) // cols
544
434
 
545
- fig, ax = pyplot.subplots(
546
- rows, cols, figsize=figsize, dpi=dpi, sharex=sharex, sharey=sharey
547
- )
435
+ def write(self, filename: str, compressed: bool = True, geometry: bool = True):
436
+ """Write the series to a binary file.
548
437
 
549
- vmin = (
550
- min(field.min() for field in self)
551
- if normalize
552
- else kwargs.pop("vmin", None)
553
- )
554
- vmax = (
555
- max(field.max() for field in self)
556
- if normalize
557
- else kwargs.pop("vmax", None)
558
- )
559
-
560
- flat_ax = ax.flatten() if isinstance(ax, np.ndarray) else [ax]
561
-
562
- for i, field in enumerate(self):
563
- field.plot(ax=flat_ax[i], vmin=vmin, vmax=vmax, **kwargs)
564
-
565
- for i in range(len(self), len(flat_ax)):
566
- fig.delaxes(flat_ax[i])
567
-
568
- with warnings.catch_warnings():
569
- warnings.simplefilter("ignore")
570
- fig.tight_layout()
438
+ Args:
439
+ filename (str): Filename of the binary file.
440
+ compressed (bool, optional): `True` if file should be compressed, `False`
441
+ otherwise. Defaults to `True`.
442
+ geometry (bool, optional): `True` if geometry should be written, `False`
443
+ otherwise. Defaults to `True`.
444
+ """
571
445
 
572
- return fig, ax
446
+ file_open = gzip.open if compressed else open
447
+ with file_open(filename, "wb") as file:
448
+ for field in self:
449
+ field.to_file(file, geometry)
450
+ geometry = False
573
451
 
574
452
 
575
453
  class File:
@@ -609,12 +487,12 @@ class File:
609
487
  self.spacing: np.ndarray[(3,), np.float32] = None
610
488
 
611
489
  try:
612
- self.find_geo()
490
+ self.find_geometry()
613
491
  except (geo.GeometryFileNotFoundError, geo.MultipleGeometryFilesError):
614
492
  if self._verbose:
615
493
  self._warn("Caution: A geometry file was not found.")
616
494
 
617
- def __getitem__(self, key: int or slice or list or Callable[[Field], bool]):
495
+ def __getitem__(self, key: Union[int, slice, list, Callable[[Field], bool]]):
618
496
  return self.read(key)
619
497
 
620
498
  def __enter__(self):
@@ -704,42 +582,43 @@ class File:
704
582
  """
705
583
  return [position.time for position in self.index()]
706
584
 
707
- def set_geo(self, shape: Tuple[int, int, int], spacing: Tuple[float, float, float]):
585
+ def set_geometry(
586
+ self, shape: Tuple[int, int, int], spacing: Tuple[float, float, float]
587
+ ):
708
588
  """Set the geometry.
709
589
 
710
590
  Args:
711
591
  shape (Tuple[int, int, int]): Shape of the geometry.
712
- spacing (Tuple[float, float, float]): Spacing of the geometry in μm³.
592
+ spacing (Tuple[float, float, float]): Spacing of the geometry in μm.
713
593
  """
714
594
 
715
595
  self.shape = np.array(shape)
716
596
  self.spacing = np.array(spacing)
717
597
 
718
- self.print_geo()
598
+ self.print_geometry()
719
599
 
720
- def read_geo(
721
- self, filename: str, type: geo.Type = geo.Type.EXTENDED, compressed: bool = True
722
- ):
600
+ def read_geometry(self, filename: str, compressed: Optional[bool] = None):
723
601
  """Read geometry from a file.
724
602
 
725
603
  Args:
726
604
  filename (str): Filename of a geometry file.
727
- type (Type, optional): Data type to be read. Defaults to `Type.EXTENDED`.
728
- compressed (bool, optional): `True` if file is compressed, `False`
729
- otherwise. Defaults to True.
605
+ compressed (bool, optional): `True` if file is compressed, `False`
606
+ otherwise. Defaults to `None` (auto).
730
607
  """
731
- geo_dict = geo.read(filename, type=type, compressed=compressed)
608
+ if compressed is None:
609
+ compressed = utils.is_compressed(filename)
732
610
 
733
- shape = geo_dict["shape"]
734
- spacing = utils.convert_si(geo_dict["spacing"], "cm", "μm")
611
+ geometry = geo.read(filename, type=geo.Type.BASIC, compressed=compressed)
735
612
 
736
- self.set_geo(shape, spacing)
613
+ shape = geometry["shape"]
614
+ spacing = geometry["spacing"]
737
615
 
738
- def find_geo(self, type: geo.Type = geo.Type.EXTENDED, compressed: bool = None):
616
+ self.set_geometry(shape, spacing)
617
+
618
+ def find_geometry(self, compressed: Optional[bool] = None):
739
619
  """Find geometry file and read it.
740
620
 
741
621
  Args:
742
- type (Type, optional): Data type to be read. Defaults to `Type.EXTENDED`.
743
622
  compressed (bool, optional): True if file is compressed, False otherwise.
744
623
  Defaults to `None` (auto).
745
624
 
@@ -749,12 +628,9 @@ class File:
749
628
  """
750
629
  filename = geo.find(self._filename)
751
630
 
752
- if compressed is None:
753
- compressed = utils.is_compressed(filename)
754
-
755
- self.read_geo(filename, type=type, compressed=compressed)
631
+ self.read_geometry(filename, compressed=compressed)
756
632
 
757
- def print_geo(self):
633
+ def print_geometry(self):
758
634
  """Get a string representation of the geometry."""
759
635
 
760
636
  if self.shape is None or self.spacing is None:
@@ -763,36 +639,38 @@ class File:
763
639
 
764
640
  dimensions = Field.dimensions(self.shape)
765
641
  cells = self.shape
766
- spacing = np.round(self.spacing, 7)
642
+ spacing = 1e4 * np.round(self.spacing.astype(float), 7)
767
643
  size = cells * spacing
768
644
 
769
645
  self._info(f"Geometry: {dimensions}-Dimensional Grid")
770
- self._info(f"Grid Size: {tuple(size)}μm³")
646
+ self._info(f"Grid Size [μm]: {tuple(size)}")
771
647
  self._info(f"Grid Shape (Cell Count): {tuple(cells)}")
772
- self._info(f"Grid Spacing (Cell Size): {tuple(spacing)}μm³")
648
+ self._info(f"Grid Spacing (Cell Size) [μm]: {tuple(spacing)}")
773
649
 
774
650
  def iterate(self) -> Generator[Field, None, None]:
775
651
  """Iterate over fields in the file.
776
652
 
777
653
  Returns:
778
- Field data.
654
+ A generator of fields.
779
655
  """
780
656
  for position in self.index():
781
657
  yield Field.read(position, shape=self.shape, spacing=self.spacing)
782
658
 
783
- def read(self, key: int or slice or list or Callable[[Field], bool] = None) -> FieldList:
784
- """Read a field from the file.
659
+ def read(
660
+ self, key: Optional[Union[int, slice, list, Callable[[Field], bool]]] = None
661
+ ) -> Series:
662
+ """Read field data from the file.
785
663
 
786
664
  Args:
787
- key (int or list or slice or Callable[[Field], bool], optional): Field ID,
665
+ key (Union[int, slice, list, Callable[[Field], bool], optional): Key to
788
666
  list of field IDs, a slice object, or a condition function.
789
667
  Defaults to `None`.
790
668
 
791
669
  Returns:
792
- List of fields.
670
+ A series of fields.
793
671
  """
794
672
 
795
- def read_field(self, field_id: int or slice or list):
673
+ def read_field(self, field_id: Union[int, slice, list]):
796
674
  position = self._index[field_id]
797
675
  return Field.read(position, shape=self.shape, spacing=self.spacing)
798
676
 
@@ -819,4 +697,113 @@ class File:
819
697
  else:
820
698
  raise TypeError("Invalid argument type")
821
699
 
822
- return FieldList(fields)
700
+ return Series(fields)
701
+
702
+
703
+ @dataclass
704
+ class PlotArgs:
705
+ """Arguments for plotting a field.
706
+
707
+ Args:
708
+ title (str, optional): Title of the plot. Defaults to `None`.
709
+ xlabel (str, optional): Label of the x-axis. Defaults to `None`.
710
+ ylabel (str, optional): Label of the y-axis. Defaults to `None`.
711
+ figsize (Tuple[float, float], optional): Figure size. Defaults to `None`.
712
+ dpi (int, optional): Figure DPI. Defaults to `None`.
713
+ aspect (str, optional): Aspect ratio. Defaults to `equal`.
714
+ ax (matplotlib.Axes, optional): Axes of the plot. Defaults to `None`.
715
+ cax (matplotlib.Axes, optional): Axes of the color bar. Defaults to `None`.
716
+ vmin (float, optional): Minimum value of the color bar. Defaults to `None`.
717
+ vmax (float, optional): Maximum value of the color bar. Defaults to `None`.
718
+ cmap (str, optional): Colormap. Defaults to `micpy`.
719
+ """
720
+
721
+ title: Optional[str] = None
722
+ xlabel: Optional[str] = None
723
+ ylabel: Optional[str] = None
724
+ figsize: Optional[Tuple[float, float]] = None
725
+ dpi: Optional[int] = None
726
+ aspect: str = "equal"
727
+ ax: "matplotlib.Axes" = None
728
+ cax: "matplotlib.Axes" = None
729
+ vmin: Optional[float] = None
730
+ vmax: Optional[float] = None
731
+ cmap: str = "micpy"
732
+
733
+
734
+ def plot(
735
+ field: Field,
736
+ axis: str = "y",
737
+ index: int = 0,
738
+ args: Optional[PlotArgs] = None,
739
+ ) -> Tuple["matplotlib.Figure", "matplotlib.Axes"]:
740
+ """Plot a slice of the field.
741
+
742
+ Args:
743
+ field (Field): Field to plot.
744
+ axis (str, optional): Axis to plot. Possible values are `x`, `y`, and `z`.
745
+ Defaults to `y`.
746
+ index (int, optional): Index of the slice. Defaults to `0`.
747
+ args (PlotArgs, optional): Arguments for plotting. Defaults to `None`.
748
+
749
+ Returns:
750
+ Matplotlib figure and axes of the plot.
751
+ """
752
+
753
+ if matplotlib is None:
754
+ raise ImportError("matplotlib is not installed")
755
+
756
+ if axis not in ["x", "y", "z"]:
757
+ raise ValueError("Invalid axis")
758
+
759
+ if field.ndim != 3:
760
+ raise ValueError("Invalid field shape")
761
+
762
+ if args is None:
763
+ args = PlotArgs()
764
+
765
+ if axis == "z":
766
+ x, y = "x", "y"
767
+ slice_2d = field[index, :, :]
768
+ elif axis == "y":
769
+ x, y = "x", "z"
770
+ slice_2d = field[:, index, :]
771
+ if Field.dimensions(field.shape) == 2:
772
+ slice_2d = slice_2d.reshape(slice_2d.shape[1], slice_2d.shape[0])
773
+ elif axis == "x":
774
+ x, y = "y", "z"
775
+ slice_2d = field[:, :, index]
776
+
777
+ fig, ax = (
778
+ pyplot.subplots(figsize=args.figsize, dpi=args.dpi)
779
+ if args.ax is None
780
+ else (args.ax.get_figure(), args.ax)
781
+ )
782
+
783
+ if args.title is not None:
784
+ ax.set_title(args.title)
785
+ else:
786
+ if isinstance(field, Field):
787
+ ax.set_title(f"t={np.round(field.time, 7)}s")
788
+ if args.xlabel is not None:
789
+ ax.set_xlabel(args.xlabel)
790
+ else:
791
+ ax.set_xlabel(x)
792
+ if args.ylabel is not None:
793
+ ax.set_ylabel(args.ylabel)
794
+ else:
795
+ ax.set_ylabel(y)
796
+ if args.aspect is not None:
797
+ ax.set_aspect(args.aspect)
798
+ ax.set_frame_on(False)
799
+
800
+ mesh = ax.pcolormesh(slice_2d, cmap=args.cmap, vmin=args.vmin, vmax=args.vmax)
801
+
802
+ bar = pyplot.colorbar(mesh, ax=ax, cax=args.cax)
803
+ bar.locator = matplotlib.ticker.MaxNLocator(
804
+ integer=np.issubdtype(slice_2d.dtype, np.integer)
805
+ )
806
+ bar.outline.set_visible(False)
807
+ bar.update_ticks()
808
+
809
+ return fig, ax
micpy/geo.py CHANGED
@@ -5,6 +5,7 @@ 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
@@ -89,7 +90,7 @@ 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
95
  Defaults to `True`.
95
96
 
@@ -131,7 +132,7 @@ def write(
131
132
  filename (str): Filename of a geometry file.
132
133
  data (dict): Dictionary representation of a geometry file.
133
134
  type (Type, optional): Data type to be written. Defaults to `Type.EXTENDED`.
134
- compressed (bool, optional): `True` if file should be compressed, `False`
135
+ compressed (bool, optional): `True` if file should be compressed, `False`
135
136
  otherwise. Defaults to `True`.
136
137
  """
137
138
  array_data = _dict_to_ndarray(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,7 +175,7 @@ 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:
@@ -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
@@ -13,31 +11,30 @@ except ImportError:
13
11
 
14
12
  def _register_colormaps():
15
13
  colors = [
16
- "#1102d8", # 17, 2, 216
17
- "#3007ba", # 48, 7, 186
18
- "#500b9d", # 80, 11, 157
19
- "#6f0e81", # 111, 14, 129
20
- "#8d1364", # 141, 19, 100
21
- "#ac1748", # 172, 23, 72
22
- "#cb1b2b", # 203, 27, 43
23
- "#ea1e0f", # 234, 30, 15
24
- "#f83605", # 248, 54, 5
25
- "#fa600f", # 250, 96, 15
26
- "#fb8817", # 251, 136, 23
27
- "#fdb120", # 253, 177, 32
28
- "#ffda29", # 255, 218, 41
29
- "#ffed4d", # 255, 237, 77
30
- "#fff380", # 255, 243, 128
31
- "#fffbb4", # 255, 251, 180
14
+ "#1102d8",
15
+ "#3007ba",
16
+ "#500b9d",
17
+ "#6f0e81",
18
+ "#8d1364",
19
+ "#ac1748",
20
+ "#cb1b2b",
21
+ "#ea1e0f",
22
+ "#f83605",
23
+ "#fa600f",
24
+ "#fb8817",
25
+ "#fdb120",
26
+ "#ffda29",
27
+ "#ffed4d",
28
+ "#fff380",
29
+ "#fffbb4",
32
30
  ]
33
- version = LooseVersion(matplotlib.__version__) >= LooseVersion("3.7")
34
31
 
35
32
  create = matplotlib.colors.LinearSegmentedColormap.from_list
36
33
 
37
34
  colormap = create(name="micpy", colors=colors, N=1024)
38
35
  colormap_r = create(name="micpy_r", colors=colors[::-1], N=1024)
39
36
 
40
- if version:
37
+ if version.parse(matplotlib.__version__) >= version.parse("3.7"):
41
38
  register = matplotlib.colormaps.register
42
39
  register(colormap)
43
40
  register(colormap_r)
@@ -47,28 +44,6 @@ def _register_colormaps():
47
44
  register("micpy_r", colormap_r)
48
45
 
49
46
 
50
- def _set_aixvipmap_font():
51
- font = "IBM Plex Sans"
52
- if font in matplotlib.font_manager.get_font_names():
53
- pyplot.rcParams["font.family"] = font
54
-
55
-
56
- def _set_aixvipmap_colors():
57
- colors = [
58
- "#00549F",
59
- "#F6A800",
60
- "#57AB27",
61
- "#CC071E",
62
- "#612158",
63
- "#006165",
64
- "#E30066",
65
- "#0098A1",
66
- "#BDCD00",
67
- "#0098A1",
68
- ]
69
- pyplot.rcParams["axes.prop_cycle"] = pyplot.cycler(color=colors)
70
-
71
-
72
47
  def configure():
73
48
  if not matplotlib:
74
49
  return
@@ -77,7 +52,3 @@ def configure():
77
52
  _register_colormaps()
78
53
  except ValueError:
79
54
  pass
80
-
81
- if "AIXVIPMAP_HOME" in os.environ:
82
- _set_aixvipmap_font()
83
- _set_aixvipmap_colors()
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.17b0"
1
+ __version__ = "0.3.1b0"
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.1
2
+ Name: micress-micpy
3
+ Version: 0.3.1b0
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=3xclBJLbbBW9N8w9izFEgFh3S2yKgYzrdQKjJsXftSU,25831
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=o80l_DIF5j_g0KFIiC8SP5DtyipTIK4_NRfkjHjz1Mk,25
8
+ micress_micpy-0.3.1b0.dist-info/LICENSE,sha256=seHdCiArizUoWZ6bEFg6N3K2ZtfPK35wvOwg0kH-f6o,1488
9
+ micress_micpy-0.3.1b0.dist-info/METADATA,sha256=UFUfB1rNxBQxj1OlMO9Qo3YX92weqibYc59tujF8lBo,1229
10
+ micress_micpy-0.3.1b0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
11
+ micress_micpy-0.3.1b0.dist-info/top_level.txt,sha256=RiopkpW0AGNYdtOW2eQUWgm3yHGC13q9pWlHb2alhiE,6
12
+ micress_micpy-0.3.1b0.dist-info/RECORD,,
@@ -1,186 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: micress-micpy
3
- Version: 0.2.17b0
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=GzY0Gva3wp0n6vcKqv9yR3zzcg6ib1GtFzCKv8yXsR0,26777
3
- micpy/geo.py,sha256=pwMDSwG8i9XqmX2DWrSWuvtpni_Z7VLO586LNgEuORk,7366
4
- micpy/matplotlib.py,sha256=SLfk7H1v5L7H_L-a_FqobS0nKY3eSbFRF01h433SpVQ,2090
5
- micpy/tab.py,sha256=QCnfggxRWkKgS9-zGj8kyCjhfUw7QeTgGZWedHh4MTw,3548
6
- micpy/utils.py,sha256=-JS5SRqH4QMD6_pXBKPVw5zPNTbaqkcOUu9ej5Gi0QU,1282
7
- micpy/version.py,sha256=JOpzAb63fvBI_jPvlsPW3cGBI7CByIaM5IXZyr6SXOU,26
8
- micress_micpy-0.2.17b0.dist-info/LICENSE,sha256=seHdCiArizUoWZ6bEFg6N3K2ZtfPK35wvOwg0kH-f6o,1488
9
- micress_micpy-0.2.17b0.dist-info/METADATA,sha256=z2YjOYSHKOAtMtFk1NXS1bHkV31fKPHJcJZllNB1ZjQ,3824
10
- micress_micpy-0.2.17b0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
11
- micress_micpy-0.2.17b0.dist-info/top_level.txt,sha256=RiopkpW0AGNYdtOW2eQUWgm3yHGC13q9pWlHb2alhiE,6
12
- micress_micpy-0.2.17b0.dist-info/RECORD,,