vita: generate the project index, and add a web_visible axis

The project index was the last hand-maintained duplicate of CV data, and it
had already drifted: a superseded Weenix line count and an end date the CV
disagreed with. It now renders from yaml-source/data/projects.yml, which
grows from three entries to nine and gains `group` (drives the index's
sections, first-appearance order, like now.yaml) and `links`.

Pmacs, Levshell, and Epiphany join under "Personal" — self-directed tools
rather than coursework or employment — written from the repos rather than
from memory. They are web-only: personal infrastructure is not academic
output, and the résumé is a full page. LeVCS keeps its essay; project
writeups stay essays, since a writeup is an informal presentation of a
project and not a record of it.

web_visible is a third visibility axis alongside cv_visible and
resume_visible, defaulting to cv_visible so nothing changes until it is
set. The file already knew how to let the CV and résumé disagree about an
entry; generating a third surface from the same data would otherwise have
collapsed that, and a document handed to a chosen reader is not a page that
gets crawled. This is what makes web-only projects expressible — and what
would let the xAI entry sit quietly on the CV while staying off the site,
if that is wanted later. It stays off both for now.

Two LaTeX conversions added while moving the markdown prose into the YAML:
$\rightarrow$ and $\sim$. The latter fixes a bug I introduced doing it —
"~10 crates" meant "about ten", but bare ~ is LaTeX's non-breaking space,
so it rendered "( 10 crates" and dropped the approximation.

Weenix's description now carries the site's fuller tail (the CS 169 origin
and the later pipes/preemption work) rather than the CV's terser "Brown CS
169." Single-sourcing means picking one, and this is the better one; the CV
grows by a clause and the résumé still fits on one page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SUGesXiMmACsLBTGG1xuEU
This commit is contained in:
Levi Neuwirth 2026-08-11 09:07:38 +02:00
parent af4c9e4ef2
commit 1f1969ab28
No known key found for this signature in database
7 changed files with 271 additions and 41 deletions

View File

