Skip to main content

Auto-Organotypic

Automated analysis of bioluminescence and fluorescence time-lapse recordings from organotypic suprachiasmatic nucleus (SCN) slices.

The SCN is the master circadian pacemaker of the anterior hypothalamus. Kept alive as an organotypic slice and imaged for days, it reports its own timekeeping as a movie. Auto-Organotypic turns those movies into per-cell and whole-tissue rhythm measurements.

What is here

The first step, and the one that usually costs a person an afternoon with a mouse: finding the tissue. One call reads a registered recording, draws the accepted two-lobe outline, rotates it so both lobes sit the same way up in every recording, and writes a square crop centred on the outline — with no hand-drawn region of interest anywhere in the chain.

from auto_organotypic import outline

result = outline.automatic(
    "meanred_MCG_04_1_595.tif",
    valid_mask="validfield_MCG_04_1_595.tif",
    output_dir="out",
)
result["output"]           # the two-lobe label image, oriented
result["cropped_output"]   # tight square crop around the SCN centre
result["report"]           # every setting, hash and measurement of that run

A registered ImageJ or OME hyperstack works as well as a two-dimensional time mean. scn_channel picks the outline channel and scn_z the depth plane, both using one-based ImageJ numbering; scn_time is "mean" (the default), "max", or a one-based frame number such as 320. Whichever plane is chosen determines one orientation and one crop, which are then applied to every plane in the stack. For a flattened RGB TIFF, scn_channel=1, 2, or 3 selects its red, green, or blue sample respectively. For a large online-only hyperstack, selected_source_only=True writes only the chosen two-dimensional plane and hash_source=False avoids downloading the rest merely to calculate a full-file hash; the selected plane and all outputs remain hashed. write_oriented_source=False keeps the orientation but skips writing the full oriented copy of the stack, which costs a second streaming pass over every plane and a second full-size file — wasted if the next step reads only the crop. The transform stays in the report either way.

lobes defaults to 2, preserving the accepted method. Set lobes=1 (or --lobes 1) to keep a single detected or drawn region whole, or lobes="any" to let the midline evidence return either one or two. A one-lobe outline cannot infer anatomical up from a gap between lobes, so it keeps source orientation unless an angle is supplied; its square crop and lobe_1 trace still run.

A folder of recordings

from auto_organotypic import batch

records = batch.run(
    "D:/recordings",                 # a folder, a file, or any list of paths
    output_root="D:/scn_out",        # each recording gets output_root/<stem>/
    scn_channel=1, scn_time="mean",  # shared by every recording
    per_source={"MCG_04_1_595": {"scn_channel": 2}},   # what differs
    workers=8,                       # recordings at once; unset chooses
    on_progress=lambda record: print(record["input"], record["ok"]),
)

workers is where a folder's wall-clock lives. The outline is single-threaded and around thirty seconds for a 512×512 field, against four milliseconds of file reading, so nothing about how the images are stored or passed in will move it. Left unset it is half the machine's cores, at most eight. A 27-recording library, measured on sixteen logical cores:

processes wall clock speed-up peak memory
1 434 s 1.00× 0.2 GB
2 196 s 2.21× 0.4 GB
4 141 s 3.07× 0.8 GB
8 90 s 4.83× 1.5 GB

The cap is memory, not speed: the gain is still real at eight, and each process holds its own recording, so a folder of stacks costs far more per process than the single frames measured there. Pass workers=1 for one process, or a bigger number on a machine with the memory for it.

On Windows a process pool needs the calling script to have an if __name__ == "__main__": guard — without one, each new process re-imports the script and runs it again. A run that chose its own worker count meets that by saying so and finishing in one process, so a script that worked before still works. A run that was told workers=8 fails loudly instead — every recording comes back carrying the error — because a caller who named a number is owed the news that it did not happen.

batch.run is a separate call rather than a list argument on outline.automatic, because the two want opposite things when something goes wrong: one recording should raise, a folder should finish and then tell you which three failed. Each record carries ok, skipped, the report path, and either the full single-call result or the error with its traceback; the same records are written to batch_scn_outline_manifest.json.

The same run from a shell:

python -m auto_organotypic D:/recordings -o D:/scn_out --workers 8 --scn-channel 1
python -m auto_organotypic D:/recordings --dry-run     # list what would be processed

Installing also puts a auto_organotypic command on the path. It is argparse over batch.run and nothing else — every flag is one of that call's keywords, one for one. It prints a line per recording as it finishes and exits 0 when every recording produced output, 1 when any failed.

Discovery keeps the recordings and drops the valid-field masks, the partial writes and anything an earlier run wrote — from names alone, because opening a folder's worth of online-only files to read their metadata downloads the folder. A re-run skips any recording whose report already exists, so an interrupted folder resumes where it stopped; pass overwrite=True to redo the work.

Crops are "tight", "standard" (the default), "wide", or an exact crop_size_px. Every preset is checked to keep every outline pixel, and a custom size that would cut the outline is refused rather than silently clipped.

An instrument's folder, straight in

A PyIncucyte or PyLV200 download writes a folder of TIFFs and a manifest beside them. read_manifest turns that manifest into recordings, and outline_plan turns those into the two arguments batch.run already takes:

from auto_organotypic import batch, outline_plan, read_manifest

recordings = read_manifest("D:/pull")        # either instrument, same records
sources, per_source = outline_plan(recordings, channel="red")
batch.run(sources, per_source=per_source, output_root="D:/scn_out")

channel takes a name — "red" also finds TRITC and mCherry, "phase" finds BF and brightfield — or a one-based number. The index it produces describes the stack as written, not the vessel, so a well that missed a channel does not shift every name after it by one.

No extra is needed to read a finished pull. A manifest is plain JSON, so the four dependencies above are enough. What a manifest says is worth looking at before a long run:

auto-organotypic plan D:/pull
VID1234_A1_PhRd_20260820.tif  well A1  TCYX  393 frames  [Phase, Red]  -> channel 2

The ClockCyteR cell grid, without Fiji

ClockCyteR.FIJI is four ImageJ macros that prepare a recording for ClockCyteR.spatial. The fourth lays a grid of 5 x 5 pixel squares over the frame and measures every one of them in every frame of every channel. cellgrid is that macro in NumPy:

from auto_organotypic import cellgrid
grid = cellgrid.extract("D:/scn/L_1.tif", "D:/scn/L_1_results")

It writes the same two files the macro does, grid_centroids.csv and one Ch<n>_<n>_grid_vals.csv per channel, and needs no Fiji.

