API reference¶
The full public surface of the damacy package.
Pipeline¶
Pipeline
¶
Pipeline(
config: Config | None = None,
*,
planner: ChunkPlanner | None = None,
executor: CpuExecutor | CudaExecutor | None = None,
output: BatchSpec | None = None,
queues: QueueLimits | None = None,
pop_timeout_s: float | None = 30.0,
)
Load batches using an injected planner and CPU or CUDA executor.
planner resolves source metadata into owned chunk plans. executor
reads, decodes, and assembles them. output defines the batch tensor;
queues bounds preparation. The planner and executor serve one active
pipeline and may be reused after it closes. Exported tensors retain their
storage.
Pipeline(Config(...)) composes the CUDA pipeline for existing callers.
For CUDA, pass an explicit executor device or make a CUDA context current
before constructing the pipeline. CPU execution requires neither.
Source code in python/damacy/__init__.py
1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 | |
pending
property
¶
True if push() has accepted samples that haven't yet entered
the native lookahead. Becomes False as :meth:pop frees space.
close
¶
Release the underlying handle. Idempotent. Subsequent calls
on the pipeline raise :class:ShutdownError.
Source code in python/damacy/__init__.py
push
¶
push(
samples: Iterable[
Sample | IndexQuery | ResolvedSpatialQuery
],
) -> None
Queue samples for processing. Accepts any iterable (list,
generator, infinite generator, …); large or unbounded sources
are pulled lazily as :meth:pop frees space.
Local validation (shape/rank against Pipeline.output.shape)
raises the matching :class:DamacyError subclass here and
discards the offending iterator. So does an :class:IndexQuery
on a CUDA executor with max_index_bytes=0, which raises
:class:BudgetExceeded; the pipeline keeps running. Errors that
depend on store contents — :class:NotFound, :class:DtypeMismatch,
per-array :class:RankMismatch, decode failures — surface at
:meth:pop instead, since the pipeline fetches metadata
asynchronously after push returns. Once any such error fires,
the pipeline is terminal — rebuild a fresh :class:Pipeline
to recover. Calling :meth:push on a terminal pipeline raises
:class:ShutdownError.
Batching is drop_last=True: only complete batches of
Pipeline.output.samples are emitted, so trailing samples
beyond the last whole multiple are never returned. (Emitting
the ragged final batch is not yet supported — issue #139.)
Source code in python/damacy/__init__.py
pop
¶
pop() -> Batch
Block until the next batch is ready. Returns a
:class:Batch you can hand to torch.from_dlpack (or any
DLPack consumer) — preferably inside a with block.
Raises :class:PoolStarved if no batch arrives within
Config.pop_timeout_s seconds (default 30). Usually that
means tensors from previous batches are still being held —
drop them, or .clone() if you need to keep them. It also
fires if you pop past the batches produced: only
len(pushed) // Config.samples_per_batch exist (drop_last),
and a further pop waits on a batch that is never sealed.
Store-derived errors — :class:NotFound,
:class:DtypeMismatch, per-array :class:RankMismatch,
decode failures — surface here rather than at push. Once any
such error fires, the pipeline is terminal; subsequent calls
re-raise the same status.
Source code in python/damacy/__init__.py
batches
¶
batches(n: int) -> Iterator[Batch]
Pop n batches as an iterator. Each call to :meth:pop
blocks until that batch is ready.
Pair with a with block so the slot is released::
for batch in d.batches(8):
with batch as t:
x = torch.from_dlpack(t)
...
Source code in python/damacy/__init__.py
stats
¶
stats() -> Stats
Cumulative pipeline metrics as a :class:Stats snapshot.
The snapshot is taken at call time; pipeline counters keep
accumulating in the background. Per-stage :class:Metric
fields carry cumulative milliseconds, the best single
observation, input/output byte totals, and a sample count.
Cache hit/miss counters and lifetime totals (batches emitted,
waves emitted, chunks dispatched, …) round out the snapshot.
gpu_bytes_committed reflects the live GPU footprint
counted against Config.max_gpu_memory_bytes; it grows from
wave-init to first pop (lazy batch-output sizing) and stays
flat afterward.
Use :meth:stats_reset to zero the cumulative timing and
metadata-latency counters. gpu_bytes_committed is not reset
— it reflects the live commitment, not a delta.
Raises:
| Type | Description |
|---|---|
ShutdownError
|
If the pipeline has been closed. |
Source code in python/damacy/__init__.py
stats_reset
¶
Zero the cumulative timing counters and per-stage rolling
totals. Cache hit/miss counters and gpu_bytes_committed are
left alone — they reflect live state, not deltas. Metadata
latency counters are reset with the timing counters.
Raises:
| Type | Description |
|---|---|
ShutdownError
|
If the pipeline has been closed. |
Source code in python/damacy/__init__.py
Config
dataclass
¶
Config(
*,
samples_per_batch: int,
sample_shape: Sequence[int],
max_gpu_memory_bytes: int,
dtype: Dtype | str | int = F32,
lookahead_samples: int | None = None,
max_chunk_uncompressed_bytes: int = DEFAULT_CHUNK_UNCOMPRESSED_BYTES,
max_read_op_bytes: int = DEFAULT_READ_OP_MAX_BYTES,
max_index_bytes: int = DEFAULT_MAX_INDEX_BYTES,
host_buffer_waves: int = DEFAULT_HOST_BUFFER_WAVES,
max_chunks_per_wave: int = DEFAULT_MAX_CHUNKS_PER_WAVE,
max_substreams_per_chunk: int = DEFAULT_MAX_SUBSTREAMS_PER_CHUNK,
n_io_threads: int = DEFAULT_IO_THREADS,
metadata_io_concurrency: int = DEFAULT_METADATA_IO_CONCURRENCY,
n_array_meta_cache: int = DEFAULT_ARRAY_META_CACHE,
n_shard_index_cache: int = DEFAULT_SHARD_INDEX_CACHE,
n_chunk_layout_cache: int = DEFAULT_CHUNK_LAYOUT_CACHE,
max_shards_per_sample: int = DEFAULT_MAX_SHARDS_PER_SAMPLE,
device: int | None = None,
pop_timeout_s: float | None = 30.0,
enable_gds: bool | None = None,
numa_strategy: NumaStrategy | str | int = AUTO,
numa_node: int = -1,
metadata_latency: LatencyModel | None = None,
)
All resource caps and pipeline shape, fixed at create time.
Build variants with :func:dataclasses.replace:
>>> import dataclasses
>>> base = Config(samples_per_batch=8, sample_shape=(8, 16),
... max_gpu_memory_bytes=1 << 30)
>>> base.dtype is Dtype.F32
True
>>> dataclasses.replace(base, samples_per_batch=64, lookahead_samples=128).samples_per_batch
64
Validation runs in __init__ so invalid configs fail before we
touch CUDA. The constructor accepts :class:Dtype, an int, or one
of the short or full names (such as "u16" / "uint16") for the
dtype argument; the stored field is always a :class:Dtype.
>>> Config(samples_per_batch=0, sample_shape=(8, 16), max_gpu_memory_bytes=1 << 30)
Traceback (most recent call last):
...
ValueError: samples_per_batch must be >= 1 (got 0)
Attributes:
| Name | Type | Description |
|---|---|---|
samples_per_batch |
int
|
Samples per batch (>= 1). |
max_gpu_memory_bytes |
int
|
Primary GPU budget knob. Hard cap on
GPU memory allocated for wave-resident buffers, decoder
scratch, per-wave fanout SOAs, and batch-output pools.
Required — no default. A value too small for the
requested batch geometry raises :class: |
dtype |
Dtype
|
Destination dtype for assembled batches. |
lookahead_samples |
int
|
User-side push-queue depth in samples. Defaults to two full output batches. |
n_io_threads |
int
|
Bulk data IO worker threads, from 1 to the number of online CPUs. Defaults to 64, so hosts with fewer CPUs must lower it. |
metadata_io_concurrency |
int
|
Async metadata request concurrency (>= 1). |
n_array_meta_cache |
int
|
LRU cap for zarr-metadata entries. Must be
|
n_shard_index_cache |
int
|
LRU cap for shard-index entries. Must be
|
n_chunk_layout_cache |
int
|
LRU cap for per-array blosc1 chunk-layout entries.
Must be |
max_shards_per_sample |
int
|
Declared upper bound on the number of shards
a single sample's AABB may intersect (>= 1). Sizes the
|
max_chunk_uncompressed_bytes |
int
|
Largest uncompressed chunk size the pipeline accepts; 0 selects the C default (512 KB). |
max_index_bytes |
int
|
GPU index storage per batch, eight bytes per index.
Must be zero, which rejects indexed queries at push, or at least
|
max_read_op_bytes |
int
|
Cap on the size of a single coalesced read issued to storage. 0 selects the C default. Tune against your storage tier: small values keep the queue deep and the read pattern fine-grained; large values amortize per-syscall overhead at the cost of latency spikes. |
device |
int | None
|
CUDA device index to bind. |
pop_timeout_s |
float | None
|
How long :meth: |
enable_gds |
bool | None
|
GPUDirect Storage opt-in. |
numa_strategy |
NumaStrategy
|
How to pin pinned-host slabs and worker
threads to a host-NUMA node. :attr: |
numa_node |
int
|
Explicit host-NUMA node when
|
metadata_latency |
LatencyModel
|
Synthetic metadata-store latency model used
for benchmarking prefetch behavior. |
Source code in python/damacy/__init__.py
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 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 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 | |
default
classmethod
¶
default(
*,
samples_per_batch: int,
sample_shape: Sequence[int],
max_gpu_memory_bytes: int,
dtype: Dtype | str | int = F32,
**overrides: Any,
) -> Self
Recommended config for networked-FS (NFS) training.
Convenience over the constructor: the tuning field defaults already carry the cluster-tuned values (see damacy_tuning_defaults). The caller supplies the workload geometry; any field may be overridden by keyword.
Source code in python/damacy/__init__.py
Sample
dataclass
¶
One sample request. aabb is a per-axis half-open interval
list in level-0 voxel indices, in the zarr's stored axis order.
Each axis may be a (start, stop) 2-tuple or a Python slice;
the tuple of slices that numpy.s_[...] produces is accepted
directly. The stored form is always
tuple[tuple[int, int], ...] regardless of how it was spelled,
so equivalent inputs hash and compare equal:
>>> a = Sample(uri="cell.zarr", aabb=[(0, 64), (0, 256), (0, 256)])
>>> b = Sample(
... uri="cell.zarr",
... aabb=[slice(0, 64), slice(0, 256), slice(0, 256)],
... )
>>> c = Sample(
... uri="cell.zarr",
... aabb=[slice(None, 64), slice(None, 256), slice(None, 256)],
... )
>>> a == b == c
True
>>> hash(a) == hash(b) == hash(c)
True
>>> a.aabb
((0, 64), (0, 256), (0, 256))
Bare ints in aabb are rejected so the behaviour stays
consistent with NumPy/zarr indexing semantics
(np.s_[64] means "point 64", not "extent (0, 64)"):
>>> Sample(uri="cell.zarr", aabb=[64, 256, 256])
Traceback (most recent call last):
...
TypeError: aabb axis 0: expected slice or (start, stop) tuple; got int
Slice validation rejects strided slices and unbounded stops:
>>> Sample(uri="cell.zarr", aabb=[slice(0, 64, 2), slice(0, 256), slice(0, 256)])
Traceback (most recent call last):
...
ValueError: aabb axis 0: slice step must be 1 or omitted (got step=2)
>>> Sample(uri="cell.zarr", aabb=[slice(0, None), slice(0, 256), slice(0, 256)])
Traceback (most recent call last):
...
ValueError: aabb axis 0: slice stop is required (got slice(0, None, None))
Source code in python/damacy/__init__.py
IndexQuery
dataclass
¶
Select independent index arrays or contiguous slices along each axis.
Axes form a Cartesian product. Index order and duplicates are preserved; singleton arrays keep their dimension. Indices are zero-based and must be nonnegative. Slices require an explicit stop and a step of one. Empty selections are rejected. Source bounds are checked when metadata arrives.
q = IndexQuery("cell.zarr", selection=([7, 2, 7], slice(4, 8))) q.shape (3, 4) q.indices ((7, 2, 7), None)
Source code in python/damacy/__init__.py
Batch
¶
A contiguous batch in CPU or CUDA memory, ready for consumption.
Use a context manager to release the batch reference::
with pipeline.pop() as batch:
tensor = torch.from_dlpack(batch)
A DLPack consumer retains the storage independently. Its view stays valid after batch release and pipeline close; the pool cannot reuse that buffer until all consumers release it.
For asynchronous CUDA work on a side stream, release with the consumer's stream or event so subsequent output writes wait for that work::
batch.release(event=side_stream)
CPU batches accept only immediate release (event=None).
Source code in python/damacy/__init__.py
release
¶
Release this handle. Exported tensors keep their buffers. Idempotent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
object | None
|
If
|
None
|
Raises:
| Type | Description |
|---|---|
DamacyError
|
If the deferred-release CUDA call fails. On failure the slot is still released back to the pool; damacy logs and re-raises rather than silently leaking. |
Source code in python/damacy/__init__.py
Components¶
FileMetadataReader
¶
Configure a separate asynchronous queue for filesystem metadata reads.
Holds settings only; metadata providers copy them, so it can be shared.
Source code in python/damacy/__init__.py
ZarrMetadata
¶
ZarrMetadata(
*, reader: FileMetadataReader, cache: MetadataCache
)
Provide Zarr v3 array metadata and shard indexes for sample URIs.
Holds settings only. Each planner copies them and keeps its own caches, so planners can share one instance.
Source code in python/damacy/__init__.py
ChunkPlanner
¶
ChunkPlanner(
*,
metadata: ZarrMetadata,
limits: PlanLimits | None = None,
)
Prepare owned chunk plans from rectangular queries and Zarr metadata.
Source code in python/damacy/__init__.py
FileReader
¶
Read bulk encoded chunk data through a bounded filesystem I/O queue.
readahead=True preserves the operating system's default behavior.
False disables automatic file readahead for this reader's bulk reads
on Linux and macOS. This applies to buffered CPU and CUDA reads, including
cuFile compatibility mode. Metadata reads use a separate reader. It does
not clear or bypass the page cache.
Source code in python/damacy/__init__.py
CpuExecutor
¶
CpuExecutor(*, reader: FileReader, limits: CpuLimits)
Decode and assemble batches in ordinary RAM without using CUDA.
Source code in python/damacy/__init__.py
CudaExecutor
¶
CudaExecutor(
*,
reader: FileReader,
limits: CudaLimits,
device: int | None = None,
numa_strategy: NumaStrategy | str | int = AUTO,
numa_node: int = -1,
enable_gds: bool | None = None,
)
Decode and assemble batches on a CUDA device.
Source code in python/damacy/__init__.py
Spatial queries¶
NgffImage
dataclass
¶
NgffImage(
uri: str | PathLike[str],
*,
reader: FileMetadataReader,
multiscale_index: int,
limits: NgffLimits | None = None,
)
Bases: _NativeImage
Load an immutable OME-Zarr 0.5 image description through the given reader.
Loading reads the image group's metadata and each level's array metadata.
It finishes before returning.
multiscale_index explicitly selects an entry in ome.multiscales.
Source code in python/damacy/_spatial.py
resolve
¶
resolve(
query: SpatialQuery, *, shape: Iterable[int]
) -> ResolvedSpatialQuery
Resolve source geometry without I/O, batching, or output allocation.
Source code in python/damacy/_spatial.py
NgffAxis
dataclass
¶
An axis in the array's dimension order, with its declared NGFF unit.
NgffLevel
dataclass
¶
NgffLevel(
uri: str,
shape: tuple[int, ...],
scale_to_reference: tuple[float, ...],
origin_reference_index: tuple[float, ...],
)
A source array and its voxel-corner mapping into reference-level indices.
SpatialQuery
dataclass
¶
SpatialQuery(
*,
output_to_reference: Iterable[Iterable[float]],
sampler: Sampler,
level: int | Literal["auto"] = "auto",
)
Map a fixed output grid into reference-level voxel-corner coordinates.
output_to_reference has rank rows and rank + 1 columns. The last
column is the translation; the preceding columns are the linear map.
Rows and columns follow NGFF array axis order, including time/channel
dimensions, which only allow identity plus integer translation.
level='auto' selects the coarsest level no coarser than the output
spacing in any direction, falling back to level zero for upsampling.
Source code in python/damacy/_spatial.py
ResolvedSpatialQuery
dataclass
¶
Owned source geometry returned by NgffImage.resolve().
Aligned results can be pushed to a Pipeline. Other results raise UnsupportedOperation when pushed; their geometry remains inspectable.
Source code in python/damacy/_spatial.py
Sampler
dataclass
¶
Sampler(
filter: Literal["nearest", "linear"] = "linear",
boundary: Literal[
"error", "constant", "clamp"
] = "error",
constant_value: float = 0,
)
Point interpolation and treatment of source indices outside spatial axes.
Nearest selects floor(source_corner). Linear interpolates between
centers at index + 0.5. Constant extends the source with
constant_value; clamp repeats the closest edge value; error rejects
a query needing out-of-bounds samples. No extra antialias filter is applied.
NgffLimits
dataclass
¶
Maximum levels and total JSON bytes read when loading an image.
Output and limits¶
BatchSpec
dataclass
¶
BatchSpec(
samples: int,
shape: Sequence[int],
dtype: Dtype | str | int = F32,
)
Fixed output tensor geometry, independent of the source metadata.
Source code in python/damacy/__init__.py
QueueLimits
dataclass
¶
Bounds for pending samples and complete plans awaiting execution.
MetadataCache
dataclass
¶
Cache capacities used by a metadata provider.
PlanLimits
dataclass
¶
PlanLimits(
max_chunks: int = 16384,
max_chunk_bytes: int = 2 << 20,
max_shards_per_sample: int = 64,
max_plan_bytes: int = 64 << 20,
)
Per-batch planning limits; max_chunks counts uses before deduplication.
CpuLimits
dataclass
¶
CpuLimits(
max_memory_bytes: int,
decode_workers: int = 8,
max_encoded_chunk_bytes: int = 4 << 20,
max_decoded_chunk_bytes: int = 2 << 20,
chunks_per_input_buffer: int = 256,
)
Bound input, decoded data, codec workspace, and output storage.
CudaLimits
dataclass
¶
CudaLimits(
max_gpu_memory_bytes: int,
max_index_bytes: int = DEFAULT_MAX_INDEX_BYTES,
chunk_layout_entries: int = 256,
max_chunk_bytes: int = DEFAULT_CHUNK_UNCOMPRESSED_BYTES,
max_read_bytes: int = DEFAULT_READ_OP_MAX_BYTES,
host_buffer_waves: int = DEFAULT_HOST_BUFFER_WAVES,
max_chunks_per_wave: int = DEFAULT_MAX_CHUNKS_PER_WAVE,
max_substreams_per_chunk: int = DEFAULT_MAX_SUBSTREAMS_PER_CHUNK,
)
GPU buffer limits, wave geometry, and codec-layout cache capacity.
max_index_bytes is index storage per batch, eight bytes per index. It
must be zero or at least 8 * samples * sum(shape) of the output, so a
batch never runs out; a smaller value raises :class:BudgetExceeded from
:class:Pipeline. With zero, :meth:Pipeline.push rejects an
:class:IndexQuery with :class:BudgetExceeded. Storage counts against
the total GPU budget; only what the output shape needs is allocated.
Value types¶
BatchInfo
dataclass
¶
BatchInfo(
device_ptr: int,
shape: tuple[int, ...],
dtype: Dtype,
ready_stream: int,
batch_id: int,
device_type: DeviceType = CUDA,
device_id: int = 0,
)
Batch geometry and memory location. CPU data is ready for host access.
Metric
dataclass
¶
Metric(
name: str,
ms: float,
best_ms: float,
input_bytes: float,
output_bytes: float,
count: int,
)
One pipeline-stage metric. ms is cumulative; best_ms is the
best single observation (large sentinel when no samples yet).
Stats
dataclass
¶
Stats(
plan: Metric,
io: Metric,
input_transfer: Metric,
decode: Metric,
post_decode: Metric,
decode_gap: Metric,
assemble: Metric,
bind_wait: Metric,
pop_wait: Metric,
array_meta_hits: int,
array_meta_misses: int,
shard_index_hits: int,
shard_index_misses: int,
chunk_layout_hits: int,
chunk_layout_misses: int,
metadata_latency_ops: int,
metadata_latency_stat_ops: int,
metadata_latency_submit_ops: int,
metadata_latency_active: int,
metadata_latency_max_active: int,
metadata_latency_total_sleep_ns: int,
metadata_latency_max_sleep_ns: int,
metadata_backend_read_jobs: int,
metadata_backend_read_active: int,
metadata_backend_read_max_active: int,
batches_emitted: int,
waves_emitted: int,
chunks_planned: int,
chunks_to_load: int,
chunks_dispatched: int,
reads_issued: int,
worker_steps: int,
gpu_bytes_committed: int,
host_bytes_committed: int = 0,
)
Cumulative pipeline metrics. Reset with :meth:Pipeline.stats_reset.
Enums¶
DeviceType
¶
Bases: IntEnum
DLPack memory location for a batch.
Dtype
¶
Bases: IntEnum
Destination dtype for assembled batches. Sources are cast to this type.
coerce
classmethod
¶
Accept an enum, its integer value, or a short or full dtype name.
>>> Dtype.coerce("f32") is Dtype.F32
True
>>> Dtype.coerce("BFloat16") is Dtype.BF16
True
>>> Dtype.coerce(Dtype.F32) is Dtype.F32
True
>>> Dtype.coerce("nope")
Traceback (most recent call last):
...
ValueError: unknown dtype: 'nope'
Source code in python/damacy/__init__.py
Status
¶
Bases: IntEnum
Mirrors enum damacy_status.
Exceptions¶
DamacyError
¶
Bases: DamacyError
Base class for all damacy errors. .status is a :class:Status;
.what names the failing stage (e.g. "create", "pop").
TryAgain
¶
InvalidArgument
¶
NotFound
¶
DeviceType
¶
Bases: IntEnum
DLPack memory location for a batch.
DtypeMismatch
¶
RankMismatch
¶
StorageError
¶
Bases: DamacyError
Read or open failure on a shard file. Named StorageError rather
than IOError to avoid shadowing :class:builtins.IOError for
callers that from damacy import *.
DecodeError
¶
NativeCudaError
¶
OutOfMemory
¶
Bases: DamacyError
Host allocation failed (malloc/calloc returned NULL). Distinct
from :class:BudgetExceeded — the OS denied memory, the
configured cap was not the limiting factor.
BudgetExceeded
¶
Bases: DamacyError
A configured cap is too small to satisfy the request. Most
commonly: Config.max_gpu_memory_bytes cannot fit the
requested batch geometry, or a chunk's uncompressed size exceeds
Config.max_chunk_uncompressed_bytes. Raise the relevant cap
and retry.
UnsupportedOperation
¶
ShutdownError
¶
PoolStarved
¶
Bases: DamacyError
Raised when :meth:Pipeline.pop waits longer than
Config.pop_timeout_s for the next batch.
The usual cause is your loop holding on to tensors from previous
batches — for example by stashing them in a list — which keeps
damacy from reusing that memory. Drop those references before
the next pop(), or call .clone() if you need to keep
them.
Logging¶
set_log_level
¶
Set the threshold for the C-side stderr sink (TRACE=0..FATAL=5).
The Python sink (logging.getLogger("damacy")) is independent.