@ -29,7 +29,7 @@ import Compilers (essayCompiler, postCompiler, pageCompiler, poetryCompiler, fi
import Catalog (musicCatalogCtx)
import Commonplace (commonplaceCtx)
import Now (nowCtx)
import Vita (vitaCtx)
import Vita (vitaCtx, projectsCtx)
import Contexts (siteCtx, essayCtx, postCtx, pageCtx, poetryCtx, fictionCtx, compositionCtx,
contentKindField, recentFirstByDisplay,
tagLinksFieldExcludingTopSegment, isProvedConfidence)
@ -404,7 +404,18 @@ rules = do
-- so nginx serves them via index-file resolution and the URLs stay stable
-- if the underlying files are later reorganized into co-located directories.
-- ---------------------------------------------------------------------------
match "content/cv/*.md" $ do
-- The project index is generated from yaml-source/data/projects.yml —
-- the same file that feeds the CV's and résumé's project sections — so
-- it gets projectsCtx and its own template. The markdown keeps only the
-- page's framing prose. Individual project writeups stay as essays.
match "content/cv/projects.md" $ do
route $ constRoute "cv/projects/index.html"
compile $ pageCompiler
>>= loadAndApplyTemplate "templates/projects.html" projectsCtx
>>= loadAndApplyTemplate "templates/default.html" projectsCtx
>>= relativizeUrls
match ("content/cv/*.md" .&&. complement "content/cv/projects.md") $ do
route $ customRoute $ \ident ->
let fname = takeFileName (toFilePath ident)
slug = takeWhile (/= '.') fname

View File

@ -21,10 +21,12 @@
-- for the full supported set and why escaping happens before conversion.
module Vita
( vitaCtx
, projectsCtx
) where
import Data.Aeson (FromJSON (..), Object, Value (..), withObject, (.:), (.:?), (.!=))
import Data.Aeson.Types (Parser, typeMismatch)
import Data.Char (toLower)
import Data.List (isPrefixOf, sortOn)
import Data.Maybe (mapMaybe)
import Data.Scientific (isInteger, toRealFloat)
@ -62,6 +64,23 @@ reqLoose o k = unLoose <$> o .: K.fromString k
optLoose :: Object -> String -> Parser (Maybe String)
optLoose o k = fmap unLoose <$> o .:? K.fromString k
-- | Whether an entry appears on this page.
--
-- The YAML has carried two visibility axes since it drove two documents:
-- @cv_visible@ and @resume_visible@, so the CV and the résumé can disagree
-- about an entry without duplicating it. Generating the vita page from the
-- same data added a third surface, and folding it into @cv_visible@ would
-- have silently collapsed a distinction the file already knew how to make
-- — an entry can be worth keeping on a document handed to a reader while
-- being wrong for a page that is crawled.
--
-- @web_visible@ therefore defaults to @cv_visible@: existing entries behave
-- exactly as before, and the axis only exists where someone sets it.
webVisible :: Object -> Parser Bool
webVisible o = do
cv <- o .:? "cv_visible" .!= True
o .:? "web_visible" .!= cv
-- ---------------------------------------------------------------------------
-- Entry types
-- ---------------------------------------------------------------------------
@ -84,7 +103,7 @@ data Edu = Edu
, edEnd :: Maybe String
, edGpa :: Maybe String
, edNotes :: Maybe String
, edVisible :: Bool
, edWeb :: Bool
}
instance FromJSON Edu where
@ -96,7 +115,7 @@ instance FromJSON Edu where
<*> optLoose o "end"
<*> optLoose o "gpa"
<*> optLoose o "notes_cv"
<*> o .:? "cv_visible" .!= True
<*> webVisible o
newtype EduDoc = EduDoc { unEduDoc :: [Edu] }
@ -112,7 +131,7 @@ data Pub = Pub
, pbTarget :: Maybe String
, pbLinks :: [Link]
, pbNote :: Maybe String
, pbVisible :: Bool
, pbWeb :: Bool
}
instance FromJSON Pub where
@ -125,7 +144,7 @@ instance FromJSON Pub where
<*> optLoose o "target"
<*> o .:? "links" .!= []
<*> optLoose o "equal_contrib_note"
<*> o .:? "cv_visible" .!= True
<*> webVisible o
newtype PubDoc = PubDoc { unPubDoc :: [Pub] }
@ -140,7 +159,7 @@ data Pres = Pres
, prYear :: String
, prMonth :: Maybe String
, prStatus :: Maybe String
, prVisible :: Bool
, prWeb :: Bool
}
instance FromJSON Pres where
@ -152,7 +171,7 @@ instance FromJSON Pres where
<*> reqLoose o "year"
<*> optLoose o "month"
<*> optLoose o "status"
<*> o .:? "cv_visible" .!= True
<*> webVisible o
newtype PresDoc = PresDoc { unPresDoc :: [Pres] }
@ -170,7 +189,7 @@ data Exp = Exp
, exOrder :: Int
, exPreamble :: Maybe String
, exBullets :: [String]
, exVisible :: Bool
, exWeb :: Bool
}
instance FromJSON Exp where
@ -185,27 +204,54 @@ instance FromJSON Exp where
<*> o .:? "cv_order" .!= 99
<*> optLoose o "cv_preamble"
<*> o .:? "bullets" .!= []
<*> o .:? "cv_visible" .!= True
<*> webVisible o
newtype ExpDoc = ExpDoc { unExpDoc :: [Exp] }
instance FromJSON ExpDoc where
parseJSON = withObject "ExpDoc" $ \o -> ExpDoc <$> o .: "experience"
data Proj = Proj
{ pjName :: String
, pjGroup :: String
, pjEssay :: Maybe String
, pjStart :: String
, pjEnd :: Maybe String
, pjDescription :: String
, pjLinks :: [Link]
, pjWeb :: Bool
}
instance FromJSON Proj where
parseJSON = withObject "Proj" $ \o -> Proj
<$> reqLoose o "name"
<*> o .:? "group" .!= "Projects"
<*> optLoose o "essay"
<*> reqLoose o "start"
<*> optLoose o "end"
<*> reqLoose o "description"
<*> o .:? "links" .!= []
<*> webVisible o
newtype ProjDoc = ProjDoc { unProjDoc :: [Proj] }
instance FromJSON ProjDoc where
parseJSON = withObject "ProjDoc" $ \o -> ProjDoc <$> o .: "projects"
-- | @personal.yml@ also carries a @display@ string per link (the value the
-- CV prints in full, since paper cannot be clicked). It is deliberately
-- not read here — see 'renderContact'.
data ProfileLink = ProfileLink
{ plLabel :: String
, plHref :: String
, plVisible :: Bool
, plWeb :: Bool
}
instance FromJSON ProfileLink where
parseJSON = withObject "ProfileLink" $ \o -> ProfileLink
<$> reqLoose o "label"
<*> reqLoose o "href"
<*> o .:? "cv_visible" .!= True
<*> webVisible o
-- | Contact details from @personal.yml@. The phone number is deliberately
-- not parsed: it is printed on the CV PDF, which is a document handed to
@ -246,6 +292,10 @@ latexToHtml =
. substAll "--" "&ndash;"
. substAll "$\\times$" "&times;"
. substAll "$\\delta$" "&delta;"
. substAll "$\\rightarrow$" "&rarr;"
-- Approximation, not a non-breaking space. Bare `~` is LaTeX's nbsp, so
-- "~10 crates" silently renders as "( 10 crates" and loses the "about".
. substAll "$\\sim$" "~"
. substAll "\\#" "#"
. substAll "{,}" ","
. substAll "~" "&nbsp;"
@ -371,7 +421,7 @@ renderEducation es
, "</ul>"
]
where
visible = filter edVisible es
visible = filter edWeb es
one e = concat
[ "<li class=\"item-card vita-card\">"
, "<div class=\"item-card-main\">"
@ -395,7 +445,7 @@ renderPublications ps
, footnote
]
where
visible = filter pbVisible ps
visible = filter pbWeb ps
-- The dagger legend lives on whichever entry declares it, but reads as a
-- section-level note, so it is rendered once at the foot of the list.
footnote = case mapMaybe pbNote visible of
@ -434,7 +484,7 @@ renderPresentations ps
, "</ul>"
]
where
visible = filter prVisible ps
visible = filter prWeb ps
dateOf p = maybe "" (\m -> tex m ++ " ") (prMonth p) ++ tex (prYear p)
one p = concat
[ "<li class=\"item-card vita-card\">"
@ -458,7 +508,7 @@ renderPresentations ps
renderExperience :: [Exp] -> String
renderExperience xs = research ++ industry
where
visible = sortOn exOrder (filter exVisible xs)
visible = sortOn exOrder (filter exWeb xs)
isRes e = exSection e == Just "research"
research = group "experience-research" "Research Experience" (filter isRes visible)
industry = group "experience-industry" "Industry Experience" (filter (not . isRes) visible)
@ -481,13 +531,57 @@ renderExperience xs = research ++ industry
, "</li>"
]
-- | The @/cv/projects/@ index. Groups render in first-appearance order, the
-- same convention "Now" uses for its sections — reordering the YAML
-- reorders the page and no separate ordering key is needed.
--
-- Entry titles link to the project's essay where one exists. The essays are
-- deliberately not generated: a writeup is an informal presentation of a
-- project, not a record of it, and belongs in the same voice as the rest of
-- the essays.
renderProjects :: [Proj] -> String
renderProjects ps = concatMap one (groupOrder visible)
where
visible = filter pjWeb ps
groupOrder = foldl (\acc g -> if g `elem` acc then acc else acc ++ [g]) []
. map pjGroup
-- "Machine Learning & Deployed" → "machine-learning-deployed".
slugify s = case foldr step [] s of
('-':rest) -> rest
cleaned -> cleaned
where
step c acc
| c `elem` (['a'..'z'] ++ ['0'..'9']) = c : acc
| c `elem` ['A'..'Z'] = toLower c : acc
| null acc || head acc == '-' = acc
| otherwise = '-' : acc
one g = section ("projects-" ++ slugify g) (escapeHtml g) $ concat
[ "<ul class=\"item-card-list vita-list\">"
, concatMap entry (filter ((== g) . pjGroup) visible)
, "</ul>"
]
entry p = concat
[ "<li class=\"item-card vita-card\">"
, "<div class=\"item-card-main\">"
, "<h3 class=\"vita-entry-title\">"
, case pjEssay p of
Just u -> "<a href=\"" ++ escapeHtml u ++ "\">" ++ tex (pjName p) ++ "</a>"
Nothing -> tex (pjName p)
, "</h3>"
, metaLine (dateRange (pjStart p) (pjEnd p)) Nothing Nothing
, "<p class=\"vita-note\">", tex (pjDescription p), "</p>"
, renderLinks (pjLinks p)
, "</div>"
, "</li>"
]
renderContact :: Person -> String
renderContact p = section "contact" "Contact" $ concat
[ "<p class=\"vita-links\">"
, "<a class=\"vita-chip\" href=\"mailto:", escapeHtml (pnEmail p), "\">"
, escapeHtml (pnEmail p)
, "</a>"
, concatMap one (filter plVisible (pnLinks p))
, concatMap one (filter plWeb (pnLinks p))
, "</p>"
]
where
@ -541,3 +635,12 @@ vitaCtx =
<> sectionField "vita-contact-html"
(renderContact <$> loadYaml "yaml-source/data/personal.yml")
<> siteCtx
-- | The @/cv/projects/@ index. Reuses the vita flag so it picks up the same
-- stylesheets and reads as the same kind of surface.
projectsCtx :: Context String
projectsCtx =
constField "vita" "true"
<> sectionField "vita-projects-html"
(renderProjects . unProjDoc <$> loadYaml "yaml-source/data/projects.yml")
<> siteCtx

