catalog
tit.catalog ¶
Project catalog: subject and simulation discovery for the UI.
Built only on :class:tit.paths.PathManager (plus the existing per-domain
helpers it composes: :mod:tit.sim.utils montage I/O, :mod:tit.atlas
discovery, :mod:tit.opt.ex.roi/:mod:tit.opt.leadfield ROI and leadfield
helpers, :mod:tit.opt.flex.manifest); the server routes
(tit.server.routes.catalog, tit.server.routes.catalog_v1) serialise
these dicts as-is, so the BIDS / derivative layout rules are never
re-implemented outside tit.
v1 additions (this module is extended, not replaced, for Stage 1 / lane B2 —
see docs/dev/DECISIONS.md § 2026-08-27 (Electron and the job server) §3 "Catalog"): subject/simulation detail,
montage CRUD, EEG nets, atlases + regions, ROI CRUD, leadfields, flex/ex/mex
runs, analyses, reports, freehand configs, the project-level group catalog
and Quick Notes. A run/analysis directory
without the manifest file that marks it complete (flex_meta.json,
run_config.json, or the analysis.json+results.csv pair) is
ignored, so a cancelled or in-progress run never appears as a result.
subject_ids ¶
subject_ids(pm: PathManager) -> list[str]
Union of raw-BIDS, FastSurfer, legacy-FreeSurfer and m2m subjects.
Naturally sorted. derivatives/freesurfer is still unioned in so a
project processed before FastSurfer replaced recon-all keeps listing
its subjects.
Deliberately excludes subjects known only from sourcedata/ (DICOMs
staged, nothing converted yet) -- this is the onboarded set every other
catalog function gates on (montages, ROIs, EEG nets, ...), where a
subject with no derivative of any kind would be meaningless. See
:func:sourcedata_only_subject_ids and, for the two routes that do need
to see a not-yet-onboarded subject, :func:list_subjects /
:func:subject_detail.
Source code in tit/catalog.py
sourcedata_only_subject_ids ¶
sourcedata_only_subject_ids(pm: PathManager) -> list[str]
Subject ids with raw data staged under sourcedata/ and nowhere else.
:func:subject_ids would never mention these -- no BIDS directory, no
m2m, no FastSurfer/FreeSurfer -- so a project's own newly-arrived DICOMs
(Dataset 000's sub-102, before its DICOM-conversion stage has ever
run) were invisible to every page built on the catalog (lane FX5,
docs/dev/DECISIONS.md § 2026-09-03 (One Docker image and a real development loop)). Naturally sorted; a subject that
already appears in :func:subject_ids is never repeated here even if
sourcedata/ also holds a copy of its DICOMs.
Source code in tit/catalog.py
list_subjects ¶
list_subjects(pm: PathManager) -> list[dict]
One entry per subject: presence flags and the simulation count.
Also lists subjects known only from sourcedata/ (DICOMs staged, no
BIDS directory yet -- see :func:sourcedata_only_subject_ids): the
Subjects page and Pre-processing's own subjects table are exactly where
a project's newest subject needs to be plannable, before anything else
about them exists. An entry carries has_sourcedata: True when staged
raw data is present (so a client can tell "staged, not yet converted"
from has_raw False); the key is left out entirely otherwise -- the
overwhelming common case (a subject with no sourcedata copy at all), and
the shape every existing caller/test built before this field existed
stays byte-identical (/api/catalog/subjects is served with
response_model_exclude_unset, tit/server/routes/catalog.py).
Source code in tit/catalog.py
list_simulations ¶
list_simulations(pm: PathManager, sid: str) -> list[dict] | None
Simulations of sid (TI/ and mTI/ presence), None if unknown.
Source code in tit/catalog.py
output_files ¶
Every file under a run's output folder as {path, kind, label, bytes} rows.
The one filesystem read behind a finished job's Artifacts tab
(GET /api/jobs/{id}/artifacts): what is on disk, not what a runner remembered to
register. label is the path relative to root; bytes is the file size (None
if it cannot be stat'ed). Same bounded walk and project jail as :func:_dir_artifacts.
Source code in tit/catalog.py
classify_view_file ¶
What path is, as one of the seven kinds a scene understands — or None.
volume · label-volume · surface · mesh · annotation · morph ·
surface-data.
Decided from the name and its neighbours only. No file is opened: this runs once per row
of a menu that is redrawn on every keystroke, and a menu that reads its way through a
FreeSurfer surf/ directory is a menu that stutters. The one filesystem touch is
:func:_has_lut_sidecar, a single os.path.isfile on a sibling.
None means "not something a scene can use" and is returned for everything unrecognised —
.mat, .txt, .geo, .sigma, .label, .ctab, a log, and the derived FreeSurfer files
(lh.pial.T1, lh.smoothwm.K.crv) that are recon-all's bookkeeping rather than anyone's
input. Refusing to guess is the point: a file offered under the wrong kind fails at Open,
which is a much worse moment to find out than not being offered at all.
Source code in tit/catalog.py
surface_attachments ¶
surface_attachments(surface_path: str, *, extra_dirs: tuple[str, ...] = (), project_root: str | None = None) -> list[str]
Every annotation / morph / data-GIfTI file that belongs on surface_path.
Matched by hemisphere and nothing else, because that is the only correspondence FreeSurfer
actually promises: lh.thickness has one value per vertex of every lh.* surface, since
they all share a vertex numbering. Which lh. surface you hang it on is the viewer's choice,
not a fact about the file. (Tetravox's own worker checks the vertex count when the file is
attached, so a genuine mismatch is refused there with both counts named — this side does not
need to read a byte to be safe, only to be plausible.)
Searched in the surface's own directory plus extra_dirs — SimNIBS keeps the geometry in
m2m_<sid>/surfaces/ but writes the parcellations it made into m2m_<sid>/segmentation/,
two directories apart, which is exactly why nothing offered them before.
Source code in tit/catalog.py
subject_detail ¶
subject_detail(pm: PathManager, sid: str) -> dict | None
Full detail for one subject, None if unknown.
"Unknown" also admits a sourcedata-only subject (see
:func:sourcedata_only_subject_ids) -- the Subjects page fetches this
for every row :func:list_subjects returns, sourcedata-only rows
included, and a 404 there would just blank the detail pane rather than
error, but there is real information to return (has_sourcedata) so
it is returned rather than dropped.
Source code in tit/catalog.py
classify_sim_nifti ¶
What a simulation NIfTI holds, read from the name the simulator gives it.
The simulator names every volume <base>_subject_<field>.nii.gz or
<base>_MNI_MNI_<field>.nii.gz (tit.tools.mesh2nii), where <base> is
the mesh it came from: <tissue_><sim>_<TI|mTI> for the envelope fields
(grey_/white_ for the volume mesh's tissue crops, tit.tools.mesh2nii) and <sid>_TDCS_<i>_<cond> for a
carrier's magnE. Space and field are read from the tokens at the end and the
tissue from the prefix before the simulation's own name, so a simulation called
MNI_target, grey_matter or TDCS_vs_TI is classified like any other.
Parameters¶
basename : str
File name, without directory.
simulation : str or None, optional
The simulation the file belongs to. With it the tissue prefix is read exactly
(grey_matter_TI is the whole-head file of grey_matter); without it a
known tissue prefix is stripped.
Returns¶
dict or None
{"kind": "envelope" | "carrier", "field", "space": "subject" | "mni",
"tissue": "grey" | "white" | None}, or None for a name the simulator
does not write.
Source code in tit/catalog.py
simulation_detail ¶
simulation_detail(pm: PathManager, sid: str, sim: str) -> dict | None
Full detail for one simulation, None if unknown.
Source code in tit/catalog.py
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 | |
simulation_figures ¶
simulation_figures(pm: PathManager, sid: str, sim: str) -> list[dict] | None
Pictures a simulation run saved of itself; None if subject/simulation is unknown.
Today that is the montage visualisation tit.tools.montage_visualizer writes as
<sim>/<TI|mTI>/montage_imgs/<name>_highlighted_visualization.png -- the EEG net with
this run's electrodes highlighted. The Results pane shows it beside the channel chips,
which name the same montage in text, and in the run's Figures grid.
Not folded into SimulationDetail: that response has a Pydantic response_model
generated from the frozen v1 contract, so a new field there is a contract change. This is
a small presence query beside the main read.
Source code in tit/catalog.py
get_montages ¶
get_montages(pm: PathManager) -> dict
montage_list.json, reshaped to the v1 Montages schema.
Reads the explicitly selected project's montage file through the shared writer API.
Source code in tit/catalog.py
put_montage ¶
put_montage(pm: PathManager, net: str, kind: str, name: str, pairs: list[list[str]]) -> list[list[str]]
Create or overwrite one montage; returns pairs back.
pairs must be a montage the simulator can run
(:func:tit.sim.config.check_electrode_pairs: pairs of two distinct
labels, an even count from 2 to 26) and fit kind: uni_polar is 2
pairs (TI), multi_polar 4 or more (mTI). Anything else raises
ValueError before the file is touched.
Source code in tit/catalog.py
delete_montage ¶
delete_montage(pm: PathManager, net: str, kind: str, name: str) -> bool
Delete one montage; False if it did not exist.
Source code in tit/catalog.py
eeg_nets ¶
eeg_nets(pm: PathManager, sid: str) -> list[dict] | None
EEG nets available to sid, with electrode labels; None if unknown.
Source code in tit/catalog.py
atlases ¶
atlases(pm: PathManager, sid: str, space: str | None = None, kind: str | None = None) -> list[dict] | None
Atlases available to sid; None if the subject is unknown.
Every entry carries kind: "surface" for a FreeSurfer .annot
parcellation, "volume" for a label volume. For MNI space the list, its
order and each entry's kind, template and licence come from
resources/atlas/manifest.json (:mod:tit.atlas.manifest) rather than
from a filename list, so an atlas is only ever offered to the targeting mode
that can read it: the cortical mode asks for kind="cortical" and gets
surfaces, the subcortical mode asks for kind="subcortical" and gets
volumes. Before the manifest existed nothing recorded the kind of an MNI
atlas and the subcortical mode was handed every shipped MNI file whatever it
was.
Source code in tit/catalog.py
1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 | |
atlas_regions ¶
atlas_regions(pm: PathManager, sid: str, atlas_id: str, hemi: str | None = None) -> list[dict] | None
Regions of atlas_id for sid; None if the subject/atlas is unknown.
Per the reconciled v1 Region schema: for a cortical (surface/annotation)
atlas, id is the FreeSurfer .annot label index within that
region's own hemisphere file -- exactly the integer
FlexConfig.AtlasROI.label / ExConfig's equivalent needs, resolved
the same way :func:tit.opt.roi_spec.resolve_cortical_region_index_map
does -- and hemi is that region's hemisphere. For a subcortical
(volumetric) atlas, id is the voxel label value and hemi is
None (a region whose label cannot be parsed as an integer is dropped
rather than returned with a non-conforming id).
Source code in tit/catalog.py
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 | |
region_roi ¶
The FlexConfig ROI targeting regions (atlas_regions rows) of atlas (an
:func:atlases entry): an AtlasROI for a surface atlas, each region read from its own
hemisphere's file (the lh. of the atlas path swapped for the region's hemi), a
SubcorticalROI for a volume, the atlas path once per region.
The desktop's picker builds the same object from its selection (roiToConfig in
desktop/src/renderer/pages/_shared/roi/types.ts); tests/fixtures/region_rois.json
pins both to one table.
Source code in tit/catalog.py
find_regions ¶
find_regions(pm: PathManager, sid: str, query: str) -> list[dict] | None
Every region of sid's atlases whose name has each word of query as a whole word
(:func:_name_matches), per atlas, with
ready ROIs: rois.all (every match, i.e. both sides) and rois.left /
rois.right when a side matched. Side words ("left", "bilateral", ...) are ignored;
None for an unknown subject, :class:ValueError when query names no structure.
The atlases searched are the ones the Optimizer's ROI picker offers: the subject's own
(space: "subject") and then the shipped MNI volume atlases (space: "mni",
atlases(..., space="mni", kind="subcortical")), whose ROI is a SubcorticalROI with
atlas_space: "mni" -- the object the picker builds for an MNI selection.
Source code in tit/catalog.py
nifti_labels ¶
nifti_labels(pm: PathManager, sid: str, path: str | None = None) -> list[dict] | None
Unique integer labels present in one label volume, named where a LUT applies.
The 3D Visual Exporter's sub-cortical mode asks the user for label numbers
(10,49); 2.5.0 answered that with a Qt dialog that parsed a FreeSurfer LUT
itself. This is that browser's data, from the toolbox's own segstats path
(:func:tit.atlas.segstats.compute_segstats +
:func:~tit.atlas.segstats.resolve_lut_for_atlas), which also writes the
<name>_labels.txt sidecar cache beside the volume -- so the first call on a
subject pays for the scan and every later one reads the cache, same as
:meth:VoxelAtlasManager.list_regions.
Parameters¶
pm : PathManager
sid : str
Subject whose m2m holds the default volume.
path : str or None
A specific label volume. None/empty means the subject's own
<m2m>/segmentation/labeling.nii.gz -- the sub-cortical mode's default,
and the only path the panel ever sends for an untouched form.
Returns¶
list of dict or None
[{"id": int, "name": str, "n_voxels": int}] sorted by id; None
for an unknown subject, a path outside the project jail, or a file that is
not there. The caller (routes/catalog_v1) turns None into a 404 --
deliberately one status for all three, so probing this route cannot tell a
file that exists outside the jail from one that does not exist at all.
Source code in tit/catalog.py
1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 | |
is_safe_name ¶
True if name is safe to use as a filename component.
Used for ROI names, free-hand config names, and montage names on every write path -- never trust a client-supplied string used to build a path.
Source code in tit/catalog.py
as_float ¶
Coerce value to float; raises ValueError naming the field on failure.
bool is rejected even though isinstance(True, int) is True in
Python -- a checkbox value has no business landing in a coordinate.
Source code in tit/catalog.py
list_rois ¶
list_rois(pm: PathManager, sid: str) -> list[dict] | None
Saved ROIs for sid; None if the subject is unknown.
Source code in tit/catalog.py
create_roi ¶
create_roi(pm: PathManager, sid: str, roi: dict) -> dict
Save a new spherical ROI centre for sid.
Writes a plain x,y,z row (what :func:tit.opt.ex.roi.read_roi_center
reads) plus a roi_list.txt entry; this module is the single writer of
the CRUD shape the v1 contract expects. The CSV format has never carried the space or the radius, so they
go in a <name>.json sidecar beside it, which :func:list_rois reads
back: the listing returns what this function answered. Without a
space the _MNI name suffix decides, as it always has.
Raises¶
ValueError
If the name is unsafe, a coordinate or the radius is not a finite
number, the radius is not positive, or the space is not subject
or mni. Nothing is written then.
Source code in tit/catalog.py
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 | |
delete_roi ¶
delete_roi(pm: PathManager, sid: str, name: str) -> bool
Delete one saved ROI; False if it did not exist (also if name is unsafe).
Source code in tit/catalog.py
list_leadfields ¶
list_leadfields(pm: PathManager, sid: str) -> list[dict] | None
Precomputed leadfields for sid; None if the subject is unknown.
Source code in tit/catalog.py
pair_by_channel ¶
Group electrodes into [a, b] pairs, one pair per stimulation channel.
The one electrode-pairing rule for a flex-search result: the Simulator's flex rows (via
:func:flex_runs), POST /api/plan's flex montage sources, the run-mapping route and
agent sim_from_flex steps (all via
:func:tit.sim.montage_sources.resolve_flex_montage) pair through it.
channel_array_indices is the optimiser's own [[channel, array], ...] bookkeeping
(_save_optimized_positions in resources/map-electrodes/tes_flex_optimization.py
writes one entry per electrode, carried through electrode_mapping_*.json by the
Hungarian mapping): entry i says which channel and which of that channel's two arrays
electrode i belongs to, so the pairs follow the channels whatever order the electrodes
are listed in. When it is missing or unusable the electrodes are paired consecutively.
Source code in tit/catalog.py
read_flex_mapping ¶
One electrode_mapping_<net>.json as {eeg_net, pairs[, max_snap_mm]}, or
None when unusable (unreadable, fewer than four labels, or a label used twice --
two optimised electrodes on one site is not a montage).
max_snap_mm is the largest distance (mm, Euclidean) an optimised electrode moved
to its net site, from the file's distances; it is left out when those are missing,
not one finite number per label, so an unknown snap is never shown as a small one.
Source code in tit/catalog.py
flex_optimized_pairs ¶
The run's free (un-mapped) XYZ electrode pairs, or None.
electrode_positions.json is written by every flex-search run, which is
what makes a run selectable in the Simulator even when it has never been
mapped onto an EEG net (Montage.Mode.FLEX_FREE).
Source code in tit/catalog.py
flex_runs ¶
flex_runs(pm: PathManager, sid: str) -> list[dict] | None
Flex-search runs for sid; None if the subject is unknown.
Source code in tit/catalog.py
ex_runs ¶
ex_runs(pm: PathManager, sid: str, kind: str = 'ex') -> list[dict] | None
Ex/mEx-search runs for sid; None if the subject is unknown.
Source code in tit/catalog.py
analyses ¶
analyses(pm: PathManager, sid: str, sim: str) -> list[dict] | None
Analyzer runs for sid/sim; None if either is unknown.
Source code in tit/catalog.py
analysis_summary ¶
analysis_summary(pm: PathManager, sid: str, sim: str, name: str) -> dict | None
results.csv of one analysis, as TableData.
name is what :func:analyses listed; a caller holding the run's own path relative
to the simulation (Analyses/Custom/run1) resolves too, so the one string a script
already has does not have to be translated into the listed form.
Source code in tit/catalog.py
reports ¶
reports(pm: PathManager, sid: str) -> list[dict] | None
Generated HTML reports for sid; None if the subject is unknown.
A subject known only from staged DICOMs (:func:sourcedata_only_subject_ids) is known:
:func:list_subjects shows it, and its pre-processing report (Dataset 000's sub-102)
was unreachable while this answered "unknown subject" for it.
Source code in tit/catalog.py
find_report_path ¶
find_report_path(pm: PathManager, report_id: str) -> str | None
Resolve a reports() id back to its file path (used by /api/files/report).
Source code in tit/catalog.py
freehand_configs ¶
freehand_configs(pm: PathManager, sid: str) -> list[dict] | None
Saved free-hand electrode configs for sid; None if unknown.
Reads m2m_<id>/stim_configs/*.json. The on-disk format, written by the
free-hand electrode placement UI, is
{"name", "type": "U"|"M", "electrode_positions": {label: [x, y, z]}}.
The contract's FreehandConfig.type enum is [U, M] (unipolar/
multipolar), matching this on-disk value exactly (fixed from an earlier
xyz/label enum that matched nothing real -- see
contracts/CHANGES.md); type is passed through verbatim.
Source code in tit/catalog.py
put_freehand_config ¶
put_freehand_config(pm: PathManager, sid: str, name: str, config: dict) -> dict
Create or overwrite one free-hand electrode configuration.
Positions are paired consecutively, as the simulator reads them
(:func:tit.sim.montage_sources.resolve_freehand_montage), so the
placement must be one it can run: type "U" with 4 positions (TI)
or "M" with 8, 12, ... (mTI), every coordinate finite and every
electrode at least 1 mm from the others
(:func:tit.sim.config.check_electrode_pairs). Anything else raises
ValueError before the file is touched.
Source code in tit/catalog.py
2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 | |
delete_freehand_config ¶
delete_freehand_config(pm: PathManager, sid: str, name: str) -> bool
Remove a saved placement definition, leaving simulation outputs intact.
Source code in tit/catalog.py
group_catalog ¶
group_catalog(pm: PathManager) -> dict
Project-level group catalog: one row per run of every project-level writer.
stats (derivatives/ti-toolbox/stats/<type>/<name>/, :meth:PathManager.stats_output),
nilearn (derivatives/ti-toolbox/nilearn_visuals/<subdir_name>/, the
tit.plotting.nilearn runner), group_analyses (one folder per group analyzer run under
derivatives/ti-toolbox/group_analysis/, :meth:PathManager.group_analysis) and
nifti_average (derivatives/ti-toolbox/nifti_average/<output_name>/, the
tit.stats.nifti_average runner, with the files it wrote as artifacts).
tests/test_group_catalog_writers.py pins each reader against its writer.
Source code in tit/catalog.py
2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 | |
group_stats_detail ¶
group_stats_detail(pm: PathManager, analysis_type: str, name: str) -> dict | None
One derivatives/ti-toolbox/stats/<type>/<name>/ run, read for the Results pane.
None when the run directory does not exist. status is "ok" once the run has
written something besides its log, and "empty" when it has not -- the state the
maintainer hit, where a failed 2-vs-1 group comparison left a bare .log and the pane
could say nothing about it. reason is then the log's own ERROR line, so the UI
reports why instead of showing an empty file list.
Source code in tit/catalog.py
2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 | |
read_notes ¶
read_notes(pm: PathManager) -> dict
Quick Notes content (derivatives/ti-toolbox/notes.txt).
Source code in tit/catalog.py
write_notes ¶
write_notes(pm: PathManager, text: str) -> dict
Replace Quick Notes content, atomically.