A path, a Series or a (frames, channels, y, x) array all work — but give it the path for anything long. Frames are measured and written a chunk at a time, so nothing scales with the length of the recording: 4000 frames of a three-channel 175 x 291 stack is a 1.2 GB file and 72 MB of memory. Handing it an array instead means having the whole recording resident first, which past a certain length is not a slow path but no path at all.

The edge squares are the whole difficulty. The macro's grid is sized with a float division and makeRectangle clips the last square to the image, so the final column and the final row come out narrower than five pixels: their means are over fewer pixels and their centroids sit off the lattice. Every slice of the published toy dataset is clipped, two of the three in both axes, so the obvious reshape(-1, 5, 5).mean() gets the square count, the centroids and the edge means all wrong. Laying it out the way the macro does reproduces all three published grid_centroids.csv files byte for byte.

To run the macros themselves instead, point AUTO_ORGANOTYPIC_FIJI at the launcher:

from auto_organotypic import fiji
fiji.extract_cellgrid("D:/scn")                 # macro 4
fiji.define_roi("D:/scn/hemis/Left")            # macro 3, re-using saved outlines

Only macro 4 is unattended in the first place. Macros 2 and 3 automate the second pass, off regions somebody drew once, and say so before starting Fiji if those are missing; macro 1 needs a line drawn on every stack and saves nothing to re-use, so auto_organotypic.orientation is the answer there rather than a wrapper.

Macro 4 measures in blocks of frames. ImageJ's Multi Measure gets slower the more rows are already in the Results table, so handing it a whole recording at once is the expensive way to ask. Measured across two grids, the cost per square sits at a median of about 74 microseconds while the table stays under roughly 50,000 rows and rises to about 272 by 230,400 — some 3.7 times worse. Blocks keep a run on the flat part: for a 4800-square grid over 48 frames and two channels the whole folder went from 131 s to 42 s. The blocks are stitched back into the one published Ch<n>_<n>_grid_vals.csv before extract_cellgrid returns, byte for byte what the unblocked macro wrote. The run-to-run spread on those timings is wide, so treat them as a floor and a direction rather than a law, and do not extrapolate past 230,400 rows. Even at that floor ImageJ is around 80 times slower than cellgrid — 42 seconds against 0.5 for that stack — so the macro route is for confirming the NumPy one, not for work.

The whole sequence

auto-organotypic pipeline D:/pull -o D:/scn_out --channel red
auto-organotypic pipeline D:/pull --dry-run     # resolve every stage, run none
auto-organotypic stages                         # what is installed on this machine

Fourteen stages, in order: acquire (PyIncucyte or PyLV200), index, trim_before_crop, broad_crop, trim, register, split, outline, image, grid, video, video_grid, trace (one call covering the cosmic-ray rule, a trace per region, the instrumental control and the rhythm verdict) and review (the quality check, below). Only the acquire stage needs another package, and the two movie stages need auto-organotypic[video] for its encoder. The two trims and the split do nothing at all unless asked, so a run that does not mention them is the run it always was.

Each is reached by dotted name, so a stage whose package is not installed is reported as pending at the top of a run rather than raising four steps in; --allow-pending runs the rest. A stage is also reported pending when a library it imports once it is already running is missing — registration reaches for scikit-image inside its estimator, and a run that discovered that forty minutes in would be the exact failure this design exists to prevent.

Every stage appends what it did to auto-organotypic-pipeline-run.json, written after each stage rather than at the end, because the stage that fails is the one whose record matters.

Seven stages are opt-in. acquire runs only when an --experiment is given; without one the folder is taken as already pulled. broad_crop runs with --broad-crop, and rewrites every stack it crops, which is not something to do to a plate somebody already framed by hand. Four are the display exports, below, and the seventh is review.

Pictures and movies out of the same run

auto-organotypic pipeline D:/pull -o D:/scn_out --image --grid --video --video-grid
auto-organotypic pipeline D:/pull -o D:/scn_out --grid-option moments=12 --grid-option columns=4
auto-organotypic pipeline D:/pull -o D:/scn_out --video-option hours_per_second=24
Flag Makes Lands in
--image one still per recording visual/images/A1_mean.png
--grid every recording tiled into one sheet visual/images/grid_12moments.png
--video one movie per recording visual/videos/A1_24hps.mp4
--video-grid every recording playing at once in one movie visual/videos/grid_24hps.mp4

The trace stage now mirrors that image structure after it measures the accepted outline: one visual/traces/A1_trace.png per recording and one visual/traces/all_slices_traces.png comparison grid. A second collective view, visual/traces/all_slices_trace_supergraph.png, overlays every recording and emphasizes their average. The numeric source stays under traces/A1/; figures are display-only and are never fed back into an analysis.

auto-organotypic pipeline D:/pull -o D:/scn_out \
  --trace-option primary=48h \
  --trace-option circadian=periodogram \
  --trace-option xtick_hours=12 \
  --trace-option visual_normalise=zscore \
  --trace-option visual_signal=detrended \
  --trace-option visual_average=median

The default is the pixel-weighted union of both outline lobes, a 24 h rolling detrend, periodogram plus cosinor annotation, x ticks every 24 h, and per- recording 0–1 normalization. visual_regions=all draws the two numbered lobes as well.

visual_signal chooses which series the figure shows, and the default is cosinor: the fitted cosine on its own, normalized to fill 0–1, with the measured wobble the fit already discounted left out. It is the shape the analysis concluded, so a row of recordings compares on phase and period without a reader having to see past the noise. Every measured series is opt-in from there. detrended draws the detrended trace; raw draws the measured trace, with its trend still in it; both draws the measured trace behind the detrended one so you can see how much the baseline took out. The measured trace is put on the detrended trace's own axis by the same divisor — each region's window mean — so it keeps the shape it was measured with. Whichever series is drawn on its own is also the one the normalization is computed from, so a raw-only figure fills its axis with the raw range rather than someone else's; the overlay is the one case that normalizes both to the measured trace, because two scales would stretch the detrended trace back over the trace it came out of. A raw-only figure carries no cosinor curve, since the fit was made on the detrended values.

A cosinor-only figure needs a fit. Where there is none — circadian=none or circadian=periodogram was asked for, or the periodogram found no peak to fit — the figure falls back to detrended rather than drawing a curve for some recordings and a blank for others; in a grid or a supergraph one unfitted recording takes the whole figure with it, so every panel keeps showing the same thing. The report records requested_signal beside the signal that was actually drawn.

