Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 22 additions & 33 deletions src/hcs/hcs.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#include "hcs.h"
#include "defs.limits.h"
#include "hcs/hcs_metadata.h"
#include "lod/lod_plan.h"
#include "ngff/ngff_multiscale.h"
#include "util/prelude.h"
#include "util/strbuf.h"
Expand All @@ -27,7 +26,7 @@ struct hcs_plate

// fovs[row * cols * field_count + col * field_count + fov]
struct ngff_multiscale** fovs;
int nfovs; // total allocated FOV slots
uint64_t nfovs; // total allocated FOV slots

// Plate-level custom attrs.
struct attr_set plate_attrs;
Expand All @@ -47,10 +46,10 @@ row_char(const struct hcs_plate_config* cfg, int r)
return cfg->row_names ? cfg->row_names[r] : (char)('A' + r);
}

static int
static uint64_t
fov_index(const struct hcs_plate* p, int row, int col, int fov)
{
return (row * p->cols + col) * p->field_count + fov;
return ((uint64_t)row * p->cols + col) * p->field_count + fov;
}

static int
Expand Down Expand Up @@ -167,25 +166,13 @@ hcs_plate_create(struct store* store, const struct hcs_plate_config* cfg)
CHECK(Fail, rn_len < 64); // bounded by hcs_plate.row_names buffer
}

// Compute pool size: max shard_inner_count across all LOD levels
struct lod_plan plan = { 0 };
int max_lev = cfg->fov.nlod > 0 ? cfg->fov.nlod : LOD_MAX_LEVELS;
CHECK(Fail,
lod_plan_init_from_dims(
&plan, cfg->fov.dimensions, cfg->fov.rank, max_lev, 0) == 0);

uint8_t na = dims_n_append(cfg->fov.dimensions, cfg->fov.rank);
uint64_t total_slots = 0;
for (int lv = 0; lv < plan.levels.nlod; ++lv) {
uint64_t sic = 1;
for (int d = na; d < cfg->fov.rank; ++d)
sic *= plan.levels.level[lv].dim[d].shard_count;
total_slots += sic;
}
lod_plan_free(&plan);
CHECK(Fail, total_slots > 0);
// One pool per plate keeps its fields of view on one io queue.
uint64_t slots_per_fov = ngff_multiscale_slot_count(&cfg->fov);
CHECK(Fail, slots_per_fov > 0);

struct shard_pool* pool = store->create_pool(store, total_slots);
uint64_t fov_count = (uint64_t)cfg->rows * cfg->cols * cfg->field_count;
struct shard_pool* pool =
store->create_pool(store, fov_count * slots_per_fov);
CHECK(Fail, pool);

struct hcs_plate* p = (struct hcs_plate*)calloc(1, sizeof(*p));
Expand Down Expand Up @@ -216,7 +203,7 @@ hcs_plate_create(struct store* store, const struct hcs_plate_config* cfg)
for (int i = 0; i < cfg->rows * cfg->cols; ++i)
attr_set_init(&p->well_attrs[i]);

p->nfovs = cfg->rows * cfg->cols * cfg->field_count;
p->nfovs = fov_count;
p->fovs = (struct ngff_multiscale**)calloc((size_t)p->nfovs, sizeof(void*));
CHECK(Fail_well_attrs, p->fovs);

Expand Down Expand Up @@ -260,13 +247,15 @@ hcs_plate_create(struct store* store, const struct hcs_plate_config* cfg)
// FOV multiscale sinks
for (int f = 0; f < cfg->field_count; ++f) {
struct strbuf fov_prefix = { 0 };
int fp_rc =
strbuf_appendf(&fov_prefix, "%s/%c/%d/%d", cfg->name, rc, c + 1, f);
int idx = fov_index(p, r, c, f);
p->fovs[idx] = fp_rc == 0
? ngff_multiscale_create_with_pool(
store, pool, strbuf_cstr(&fov_prefix), &cfg->fov)
: NULL;
uint64_t idx = fov_index(p, r, c, f);
if (strbuf_appendf(
&fov_prefix, "%s/%c/%d/%d", cfg->name, rc, c + 1, f) == 0)
p->fovs[idx] =
ngff_multiscale_create_with_pool(store,
pool,
idx * slots_per_fov,
strbuf_cstr(&fov_prefix),
&cfg->fov);
strbuf_free(&fov_prefix);
CHECK(Fail_fovs, p->fovs[idx]);
}
Expand All @@ -276,7 +265,7 @@ hcs_plate_create(struct store* store, const struct hcs_plate_config* cfg)
return p;