View File

@ -4,22 +4,6 @@ tags: meta
portal: true
---
Index of engineering artifacts. Systems depth is the primary axis of this page; machine-learning and deployed artifacts follow.
Index of engineering artifacts, generated from the same data as the project sections of the [CV](/cv.pdf) and [résumé](/resume.pdf). Systems depth is the primary axis; self-directed tools and deployed machine-learning work follow.
## Low-Level & Systems
- **[Weenix](/essays/weenix/)**\
Unix-like kernel in 10,000 lines of C. Virtual memory, VFS, system calls, threading, device drivers, interrupt handlers, and file systems; custom linker support for running userspace x86-64 ELF binaries. Originally a project from Brown CS 169 (Operating Systems with Lab), extended with further features like pipes and userspace preemption. January August 2025.
- **[Networking Stack from Scratch](/essays/networking-stack/)**\
TCP/IP, RIP, UDP, and DNS in Go, supporting file transmission of up to 1 GB across networks of 8 virtual machines. Extended with a fully RFC-compliant SSH implementation (2,000+ additional lines) supporting sustained sessions of arbitrary length. October 2024 July 2025.
- **[Where Does SIMD Help Post-Quantum Cryptography?](/essays/where-does-simd-help-post-quantum-cryptography/)** · [Artifact](https://git.levineuwirth.org/neuwirth/where-simd-helps)\
Hand-written AVX2 assembly for ML-KEM / Kyber. 35×56× speedup over compiler-optimized C for core NTT arithmetic; 5.4×7.1× end-to-end KEM speedup. Full statistical-analysis pipeline (Mann-Whitney U, Cliff's δ, bootstrapped CIs) on Brown's OSCAR HPC cluster. Phase 1 report and reproducible artifact public.
- **[LeVCS](/essays/levcs/)** · [Artifact](https://git.levineuwirth.org/neuwirth/levcs) · [Instance](https://levcs.levineuwirth.org)\
Distributed version control system in Rust (~10 crates, 194 passing tests at v0.1.0). BLAKE3 content addressing, signed Ed25519 authority chains for protocol-level identity and push authorization, federation as the normal operating mode with three storage modes (full / release / metadata), and a cascading per-file merge engine (textual → format-aware → tree-sitter → wasm plugin) that resolves git's common false conflicts. Substrate complete; workflow surface (PR/review, issues, web UI) deferred. First federation instance at [levcs.levineuwirth.org](https://levcs.levineuwirth.org). April 2026 present.
## Machine Learning & Deployed
- **[ICD-10-CM outcome calculator](https://levineuwirth.github.io/icd_embeddings/)** · [Preprint](/essays/beyond-comorbidity-indices/) · [Code](https://github.com/levineuwirth/icd_embeddings)\
Public, read-only calculator for the permutation-invariant Deep Sets model underlying the paper currently under review at *JAMIA*. Takes a diagnosis-code set; returns 30-day readmission and postdischarge mortality predictions with Integrated-Gradients attribution.
- **[NeuroPose](/essays/neuropose/)**\
3D pose-estimation and kinematic-analysis system for neurological-recovery research in Liqi Shu's laboratory at Brown Neurology. Python/TensorFlow inference pipeline, MATLAB-based statistical post-processing, Rust backend with HTML/JS frontends. 20,000+ lines across four externally-funded sub-projects since 2023.
Where a project has a writeup, its title links to it. Those are essays rather than records — informal, longer, and written in their own voice.

Binary file not shown.

Binary file not shown.

7
templates/projects.html Normal file
View File

@ -0,0 +1,7 @@
<main id="markdownBody" data-pagefind-body>
<h1 class="page-title">$title$</h1>
<div class="vita-intro">$body$</div>
$vita-projects-html$
</main>

View File

@ -1,29 +1,154 @@
# Projects shown on CV under "Systems and Engineering Projects" and on
# résumé under "Projects". The PQC entry is research-grade and also
# appears in Publications on the CV; on the résumé it's the strongest
# project and leads the list.
# Projects. One list, three consumers, three visibility axes:
#
# cv_visible — CV, under "Systems and Engineering Projects"
# resume_visible — résumé, under "Projects" (ordered by resume_order)
# web_visible — the /cv/projects/ index (defaults to cv_visible)
#
# The web index was hand-written markdown until August 2026 and had already
# drifted from this file (a superseded Weenix line count, dates that ended
# where the CV said "Present"). It is now generated from here, so the three
# surfaces cannot disagree. Individual project *writeups* stay as essays —
# they are informal presentations, not records, and `essay` points at them.
#
# `group` drives the index's section headings and renders in first-appearance
# order, like now.yaml's sections. Descriptions are LaTeX-flavoured because
# xelatex is the first consumer; build/Vita.hs converts the same subset for
# the web (\textbf, \textit, \texttt, \href, $\times$, --, ~, {,}).
projects:
# ---------------------------------------------------------------------
# Systems & Infrastructure
# ---------------------------------------------------------------------
- name: Post-Quantum Cryptography on x86 AVX2
group: Systems & Infrastructure
essay: /essays/where-does-simd-help-post-quantum-cryptography/
start: March 2025
end: Present
description: "Micro-architectural study of SIMD contributions to ML-KEM / Kyber on Intel AVX2, conducted on Brown's OSCAR HPC cluster. Hand-written AVX2 assembly achieves 35--56$\\times$ speedup over compiler-optimized C for core NTT arithmetic; 5.4--7.1$\\times$ end-to-end KEM speedup. Full statistical analysis (Mann-Whitney U, Cliff's $\\delta$, bootstrapped CIs). Technical report and reproducible artifact public."
links:
- label: Report
href: /essays/where-does-simd-help-post-quantum-cryptography/
- label: Artifact
href: https://git.levineuwirth.org/neuwirth/where-simd-helps
cv_visible: false # appears under Publications instead on CV
resume_visible: true
resume_order: 1
web_visible: true
- name: LeVCS
group: Systems & Infrastructure
essay: /essays/levcs/
start: April 2026
end: Present
description: "Distributed version control system in Rust ($\\sim$10 crates, 194 passing tests at v0.1.0). BLAKE3 content addressing, signed Ed25519 authority chains for protocol-level identity and push authorization, federation as the normal operating mode with three storage modes (full / release / metadata), and a cascading per-file merge engine (textual $\\rightarrow$ format-aware $\\rightarrow$ tree-sitter $\\rightarrow$ wasm plugin) that resolves git's common false conflicts. Substrate complete; workflow surface (PR/review, issues, web UI) deferred."
links:
- label: Writeup
href: /essays/levcs/
- label: Artifact
href: https://git.levineuwirth.org/neuwirth/levcs
- label: Instance
href: https://levcs.levineuwirth.org
cv_visible: false
resume_visible: false
web_visible: true
- name: Weenix
group: Systems & Infrastructure
essay: /essays/weenix/
start: January 2025
end: August 2025
description: "Full Unix-like kernel in 10{,}000 lines of C: virtual memory, VFS, system calls, threading, device drivers and interrupt handlers, and file systems with custom linker support for running userspace x86-64 ELF binaries. Brown CS 169."
description: "Full Unix-like kernel in 10{,}000 lines of C: virtual memory, VFS, system calls, threading, device drivers and interrupt handlers, and file systems with custom linker support for running userspace x86-64 ELF binaries. Originally Brown CS 169 (Operating Systems with Lab), extended afterward with pipes and userspace preemption."
links:
- label: Writeup
href: /essays/weenix/
cv_visible: true
resume_visible: true
resume_order: 2
web_visible: true
- name: Networking Stack from Scratch
group: Systems & Infrastructure
essay: /essays/networking-stack/
start: October 2024
end: July 2025
description: "TCP/IP, RIP, UDP, and DNS implementations in Go, supporting file transmission of up to 1~GB across networks of up to 8 virtual machines."
description: "TCP/IP, RIP, UDP, and DNS implementations in Go, supporting file transmission of up to 1~GB across networks of up to 8 virtual machines. Extended with a fully RFC-compliant SSH implementation (2{,}000+ additional lines) supporting sustained sessions of arbitrary length."
links:
- label: Writeup
href: /essays/networking-stack/
cv_visible: true
resume_visible: true
resume_order: 3
web_visible: true
# ---------------------------------------------------------------------
# Personal — self-directed tools, not coursework or employment. Web-only:
# they are infrastructure I use rather than academic output, and the
# résumé is a full page already.
# ---------------------------------------------------------------------
- name: Pmacs
group: Personal
start: "2025"
end: Present
description: "Parallel Emacs: a Rust-cored, Lua-scripted editor in the Emacs tradition, at v1.1.0 with prebuilt binaries. The hot path (rope, buffers, views, async runtime, process supervision) is Rust; commands, keymaps, hooks, and packages are an embedded Lua VM. Partitioned into a long-lived instance and thin frontends over a typed protocol: a \\texttt{crossterm} TUI attachable over SSH with \\texttt{mosh}-style reconnect, and a GPU frontend (wgpu / winit / glyphon) that renders from a semantic projection of editor state and edits optimistically against a local CRDT replica. Buffers are optionally CRDT-backed, so both frontends can edit the same buffer concurrently."
links:
- label: Source
href: https://github.com/levineuwirth/pmacs
cv_visible: false
resume_visible: false
web_visible: true
- name: Levshell
group: Personal
start: "2026"
end: Present
description: "Wayland-targeted productivity and research shell, in daily use as my main environment. Five Rust crates over a sway/i3-ipc substrate: a capture-and-restore planner for desktop state, an event-logged store with UUIDv7 ids, a daemon whose every action is a persisted job under a global lock with rollback capsules taken before any mutating restore, and a control CLI. Currently growing from a single machine to a fabric of machines that work as one, with project resume as the hero verb."
cv_visible: false
resume_visible: false
web_visible: true
- name: Epiphany
group: Personal
start: "2026"
end: Present
description: "FOSS music-notation platform: a specified, deterministic, CRDT-based score model whose LaTeX specification suite is the source of truth rather than its documentation. Twelve Rust crates across two tracks — the wire format, bundle, operations, and text projection on one; the editing seam, engraving, and layout IR on the other. Work is scoped into ratified contracts with numbered pins, mutation tables, and adversarial review rounds before any code is dispatched."
links:
- label: Source
href: https://github.com/levineuwirth/epiphany
cv_visible: false
resume_visible: false
web_visible: true
# ---------------------------------------------------------------------
# Machine Learning & Deployed
# ---------------------------------------------------------------------
- name: ICD-10-CM outcome calculator
group: Machine Learning & Deployed
essay: /essays/beyond-comorbidity-indices/
start: "2025"
end: Present
description: "Public, read-only calculator for the permutation-invariant Deep Sets model underlying the paper under review at \\textit{JAMIA}. Takes a diagnosis-code set; returns 30-day readmission and postdischarge mortality predictions with Integrated-Gradients attribution."
links:
- label: Calculator
href: https://levineuwirth.github.io/icd_embeddings/
- label: Preprint
href: /essays/beyond-comorbidity-indices/
- label: Code
href: https://git.levineuwirth.org/neuwirth/beyond_comorbidity_indices
cv_visible: false # appears under Publications and the Shu Lab bullet on CV
resume_visible: false
web_visible: true
- name: NeuroPose
group: Machine Learning & Deployed
essay: /essays/neuropose/
start: "2023"
end: Present
description: "3D pose-estimation and kinematic-analysis system for neurological-recovery research in Liqi Shu's laboratory at Brown Neurology. Python/TensorFlow inference pipeline, MATLAB statistical post-processing, Rust backend with HTML/JS frontends. 20{,}000+ lines across four externally-funded sub-projects since 2023."
links:
- label: Writeup
href: /essays/neuropose/
- label: Artifact
href: https://git.levineuwirth.org/neuwirth/neuropose
cv_visible: false # appears via the Shu Lab bullet on CV
resume_visible: false
web_visible: true