The supergraph defaults to the arithmetic mean over the time interval shared by every recording. visual_average=median is resistant to isolated extreme recordings; visual_average=trimmed_mean removes visual_trim_fraction from each tail at every time point. Set visual_time_range=union to retain the longest recording and average whichever recordings are present there. Detrending and region measurement live in auto_organotypic.region_trace; the display code lives in auto_organotypic.trace_plot. Neither reaches the external single-cell dLuc pipeline.

All four run after the outline and before the trace, so "index it, outline it and show me" is a complete run that never pays for the trace half. Files are named for the well and for what they are of, rather than for source stems that reach 149 characters by this point in the pipeline.

Every keyword of auto-organotypic image, grid and video is reachable, as --<stage>-option KEY=VALUE, repeatable. Values are read as JSON where they parse as it, so moments=12 is a number and shared_range=false is false, and as plain text otherwise, so lut=fire works without a quoting puzzle. In Python it is one mapping per stage:

run_pipeline(folder, image=True, video_grid=True,
             image_options={"when": "max", "lut": "fire"},
             video_grid_options={"columns": 4, "hours_per_second": 24})

Giving a setting is itself the asking, so --grid-option moments=12 turns the stage on. A key the export will not take is refused by name before the run starts, checked against the function's own signature — misspelling colums costs a second rather than the outline stage's minutes.

Four things are filled in from what the run already knows, each only as a default that anything you pass overrides: where the file goes, what it is called, the frame interval from the recording's own metadata, and the well names on a sheet's or a mosaic's tiles.

The mosaic is the one with opinions, because tiling recordings that disagree is how a movie comes out looking right and being wrong:

  • Unequal windows are refused. Nothing is dropped or held to make them look aligned. on_length=shortest is the explicit opt-out for a plate where one well stopped early, and it names what it cut. There is no "hold the last frame" at all: a finished recording still sitting there looking alive is a claim about the biology.
  • Two acquisition intervals cannot claim one biological speed. Asking for hours_per_second across them is refused; a plain fps claims nothing and is allowed, and each tile keeps its own recording's timestamp.
  • shared_range is one contrast per recording across its whole window, the same axis the still sheet uses — so a dim phase stays dim and a late bright phase is not clipped by an early frame. It is not one range across the plate: each well ranges itself, so comparing brightness between wells needs an explicit display_range=(black, white).
  • Accepted outlines can be drawn over the tiles, which the still sheet also does.
  • Every tile may start at its own hour zero. align="onset" finds each recording's first rise and begins its tile there, so nine slices that came up at nine different times play in phase. What that does not change is the frame-for-frame advance: two cadences still drift apart after the frame they were aligned on, which is the same reason hours_per_second is refused across them.

A plate is streamed, never assembled: one frame is painted from each recording, tiled, encoded and dropped before the next is read, so the memory is one row of frames rather than one row of stacks.

The mosaic is also its own subcommand, auto-organotypic video-grid, for a folder that never went through the pipeline.

Nothing any of the four writes may be measured from. Each marks its output as a display artefact, the run record says so per stage, and none of them touches the recordings it was handed — a lookup-table painted 8-bit picture reaching the trace stage would be the one failure that looks like a result.

Did it go well? The review

auto-organotypic pipeline D:/pull -o D:/scn_out --review   # as part of the run
auto-organotypic review D:/scn_out                         # or afterwards, on its own

Every stage already writes down what it decided and how sure it was: the crop detector's reason for keeping a frame whole, registration's residual against its own threshold, the outline's verification line and open_questions, the accepted orientation rule's three top-end cues and which of them agreed, the square crop's clipped-pixel count, the instrumental control's verdict. That is six files per recording, in JSON and CSV, so on a plate of ninety-six nobody reads any of them.

The review reads them and draws them into visual/review/:

Panel Answers
review_scorecard.png every recording against every check, worst first
review_geometry.png one column per recording, one row per stage of the geometry — registered mean, oriented square crop, crop with the outline over it
review_registration.png each frame's residual, against the threshold the run recorded
review_orientation.png which end of the medial gap each of the three cues chose, and how square the rotation came out
review_rhythm.png period and power per region, and the instrumental control
review.html all five inlined, plus every number as searchable text