Fail_fovs:
for (int i = 0; i < p->nfovs; ++i) {
for (uint64_t i = 0; i < p->nfovs; ++i) {
if (p->fovs[i])
ngff_multiscale_destroy(p->fovs[i]);
}
Expand Down Expand Up @@ -311,7 +300,7 @@ hcs_plate_destroy(struct hcs_plate* p)
write_well_group(p, r, c);

// FOV multiscales flush their own metadata in their destroy.
for (int i = 0; i < p->nfovs; ++i) {
for (uint64_t i = 0; i < p->nfovs; ++i) {
if (p->fovs[i])
ngff_multiscale_destroy(p->fovs[i]);
}
Expand All @@ -336,7 +325,7 @@ hcs_plate_fov_sink(struct hcs_plate* p, int row, int col, int fov)
CHECK_SILENT(Bad, fov >= 0 && fov < p->field_count);
CHECK_SILENT(Bad, well_active(p, row, col));

int idx = fov_index(p, row, col, fov);
uint64_t idx = fov_index(p, row, col, fov);
return ngff_multiscale_as_shard_sink(p->fovs[idx]);
Bad:
return NULL;
Expand Down
109 changes: 66 additions & 43 deletions src/ngff/ngff_multiscale.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,45 @@ ngff_multiscale_flush_fn(struct shard_sink* self)

// --- Shared create logic ---

static int
plan_from_config(struct lod_plan* plan,
const struct ngff_multiscale_config* cfg)
{
int max_lev = cfg->nlod > 0 ? cfg->nlod : LOD_MAX_LEVELS;
return lod_plan_init_from_dims(plan, cfg->dimensions, cfg->rank, max_lev, 0);
}

static void
level_dimensions(struct dimension* out,
const struct ngff_multiscale_config* cfg,
const struct lod_plan* plan,
int level)
{
const struct level_dims* ld = &plan->levels.level[level];
for (int d = 0; d < cfg->rank; ++d) {
out[d] = cfg->dimensions[d];
out[d].size =
(d == 0 && cfg->dimensions[0].size == 0) ? 0 : ld->dim[d].size;
out[d].chunk_size = ld->dim[d].chunk_size;
out[d].chunks_per_shard = ld->dim[d].chunks_per_shard;
}
}

// Spacing a slot range and advancing it must use one count.
static uint64_t
dims_slot_count(const struct dimension* dims, uint8_t rank)
{
uint64_t shard_counts[MAX_ZARR_RANK], chunks_per_shard[MAX_ZARR_RANK];
return dims_compute_shard_geometry(
dims, rank, shard_counts, chunks_per_shard);
}

// Shared init: caller provides a pre-computed LOD plan.
// The plan is consumed (freed on success, freed on failure).
static struct ngff_multiscale*
ngff_multiscale_init(struct store* store,
struct shard_pool* pool,
uint64_t slot_base,
const char* prefix,
const struct ngff_multiscale_config* cfg,
struct lod_plan* plan)
Expand Down Expand Up @@ -237,20 +271,9 @@ ngff_multiscale_init(struct store* store,
(struct zarr_array**)calloc((size_t)plan->levels.nlod, sizeof(void*));
CHECK(Fail_ms, ms->levels);

uint64_t slot_base = 0;
for (int lv = 0; lv < plan->levels.nlod; ++lv) {
struct dimension lv_dims[MAX_ZARR_RANK];

for (int d = 0; d < cfg->rank; ++d) {
lv_dims[d] = cfg->dimensions[d];
if (d == 0 && cfg->dimensions[0].size == 0)
lv_dims[d].size = 0;
else
lv_dims[d].size = plan->levels.level[lv].dim[d].size;
lv_dims[d].chunk_size = plan->levels.level[lv].dim[d].chunk_size;
lv_dims[d].chunks_per_shard =
plan->levels.level[lv].dim[d].chunks_per_shard;
}
level_dimensions(lv_dims, cfg, plan, lv);

struct strbuf level_prefix = { 0 };
int lp_rc = (prefix && prefix[0])
Expand All @@ -277,8 +300,7 @@ ngff_multiscale_init(struct store* store,
strbuf_free(&level_prefix);
CHECK(Fail_levels, ms->levels[lv]);

uint64_t sc[MAX_ZARR_RANK], cps[MAX_ZARR_RANK];
slot_base += dims_compute_shard_geometry(lv_dims, cfg->rank, sc, cps);
slot_base += dims_slot_count(lv_dims, cfg->rank);
}

CHECK(Fail_levels, write_ngff_group_metadata(ms) == 0);
Expand All @@ -303,9 +325,33 @@ ngff_multiscale_init(struct store* store,

// --- Private API ---

uint64_t
ngff_multiscale_slot_count(const struct ngff_multiscale_config* cfg)
{
CHECK(Fail, cfg);
CHECK(Fail, cfg->rank > 0 && cfg->rank <= MAX_ZARR_RANK);
CHECK(Fail, cfg->dimensions);

struct lod_plan plan = { 0 };
CHECK(Fail, plan_from_config(&plan, cfg) == 0);

uint64_t total = 0;
for (int lv = 0; lv < plan.levels.nlod; ++lv) {
struct dimension lv_dims[MAX_ZARR_RANK];
level_dimensions(lv_dims, cfg, &plan, lv);
total += dims_slot_count(lv_dims, cfg->rank);
}
lod_plan_free(&plan);
return total;

Fail:
return 0;
}

struct ngff_multiscale*
ngff_multiscale_create_with_pool(struct store* store,
struct shard_pool* pool,
uint64_t slot_base,
const char* prefix,
const struct ngff_multiscale_config* cfg)
{
Expand All @@ -316,12 +362,9 @@ ngff_multiscale_create_with_pool(struct store* store,
CHECK(Fail, cfg->dimensions);

struct lod_plan plan = { 0 };
int max_lev = cfg->nlod > 0 ? cfg->nlod : LOD_MAX_LEVELS;
CHECK(Fail,
lod_plan_init_from_dims(
&plan, cfg->dimensions, cfg->rank, max_lev, 0) == 0);
CHECK(Fail, plan_from_config(&plan, cfg) == 0);

return ngff_multiscale_init(store, pool, prefix, cfg, &plan);
return ngff_multiscale_init(store, pool, slot_base, prefix, cfg, &plan);

Fail:
return NULL;
Expand All @@ -335,42 +378,22 @@ ngff_multiscale_create(struct store* store,
const struct ngff_multiscale_config* cfg)
{
CHECK(Fail, store);
CHECK(Fail, cfg);
CHECK(Fail, cfg->rank > 0 && cfg->rank <= MAX_ZARR_RANK);
CHECK(Fail, cfg->dimensions);

struct lod_plan plan = { 0 };
int max_lev = cfg->nlod > 0 ? cfg->nlod : LOD_MAX_LEVELS;
CHECK(Fail,
lod_plan_init_from_dims(
&plan, cfg->dimensions, cfg->rank, max_lev, 0) == 0);

uint8_t na = dims_n_append(cfg->dimensions, cfg->rank);
uint64_t total_slots = 0;
for (int lv = 0; lv < plan.levels.nlod; ++lv) {
uint64_t sic = 1;
for (int d = na; d < cfg->rank; ++d)
sic *= plan.levels.level[lv].dim[d].shard_count;
total_slots += sic;
}
CHECK(Fail_plan, total_slots > 0);
uint64_t total_slots = ngff_multiscale_slot_count(cfg);
CHECK(Fail, total_slots > 0);

struct shard_pool* pool = store->create_pool(store, total_slots);
CHECK(Fail_plan, pool);
CHECK(Fail, pool);

// plan ownership transfers to ngff_multiscale_init
struct ngff_multiscale* ms =
ngff_multiscale_init(store, pool, prefix, cfg, &plan);
ngff_multiscale_create_with_pool(store, pool, 0, prefix, cfg);
if (!ms) {
shard_pool_destroy(pool);
return NULL;
}
ms->owns_pool = 1;
return ms;

Fail_plan:
lod_plan_free(&plan);

Fail:
return NULL;
}
Expand Down
12 changes: 9 additions & 3 deletions src/ngff/ngff_multiscale.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@
#include "zarr/shard_pool.h"
#include "zarr/store.h"

// Private: create a multiscale sink that borrows an existing pool.
// The caller owns the pool lifetime — ngff_multiscale_destroy will NOT
// destroy it.
// Count the pool slots one multiscale needs across its levels. Returns 0 when
// the config cannot be used.
uint64_t
ngff_multiscale_slot_count(const struct ngff_multiscale_config* cfg);

// Private: create a multiscale sink that borrows an existing pool, using the
// slots at slot_base and above. The caller owns the pool lifetime —
// ngff_multiscale_destroy will NOT destroy it.
struct ngff_multiscale*
ngff_multiscale_create_with_pool(struct store* store,
struct shard_pool* pool,
uint64_t slot_base,
const char* prefix,
const struct ngff_multiscale_config* cfg);

Expand Down
3 changes: 3 additions & 0 deletions src/zarr/shard_pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ struct shard_pool
{
// Open writer slot for shard data at the given key.
// If the slot has a pending finalize, waits for it first.
// Callers sharing a pool must use slot ranges that do not overlap. Slot
// reuse is how a caller reaches its next shard, so the pool cannot tell it
// from an overlap.
struct shard_writer* (*open)(struct shard_pool* self,
uint64_t slot,
const char* key);
Expand Down
Loading
Loading