Five words and no sixth: ok (the stage's own gates passed and it left no question open), check (it finished, and marked its own answer as one to look at), given (a person answered instead of the method), failed, and not run.

check is the one worth being clear about. It does not mean something went wrong. An automatic outline on a recording nobody has hand-checked is check every time — nothing is wrong with it, and nothing has confirmed it either. The orientation is where it earns its keep: a slice can be outlined perfectly and turned upside down, nothing downstream notices because a lobe trace is a lobe trace either way, and the cue vote is the number that would have told you.

The review re-judges nothing. A verdict here is the verdict the stage recorded, read back and given a colour — so it cannot disagree with a report, and a run from last month says the same thing to somebody who was not there. The picture strip resizes nothing either: a pixel means the same distance everywhere on the sheet, which is why a plate wraps into blocks of twelve (--columns N) rather than shrinking to fit.

auto-organotypic review D:/scn_out --crop tight       # fill each tile with tissue
auto-organotypic review D:/scn_out --panel geometry --strip-row raw \
    --strip-row cropped --strip-row outlined

--crop decides what the strip's tiles are framed on, and it is the one real choice:

  • none (the default) draws each row's whole frame, so the square crop visibly sits inside the frame it was cut from. This shows what the crop did.
  • tight, standard or wide frame every tile on the outline at the accepted crop's own scales (1.05, 1.25, 1.50 — imported from outline/crop.py, not copied), so the tissue fills the tile and the empty field goes. This shows what the boundary and the rotation are, which is what the strip is usually opened for.

Each row's box is the outline's own bounding box walked back into that row's frame — through the rotation, the registration overlap and the coarse box, each hop a number some stage recorded. A row whose walk is missing a step is drawn whole rather than guessed at, and the report names what every row was framed from.

--strip-row raw adds the pre-registration recording as a row. It is the only row that reopens a full source stack, so it is the slow one and is off unless asked. Display only, like everything else in this section; it needs auto-organotypic[image] and an output root, since a review is about a run and not about a recording.

Analysing part of a recording

auto-organotypic pipeline D:/pull -o D:/scn_out --trim 0..144h
auto-organotypic pipeline D:/pull -o D:/scn_out --split-at 72h
auto-organotypic pipeline D:/pull -o D:/scn_out --split baseline=0..70h --split drug=74h..144h

Both are said the same way: frames (1..288), hours (0..72h) or days (2d..5d), with .. between the two ends and either end left off to mean the beginning or the end. A time window is half-open, so 0..72h and 72h..144h are the two halves of a six-day recording and no frame is in both or in neither.

--trim 0..144h drops frames nothing should see. Use it when the microscope lost focus or the slice drifted out of the field: registration crops its export to the box every frame still covers, so a hundred bad frames at the end shrink the field of view for the whole recording, including the good days. It reruns registration, so it costs hours.

--split-at 72h produces two complete sets of outputs from one recording — traces/A1_start-72h/ and traces/A1_72h-end/ — measured through one registration, so the two are comparable. It reruns the outline and the trace, so it costs about a minute. Name the spans if you would rather read the folders later: --split baseline=0..70h --split drug=74h..144h, which also drops the four hours around a media change without leaving a gap inside either series.

A window said for one well beats the plate's, through the command that already exists for saying so:

auto-organotypic correct D:/pull --stage trim --only B2 --value '"0..120h"'
auto-organotypic correct D:/pull --stage split --only B2 --value '["0..48h", "48h.."]'

Moving a cut point writes new folders and leaves the old ones alone — nothing here deletes results. The run says which segment folders it did not write this time, so they are not read as current a month later.

--trim-before-crop puts the window on the stage before the broad crop instead of the one after it. It is for the recording whose bad frames fooled the crop detector, and it costs a full-frame copy of every recording it touches rather than a cropped one.

The broad crop

auto-organotypic crop D:/pull --mode wide --dry-run   # how much would wide take off?
auto-organotypic crop D:/pull -o D:/cropped --mode wide
auto-organotypic crop D:/pull --when max              # draw the box from other frames
auto-organotypic pipeline D:/pull --broad-crop --broad-crop-mode standard

--dry-run measures every recording, reports the box the chosen mode would draw, and writes nothing — worth running before committing a plate to a nine-day experiment. Without -o, crops go to AI_Exports/<stem>_broad_crop/ beside the source, which is where the folder form already knows not to look for inputs.

Registration costs what the frame costs. An Incucyte pull is 1152 × 1536 and the slice is about a fifth of it, so aligning the empty four fifths costs the same as aligning the tissue. This stage finds the tissue once and boxes it.

tight, standard and wide are 1.5, 2.0 and 2.8 times the box the tissue occupies — larger than the region crop's numbers because they scale the whole slice rather than the SCN outline, and because the surround they leave is what the accepted outline later reads its own background from. standard = 2.0 is not a taste: three Incucyte wells exist in both the full frame and a crop a person drew for this exact purpose, and those hand crops are 1.75 – 2.04 times the tissue box this finds.

--when picks which frames the box is drawn from, using the same three answers as --scn-time: mean, max, or a one-based frame number. Left alone it uses this stage's own rule — a median inside each of five chunks spread across the recording, then the maximum across them — which survives both a cosmic ray and the circadian trough while reading two dozen planes instead of all of them. Reach for --when when the default framed something you did not want; max holds the union of everywhere a drifting slice went, and a frame number is for the recording where you know which frame to look at.

It never cuts tissue, and it does not get tighter as the signal gets weaker. Otsu's threshold on the frame's own histogram says what is certainly tissue; a second, lower one says what is certainly background; and the box holds every connected piece that reaches the first while spreading out to the second. Both levels are read off the frame, so nothing has to be chosen in advance. Where the tissue fills the frame, or the well is empty, or the histogram cannot separate anything, it keeps the whole frame and the run record says why.

Four settings, three of them fractions of the frame and the fourth a compute budget — down from eleven, because Otsu now reads off the histogram what three of those settings used to have to be told.

single_object=False is the default and should stay off unless the stage did not move: the detection frame is a maximum across time chunks, so a slice that drifted shows as two blobs and the union of them is where the slice was. Turn it on for a large frame with bright debris elsewhere in the well. region=(x0, y0, x1, y1) crops to exactly what you name — obeyed, not judged, though the record still says whether it holds the tissue.

Measured across 75 real recordings spanning a 138-fold range of signal-to-noise ratio: 16 of the 75 cropped, none of the 16 hand-drawn outlines clipped, and the ten viral-reporter recordings that the first version of this stage was quietly cutting now keep every pixel of their tissue.

Drawing it yourself, or just saying it

auto-organotypic pipeline D:/pull --broad-crop-rois D:/rois/crops    # the broad crop
auto-organotypic pipeline D:/registered --stages outline     --outline-rois D:/rois/outlines                           # the SCN outline
auto-organotypic crop D:/pull --rois D:/rois/crops --rois-if-missing raise
auto-organotypic crop D:/pull --region 300,220,900,760              # one box, whole plate
auto-organotypic D:/registered --angle 74 --crop-region 40,40,360,360
auto-organotypic D:/registered --orient-rois D:/rois/up --outline-crop-rois D:/rois/squares

Four measured steps take the answer instead. Each has a folder of drawings and a value for the whole run, and they are separate options because they are cut from different frames:

step drawn said
broad crop --broad-crop-rois --broad-crop-region x0,y0,x1,y1
SCN outline --outline-rois
which way is up --orient-rois --angle DEG, --flip
square crop --outline-crop-rois --outline-crop-region x0,y0,x1,y1

Every flag says which crop, because pipeline runs two of them. There is no --crop-rois there: it used to mean the broad one, and a spelling that means a different stage in a different command writes a finished run in which nothing looks amiss. Typed anyway, it stops the run and names the four flags that do say which. Under auto-organotypic <folder>, where the outline is the only stage, the square crop is also spelled --crop-rois and --crop-region, beside the --crop that already means it there.

Open the recording in Fiji, draw round the tissue with the freehand tool, save the region as a .roi or a ROI Manager RoiSet.zip named after the recording or its well, and point the run at the folder. A recording nobody drew is left to the automatic method, which is the point: a plate where three wells needed a person and ninety-three did not is the normal case. --rois-if-missing skip leaves the undrawn ones alone; raise stops the run, which is how a fully manual pass is asked for.

Which way is up is one line, not a region. Draw it from the ventral base to the dorsal tip with the straight-line tool: the direction of the stroke is the direction that ends up at the top. Analyze > Measure gives that stroke's angle, and typing it into --angle is the same instruction — 0 to the right, 90 straight up the screen. When the automatic rule found the axis and only called the dorsal end upside down, --flip is the whole correction. A per-recording angles.json{"B2": 74.5}, or {"B2": {"flip": true}} — says either for a plate.

Draw on what the step sees. An ImageJ ROI stores pixel coordinates and not the size of the image they were drawn on, so a region drawn on the raw recording and applied to a registered stack lands in the wrong place and nothing in the file says so. That is why the flags are separate: --broad-crop-rois is drawn on the raw recording, --outline-rois on the plane the outline reads — the *_OUTLINE_INPUT_*.tif a previous run wrote — and the square crop on the *_ORIENTED_SOURCE.tif, because that crop is cut after the rotation. A drawing that cannot fit inside the frame it is handed is refused rather than clamped into a plausible crop. A drawn direction is the exception: an angle has no position, so the same stroke means the same thing on either frame.

A given box is obeyed, not judged — and counted. The presets keep every outline pixel and --crop-size-px is refused if it would not, but a box you named is taken as given: the report carries outline_pixels_clipped and raises an open question rather than quietly returning a smaller SCN.

The default outline is two lobes. Two drawn regions is one lobe each; one region plus a line down the middle is the region cut along that line; one region alone leaves the midline to the accepted split. --lobes 1 instead keeps that region whole, and --lobes any accepts either result. The report is written under its own tool name so nothing claims the frozen accepted geometry for an outline a person drew.

pip install "auto-organotypic[incucyte]" adds the Incucyte download and pip install "auto-organotypic[lv200]" the LV200 one; pip install "auto-organotypic[rhythm]" adds what the trace stage needs to put a period and a saved trace figure on a trace.

The rhythm adapter imports only Circadian Workbench's public package-root facade. Its period, phase, synchrony and channel answers therefore follow the same versioned contract as a direct circadian_workbench call; the adapter only reshapes those results for this pipeline.

Something to watch

auto-organotypic video D:/pull --filters broad_crop,register,bioluminescence
auto-organotypic video D:/pull/A1.tif --hours-per-second 6 --lut C1=green --lut C2=red
auto-organotypic video-grid D:/pull --columns 3              # every well together
auto-organotypic video D:/outlined --outline                 # plain + outlined copy
auto-organotypic video --list-filters                 # the steps and their parameters

One movie per recording, and the filtering happens on the way through. --filters is a chain of this package's own steps, run over each channel in memory: steps separated by commas, parameters by colons, so register:downsample=2,unmix:coefficient=0.04 is a chain of two. A raw instrument pull goes straight to something watchable, and the filtered stack that used to sit between them — a gigabyte nobody wanted — is never written. --save-filtered keeps it for the run where you do.

step what it does
broad_crop box the tissue, drop the empty four fifths of the sensor
register take the drift out, keep the field every frame still covers
unmix subtract a scaled autofluorescence channel from this one
cosmic_rays replace pixels sitting above what the same pixel does either side
static_background the per-pixel whole-record mean out, band-limited, and back
bioluminescence suppress only what matches each pixel's own noise
amplitude each pixel against its own quiet level, so the rhythm is the picture
no_drift the slow fade out, so what moves on screen is the rhythm
smooth, local_contrast display smoothing, and each frame against its own blur

Every step calls the owning module's own function rather than a copy: a chained bioluminescence comes out bit-identical to display.bioluminescence_display's TIFF, and a test asserts it. The first two are geometry — they change how big the picture is — so they are planned once for the recording and every channel gets the same box.

A week-long movie is mostly a picture of the battery running down. The substrate is used up, the tissue settles, the focus creeps, and that fade is larger than the daily rhythm the recording is of: on the ten-recording reference set only 0.40 of everything moving on screen is the rhythm. no_drift gives every pixel a moving average of its own circadian period, subtracts it, and puts the pixel's own long-run brightness back — a moving average over exactly one period cancels the rhythm, so what the average holds is the fade alone. The rhythm's share goes to 0.85, and the anatomy does not move.

auto-organotypic video D:/pull --filters no_drift:window_h=22.4 --time-gain 6

Give it the recording's own fitted period where you know it; a window 10 % off leaves about 9 % of the rhythm in the average and takes that much away. And expect to want --time-gain with it: once the fade is gone the rhythm is left using about 1.6 % of the display range where the fade had been using 11.6 %, so a drift-free video at 1x is honest and faint. It is display only.

Order is yours and it means something. broad_crop,register boxes the union of everywhere the slice went and registers the small frame — the pipeline's order, and the cheap one. register,broad_crop aligns the whole sensor first and boxes where the slice is. A filter either side is the same choice: filter the full frame, or filter the crop. The chain runs what is written down.

broad_crop takes the same --when the crop stage does: --filters broad_crop:when=max,register.

Nothing here may be measured from. Every movie records itself as a display artefact, and the one stack --save-filtered can write is marked _DISPLAY_ONLY whatever its chain was: it carries the movie's record rather than the unmixing coefficient's or the cosmic-ray rule's, so a number taken from it would have no artefact behind it. filtering.unmix and cosmic.remove_cosmic_rays are how a measurable stack is made.

A stack already marked _DISPLAY_ONLY can be cropped and registered — that is usually the one you most want held still to watch, and the movie is display-only either way. It still cannot be unmixed or de-spiked: that rule is about steps that change what a pixel means, and moving the picture is not one.

Playback is stated in experimental hours per second, not frames per second. At 6 — the default — one biological day takes four seconds of screen time whatever the acquisition interval was, and the frame rate is derived. No frame is ever dropped or duplicated to hit a rate. One number for every movie the package writes, single tiles and mosaics alike, so two exports of one recording never play at two speeds.

video-grid makes one moving comparison sheet from several recordings. Its visual controls are the image grid's: gutters are white and 2.5% of a tile, and each recording gets one automatic range measured across its complete selected window. --timestamp-format, --columns, --gap-px, --background, --tile-label and --own-range mean the same thing in both grid commands.

Two defaults differ from the sheet's, and both are about space. A sheet's rows are recordings, so one strip down the left names nine rows; a mosaic's tiles wrap, so every tile pays for that strip and it takes a quarter of the width. So --well-label-position defaults to top-left here — the name drawn over the frame, costing no layout — with top-right, bottom-left, bottom-right, and left for the outside strip. --well-label-orientation is a property of that strip and is ignored at a corner. And when the time cannot be hoisted into one heading (see below) it goes over a corner too, taking the one the name is not using; asking for both in the same corner by name is refused rather than quietly moved.

A mosaic on one time axis says the time once. Every tile is at the same experimental hour, so nine copies of it is eight too many. Two settings, because these are two questions:

--timestamp-scope how many times it is said auto (once when the tiles read one time, per tile when they do not), mosaic, tile, none
--timestamp-position where it goes auto, header, bottom, or a corner — of the mosaic or of each tile, whichever the scope is

none leaves the time off and keeps the names; mosaic insists on one caption and refuses a mosaic whose tiles disagree. A caption names the frame on the screen, but a frame chosen as the nearest to an instant is captioned by that instant, softened by exactly what the recording cannot resolve — the rule a contact sheet's columns already follow. Without it a tile sampled every 33 minutes reads 99.9 h beside eight reading 100, and the mosaic could never say its time once.

Every input contributes one frame to every output frame, so unequal selected frame counts are refused rather than silently truncated; --on-length shortest stops at the shortest tile and names what it cut.

Advancing by frame index is also what makes unlike cadences play at unlike speeds, and that is the refusal worth understanding. A plate holding a 30-minute recording and a 33-minute one covers different amounts of biological time in the same number of output frames, so the second plays 11% fast for its whole length and nothing on screen says so. Experimental-hours playback therefore refuses it. There are two ways past:

what it does what it costs
--on-cadence resample every tile on one grid of experimental hours, stepping at the finest cadence present; each tile shows its own nearest frame to each instant a coarser tile repeats a frame where it has none, counted per tile in resampled
--fps N plays at a stated frame rate and claims nothing about biological speed the tiles really are at different speeds

resample is the only one that makes the mosaic real time. It is the contact sheet's rule applied to a movie — a sheet's columns are already each recording's nearest frame to a requested hour. Each tile's timestamp always follows its own recording.

Something to put in a figure

auto-organotypic image D:/pull --filters broad_crop,register,bioluminescence
auto-organotypic image D:/pull/A1.tif --when 412 --lut red    # one named frame
auto-organotypic grid  D:/pull --moments 6                    # a time montage
auto-organotypic grid  D:/pull --when mean                    # every well, one sheet

image is video with the encoder taken off, argument for argument — the same --filters, the same --lut, the same --display-range, the same --display-gamma, because both draw through the same engine. A still made with a movie's arguments is that movie's frame, to the pixel.

--when is the whole difference:

--when the picture is
mean the window averaged. The default: a nine-day bioluminescence recording is mostly noise in any one frame
max the brightest each pixel ever got — and a hot pixel too, if the cosmic-ray rule has not run
412 recording frame 412, one-based, the same vocabulary --scn-time uses

A projection has its display range measured on itself, not on the frames behind it. The mean of a thousand frames has a lower maximum than any frame in it, so a range read off the frames would draw the mean too dark by exactly the amount the averaging removed. Its caption says a stretch rather than an instant — Time: 0 h 00 min - 240 h 00 min — because a mean happened at no single time.

grid tiles several of those into one file, and what varies decides what the sheet is:

tiles
--moments 12 one recording across time — a montage
a folder every recording at one moment — a contact sheet
a folder --moments 6 rows of recordings, columns of time

A montage covers one circadian cycle unless told otherwise: see below.

Time-course rows put each well name once on the left; use --well-label-orientation horizontal|vertical to turn those names.

A caption every row repeats is said once, above its column. Nine wells at six times used to carry the time in a black band under all fifty-four tiles; --timestamp-position auto, the default, puts six headings at the top instead and the other eight bands go. It hoists only when every row of every column says the same thing, so a contact sheet of nine differently named wells keeps its names on its tiles. header asks for the hoist and refuses a sheet whose columns disagree, naming the column that does; bottom is the old band under each tile, and top-left or top-right draw over that corner. --timestamp-format accepts hours, elapsed, clock, or a template such as {prefix} {total_hours:.0f} h, day {day}.

A montage is one circadian cycle, trough to peak to trough

What a sheet of a slice is for is showing how it looks at each stage of its rhythm. So --moments 7 gives seven columns evenly over one cycleCT 0, 4, 8, 12, 16, 20, 24 — and not seven days at the same phase, which shows the cycle exactly once and the damping six times. That was the behaviour until 2026-08-28.

Three defaults move together to do it, and any of them may be set on its own:

default what else
--between cycle — one period from hour zero window, or 24:96 in hours
--time-scale ct — the caption counts from the rhythm elapsed, zt
--align best — hour zero is the best day's trough trough, onset, peak, start, clock, hours

The cycle closes, and it opens at a trough. Changed 2026-09-03, from six columns opening on a rise. The first tile and the last are now the same phase a day apart, which is a fixed point: if the two do not match, what is on the sheet is not the rhythm. Seven and not six because an odd count with both ends drawn puts one column exactly halfway, so a day cut at its trough shows its peak in the middle tile. --between 0:20 gets the open cycle back.

Which cycle is best is measured. Each complete cycle is scored on three things and the product decides:

catches
swing tenth percentile to ninetieth of the detrended cycle a dead day
goodness variance a cosinor at the period explains a cosmic ray
consistency how nearly it peaks where its neighbours peak a one-off bump

No two are enough. A cosmic ray gives a huge excursion and explains nothing; a flat cycle explains most of its own tiny variance; and the mounting transient — the slice recovering from being cut — is the largest, smoothest bump in most organotypic records and scores well on both. The first cycle is therefore passed over whenever another exists. Every cycle's score is in the report under cycles, so the choice can be checked and disagreed with; --align onset takes the first cycle deliberately, and --align 36 takes whichever you like.

A column is a time, not a frame number. Each recording supplies its nearest frame to the hour a column names. Until 2026-08-28 they were frame numbers shared across the plate, so on the reference set — eight wells imaged every 30 minutes and one every 33 — column three was 24 hours into eight of them and 26.6 into the ninth, and a reader comparing the row could not tell.

The gap divides the cycle: a half, third, quarter, sixth, eighth or twelfth of the period, or a whole number of them. Over more than one cycle that means every column falls at the same stage, so a row is one phase sampled repeatedly rather than a beat between the sampling and the rhythm. The count you asked for is honoured — the snap is capped at the widest gap that still fits it — and spacing in the report says what it settled on. --every-h 18 states the gap outright and is never snapped; --even-spacing divides the span evenly.

--period-h 23.4 says what the cycle is; --period-h fit reads it off the recordings and takes the median. Display only, and never a period measurement — six columns over a slice free-running at 23.4 hours walk 3.6 hours off its rhythm in a day if the sheet assumes 24.

--between window restores the old behaviour, and --between 24:96 restricts the columns to those hours. A recording too short to hold a cycle falls back to its window and says so in the report; asking for cycle outright is refused rather than shrunk.

What a tile is a picture of

A circadian day is a small change on a large structure. On the ten-recording reference set a whole day is 4–6 % of the tissue's brightness — about 1.2 units of CIELAB L*, where two units is the least a reader can tell apart between two tiles side by side — so eight of those ten dishes draw a day nobody can see. Four settings decide what to do about that, and the sheet ships doing none of them:

--plot change each tile against the average of its own day 68 grey levels where intensity shows 12
--time-gain 6 the same departures drawn six times larger 6.2 L* instead of 1.2
--soft-range the ends of the range bend instead of cutting auto: on for a closing cycle
--change-levels 28 a change sheet's full scale, shared by every recording own scales each by its own spread

A video grid takes the same four, with the same defaults and the same refusals — auto-organotypic video-grid ... --plot change, --time-gain 6 — and a frame is drawn against the mean image of the record exactly as a tile is drawn against the mean image of its day. Pair --time-gain with --filters no_drift on a long recording, or the gain amplifies the fade along with the rhythm. On a video the note is printed on every frame: one frame travels on its own.

One scale for the sheet, or the sheet is not fair. Scaling each recording by its own spread draws a well with no rhythm as vividly as the best one in the set: across the reference recordings, what a sheet showed correlated 0.598 with each dish's own fitted amplitude when the scale was per dish and 0.846 when it was shared. The same is true of the gain — one number, every pixel, every recording, so a well with half the rhythm shows half the change.

A change plot is drawn in the diverging change map by default — on a sheet and on a video alike: cyan below the day's average, black at no change, red above. A dim tile on it is not dim tissue; it is tissue below its own average that day, and a single-hue ramp cannot say which side of the average a pixel is on. Name a lut and that one is used instead.

Both are display only, and a sheet drawn either way prints CHANGE or DIFFERENCES x6 beside every recording's name and records the setting in its own report, so it cannot travel without saying what it is.

Three clocks

--time-scale decides what the number is and --timestamp-format decides how it is spelled, so ZT 16:14 and CT 3.4 h are both available to either.

--time-scale zero is wraps answers
elapsed the first drawn frame never how long in
zt midnight, real time 24 h what time of day
ct this slice's own rhythm the period where in its cycle

ZT here is the wall clock, not a light cycle. A slice has no retina, so nothing in the dish responds to light and there is no Zeitgeber to count from; what the name is borrowed for is the real time of day a frame was taken. It is read off the acquisition timestamp and refused without one--clock-start 2026-07-10T16:13:53 supplies it for a derived stack that no longer carries it.

--align puts hour zero, and therefore column one, at best, onset, peak, start, clock (the last midnight), or a stated number of hours:

auto-organotypic grid D:/pull --moments 6                        # one cycle, CT, best
auto-organotypic grid D:/pull --moments 6 --align onset          # the first cycle
auto-organotypic grid D:/pull --moments 6 --between window       # five days, one phase

onset is the first rise of a recording's own rhythm — the upward crossing of the midpoint between its first trough and its first peak. Which cycle that is comes from a cosinor at the period, and where in it from the trace itself: the largest maximum is not the first one, and on the reference plate it is a mounting transient on day one in well 1423 and a cosmic ray on day nine in 1432. best is the same rise, of the best cycle rather than the first. Both are measured from a coarse tissue-mean trace, are display only, and are reported under clocks so they can be checked or replaced with a number.

The same arguments are on image, video and video-grid. On a movie --between cycle plays exactly one period and --between 0:72 cuts the playing window in hours rather than frames, so a movie can start at the best cycle's rise and run one day. On the mosaic --align best starts every tile at its own zero, so nine slices that came up at nine different times play in phase and the grid shows how their rhythms differ rather than when each was mounted.

Tiles have white gutters 2.5% of their short side by default, following the Plot That image-grid reference. --gap-px and --background override them.

One display range for each complete recording or split. It is measured across the selected time window and applied to every time tile from that input, so a dim phase stays dim and a late bright phase cannot be clipped by an early tile. --own-range turns sharing off for a layout check.

Automatic contrast: black at the 50th percentile of the empty field, white at the 99.8th of the stack. Measured, not chosen — see docs/display-contrast.md. The old white point at the 99.999th percentile was set by a handful of outlier pixels, so the tissue lived in the bottom sixth of the ramp and a circadian cycle moved the bright regions by 52 of 255 grey levels; at 99.8 it moves them by 66.

The black point took three rounds, and the first two were graded by the wrong ruler. Clipping was measured over largest_tissue_mask — the Otsu mask the floor is read outside of — so tissue the floor had crushed fell below that threshold, sat outside the mask, and was never counted. Measured over the accepted SCN outline instead, that mask covers 56% of the slice, a third of the "empty field" is accepted tissue, and at 90 the floor drew 33.5% of the slice pure black (46% on the worst recording). At 50 it draws 2.0%, the dim sixth of the slice goes from grey 2 to 44, and the empty field still renders at a median of zero — 50 is the last floor at which it does.

And the ramp is a curve, not a line: --display-gamma, 0.7 by default. Retuned on 2026-08-31, when the same complaint came back in the same words. The 2026-08-28 sweep measured only the bright tenth of the tissue and fixed the bright tenth; measured again with a column for the dim third — the pixels the complaint is about — a straight ramp draws it at 29 of 255, which a red lookup table draws as near-black. No pair of endpoints reaches it, because a slice glows on top of the medium it sits in and its dim third therefore sits just above the black point however that point is chosen. At 0.7 the dim third is drawn at 56 and swings 51 grey levels over a recording instead of 39, and because the curve is applied after the clip the two percentiles still mean exactly what they meant — clipping is unchanged at both ends. 1.0 is the old straight line; 0.5 doubles the effect again for a dim preparation, at the cost of visible grain in the empty field.

When even a curve is not enough, --filters amplitude. A slice whose tissue sits a thousand counts above the empty field and swings a tenth of that spends most of the ramp on the constant part however it is ranged. amplitude takes each pixel against its own quiet level — the tenth percentile of its own time course — so the ramp is spent on the rhythm, and on the reference plate that is five times the visible amplitude. The price is that a tile then shows change rather than brightness, which is why it is a filter you ask for and not a default. Display only, like everything it feeds.

A recording that will not open stops the sheet. --skip-bad finishes without it and prints what was left out, because a grid quietly missing three of its wells is worse than no grid.

A sheet is filed under the set of recordings that made it, and lands in that folder's own AI_Exports. Filing it under whichever recording was listed first is not just odd to read — add a well and the old sheet's key would still match, so a stale sheet would look current. A montage of a single recording is still filed under that recording, because there it really is a fact about it.

The still grid and video grid are display only on the same terms as each individual movie, and their records carry the same display_only mark.

Accepted outlines on display exports

Every image, movie and either kind of grid takes the same outline controls:

auto-organotypic image D:/outlined --outline
auto-organotypic grid D:/outlined --outline --outline-mode only
auto-organotypic video A1.tif --outline A1_SCN_LABELS_ROI_CROP_TIGHT.tif
auto-organotypic video-grid D:/pull --outline D:/analysis/batch_scn_outline_manifest.json

--outline without a path finds the accepted labels from the outline-stage source or batch_scn_outline_manifest.json. Python also accepts a label array, an ordered list for a grid, or a mapping keyed by source path, filename, stem or well name. The labels must already share the rendered image's orientation and crop; they are never resized into a plausible-looking but displaced boundary.

The default --outline-mode copy keeps the unchanged export and adds an *_outline copy. --outline-mode only writes just the outlined export at the requested name. The default boundary is opaque cyan and two pixels wide; --outline-colour, --outline-width-px and --outline-opacity change it.

The window

auto-organotypic ui                                       # a window on this machine
auto-organotypic ui --browser                             # the same page, in a browser
auto-organotypic serve --host 0.0.0.0 --root D:/Imaging   # one machine, whole lab

A window, not a browser tab. Pick a folder — with the operating system's own dialog — see what is in it, start a run and watch the stages tick.

Underneath, the window is a loopback server drawing the same page serve serves. That is the reason it is not a desktop toolkit: Tkinter would give a window and take away hosting, and both were asked for. The window announces nothing and owns its server, so closing it stops it.

Every control on the page is a flag of auto-organotypic pipeline with the same default, so nothing it can ask for is something a shell cannot — a test asserts that rather than a paragraph promising it. The one thing the window can do that the served page cannot is open that folder dialog, and a test pins the bridge to exactly that.

Two behaviours worth knowing. A run is submitted and watched rather than blocking, because registering ninety-six wells takes hours. And stopping takes effect at the next stage boundary — a stage is one call into another package, so "stop" honestly means "finish this one and do no more".

serve is the other half, and the only one with a host to worry about. There is no login, so binding to anything but loopback requires at least one --root, and every folder browsed, planned or run is checked against it.

$ auto-organotypic serve --host 0.0.0.0
--host 0.0.0.0 serves this to the network, so it needs at least one --root
saying which folders it may read and write. Without one the folder picker would
offer the whole machine to anybody who can reach the port, and there is no login
to stop them.

The method is frozen

The outline is accepted Round 6 attempt 7 and the orientation accepted Round 9 attempt 6, from the Cry1-DIO-dLuc red-channel tuning project. The public settings retain the accepted pixel values, because six declared attempts to simplify or normalise them failed the truth, shape or generalisation gates — a shorter interface would have meant a less portable method, not a tidier one.

tests/test_automatic_scn_outline_parity.py compares output bytes against ten accepted fields rather than comparing behaviour. That evidence lives in the governed tuning project rather than in this repository; point AUTO_ORGANOTYPIC_TUNING_ROOT at it to run those tests, and without it they skip while the synthetic crop, orientation and input-safety tests still run.

Install

pip install auto-organotypic

Only numpy, scipy, tifffile, scikit-image and imagecodecs. No plotting stack, no web framework, no audit layer: outlining a slice should not install any of them. The last two are there because the method genuinely reaches them. scikit-image for convex_hull_image, inside the consensus that places the midline; it was declared under the register extra alone until 2026-08-25, which meant a plain install imported, read a manifest, and then failed partway through the first outline. imagecodecs because it is what tifffile decodes LZW with — tifffile’s own fallback handles zlib, LZMA, Zstd and PackBits and not LZW — so without it a plain install raised on the first frame of any recording Bio-Formats or Fiji had written, which is most of them. Reading its own input is not an optional capability, so it is not an extra. tests/test_self_contained.py runs the outline with everything undeclared blocked, which is the check that found the first of the two.

Eight extras, each needed only for what it names — and none of them to read a finished pull, because a manifest is plain JSON:

Extra For
auto-organotypic[incucyte] starting an Incucyte acquisition
auto-organotypic[lv200] pulling from a running LV200 (PyLV200)
auto-organotypic[register] registration (OpenCV, scikit-image)
auto-organotypic[image] stills, grids and colour maps (Pillow, matplotlib)
auto-organotypic[video] movies too, which need an encoder (adds imageio-ffmpeg, imageio)
auto-organotypic[rhythm] periods, cosinor fits and the rhythm verdict (circadian-workbench)
auto-organotypic[ui] the page and the server behind it (FastAPI, uvicorn)
auto-organotypic[desktop] the window (adds pywebview, which borrows the webview your machine already has)

Where this came from

These modules lived in PyMicroglia until 2026-08-23. Nothing about outlining a suprachiasmatic nucleus concerns microglia, and the code was already a leaf — nothing in that package imported it. PyMicroglia keeps the automatic_scn_outline action, which now delegates to auto_organotypic.outline:

pip install "PyMicroglia[scn]"

That extra reaches PyPI with PyMicroglia's next release; the version published there today predates the move and has neither the extra nor the delegating module.

Where this is going

Auto-Organotypic is the SCN layer of an automated instrument-to-rhythm pipeline: download, crop broadly, register, outline and crop, trace, test the rhythm, render videos. The headless command is stage 3 of that plan, the window stage 4 and this release stage 5. docs/automated-scn-pipeline.md, in the repository, records which package owns which step and why.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

auto_organotypic-0.6.0.tar.gz (10.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

auto_organotypic-0.6.0-py3-none-any.whl (746.8 kB view details)

Uploaded Python 3

File details

Details for the file auto_organotypic-0.6.0.tar.gz.

File metadata

  • Download URL: auto_organotypic-0.6.0.tar.gz
  • Upload date:
  • Size: 10.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for auto_organotypic-0.6.0.tar.gz
Algorithm Hash digest
SHA256 37aad18fff6fba0be9cafac306bfd9bd22bce575b2477dd2d62ac44331ad92a7
MD5 2a424d845a7ddc3e5ca3ac5ba7848e7a
BLAKE2b-256 fc553cf47ae1a8b0366c71ba29b62995b154eae755a205a16dd0c88654d8bcce

See more details on using hashes here.

File details

Details for the file auto_organotypic-0.6.0-py3-none-any.whl.

File metadata

File hashes

Hashes for auto_organotypic-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2311e9eda11a46325878eb591fc7c69343e9f0027dd48a43783e393175c2134d
MD5 deee0d1b51f2681b7861ee8ed75aaeaf
BLAKE2b-256 168499450d0d5c14ca4b9e69c9bdfbf4a5a69590ff4481286b2fef7909ed8505

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page