#!/usr/bin/env python3 """Two-stage pipeline figure: ONE pipeline, two instantiations (T2M-GPT / NSLP-G). The two systems share an identical two-step structure, so it is drawn once: Step 1 learn a pose representation from SKELETONS ALONE (no text) motion -> encoder -> latent/codebook -> decoder -> motion Step 2 learn to emit that representation FROM TEXT, stage 1 frozen text -> text encoder -> generator -> predicted latents -> stage-1 decoder -> motion Each shared box names what the two systems put there (blue = T2M-GPT, purple = NSLP-G), and a strip at the bottom lists only the places they actually diverge. Visual language follows figs/Capture.PNG: numbered badges, outlined panels, and the latent sequence drawn as little squares. All real shapes, so it stays editable. """ import argparse import os from pptx import Presentation from pptx.dml.color import RGBColor from pptx.enum.dml import MSO_LINE_DASH_STYLE from pptx.enum.shapes import MSO_CONNECTOR, MSO_SHAPE from pptx.enum.text import MSO_ANCHOR, PP_ALIGN from pptx.oxml.ns import qn from pptx.util import Emu, Inches, Pt INK = RGBColor(0x1F, 0x24, 0x2E) MUTED = RGBColor(0x60, 0x6A, 0x78) PANEL_LN = RGBColor(0x7E, 0xB0, 0xE0) WHITE = RGBColor(0xFF, 0xFF, 0xFF) BADGE = RGBColor(0x2E, 0x75, 0xB6) BOX_BG, BOX_LN = RGBColor(0xDE, 0xEB, 0xF7), RGBColor(0x9D, 0xC3, 0xE6) TOK_BG, TOK_LN = RGBColor(0xD3, 0xC2, 0xE8), RGBColor(0x9B, 0x7E, 0xC8) IO_BG, IO_LN = RGBColor(0xF2, 0xF2, 0xF2), RGBColor(0xC8, 0xC8, 0xC8) GREY = RGBColor(0xBF, 0xBF, 0xBF) RED = RGBColor(0xC0, 0x00, 0x00) T2MC = RGBColor(0x1A, 0x4F, 0x8A) NSLC = RGBColor(0x6B, 0x2E, 0x9E) HDR_BG = RGBColor(0xF7, 0xF7, 0xF9) def _fill(shape, lines, align=PP_ALIGN.CENTER): tf = shape.text_frame tf.word_wrap = True tf.vertical_anchor = MSO_ANCHOR.MIDDLE tf.margin_left = tf.margin_right = Emu(18000) tf.margin_top = tf.margin_bottom = 0 for i, (t, sz, bd, col) in enumerate(lines): p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() p.alignment = align r = p.add_run(); r.text = t r.font.size = Pt(sz); r.font.bold = bd r.font.color.rgb = col; r.font.name = 'Calibri' def rbox(sl, x, y, w, h, lines, fill, line, *, radius=0.16, lw=1.25, shape=MSO_SHAPE.ROUNDED_RECTANGLE, align=PP_ALIGN.CENTER): s = sl.shapes.add_shape(shape, Inches(x), Inches(y), Inches(w), Inches(h)) s.fill.solid(); s.fill.fore_color.rgb = fill s.line.color.rgb = line; s.line.width = Pt(lw) s.shadow.inherit = False if shape == MSO_SHAPE.ROUNDED_RECTANGLE: try: s.adjustments[0] = radius except Exception: pass _fill(s, lines, align) return s def text(sl, x, y, w, lines, *, align=PP_ALIGN.LEFT, h=0.28): tb = sl.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) tf = tb.text_frame; tf.word_wrap = True tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = 0 for i, (t, sz, bd, col) in enumerate(lines): p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() p.alignment = align r = p.add_run(); r.text = t r.font.size = Pt(sz); r.font.bold = bd r.font.color.rgb = col; r.font.name = 'Calibri' return tb def arrow(sl, x1, y, x2, *, color=RGBColor(0x44, 0x44, 0x44), w=1.6): c = sl.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, Inches(x1), Inches(y), Inches(x2), Inches(y)) c.line.color.rgb = color; c.line.width = Pt(w) ln = c.line._get_or_add_ln() ln.append(ln.makeelement(qn('a:tailEnd'), {'type': 'triangle', 'w': 'med', 'len': 'med'})) return c def badge(sl, x, y, n, d=0.30): s = sl.shapes.add_shape(MSO_SHAPE.OVAL, Inches(x), Inches(y), Inches(d), Inches(d)) s.fill.solid(); s.fill.fore_color.rgb = BADGE s.line.fill.background(); s.shadow.inherit = False _fill(s, [(str(n), 12, True, WHITE)]) return s def cells(sl, x, y, n, *, s=0.20, gap=0.055, rows=1, ell=True): for r in range(rows): for i in range(n): sh = sl.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(x + i * (s + gap)), Inches(y + r * (s + gap)), Inches(s), Inches(s)) sh.fill.solid(); sh.fill.fore_color.rgb = TOK_BG sh.line.color.rgb = TOK_LN; sh.line.width = Pt(0.9) sh.shadow.inherit = False try: sh.adjustments[0] = 0.15 except Exception: pass endx = x + n * (s + gap) if ell: text(sl, endx - 0.01, y + (rows - 1) * (s + gap) / 2 - 0.02, 0.42, [('· · ·', 13, True, MUTED)], h=0.26) endx += 0.40 return endx def stickman(sl, x, y, w=0.56, h=0.84, col=RGBColor(0x33, 0x33, 0x33)): hd = sl.shapes.add_shape(MSO_SHAPE.OVAL, Inches(x + w / 2 - 0.085), Inches(y), Inches(0.17), Inches(0.17)) hd.fill.background(); hd.line.color.rgb = col; hd.line.width = Pt(1.4) hd.shadow.inherit = False def seg(x1, y1, x2, y2): c = sl.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, Inches(x1), Inches(y1), Inches(x2), Inches(y2)) c.line.color.rgb = col; c.line.width = Pt(1.5) cx = x + w / 2 seg(cx, y + 0.17, cx, y + 0.50) seg(cx, y + 0.24, x + 0.02, y + 0.13) seg(cx, y + 0.24, x + w - 0.02, y + 0.36) seg(cx, y + 0.50, x + 0.09, y + h) seg(cx, y + 0.50, x + w - 0.09, y + h) def main(): ap = argparse.ArgumentParser() ap.add_argument('--out', default='figs/two_step_pipeline.pptx') args = ap.parse_args() prs = Presentation() prs.slide_width = Inches(13.333) prs.slide_height = Inches(7.5) sl = prs.slides.add_slide(prs.slide_layouts[6]) text(sl, 0, 0.16, 13.333, [('Two-Step Pipeline — one structure, two instantiations', 23, True, INK)], align=PP_ALIGN.CENTER, h=0.4) text(sl, 0, 0.60, 13.333, [('T2M-GPT and NSLP-G share the identical two-step design; only the boxes marked ' 'below are filled differently.', 11.5, False, MUTED)], align=PP_ALIGN.CENTER, h=0.3) AY = 1.02 # panel 1 top BY = 3.42 # panel 2 top PH = 2.10 # =============================================== STEP 1 rbox(sl, 0.30, AY, 12.73, PH, [], WHITE, PANEL_LN, radius=0.05, lw=1.5, shape=MSO_SHAPE.ROUNDED_RECTANGLE) badge(sl, 0.48, AY + 0.13, 1) text(sl, 0.88, AY + 0.15, 6.4, [('Step 1: Latent Representation', 13, True, BADGE)], h=0.3) text(sl, 0.88, AY + 0.42, 6.4, [('trained on skeletons alone — no text', 9.5, False, MUTED)], h=0.24) y = AY + 0.80 # row top mid = y + 0.42 stickman(sl, 0.62, y + 0.02) text(sl, 0.40, y + 0.92, 1.0, [('Input Motion', 9, False, INK)], align=PP_ALIGN.CENTER, h=0.24) arrow(sl, 1.24, mid, 1.56) rbox(sl, 1.62, y + 0.02, 2.62, 0.82, [('Encoder', 11.5, True, INK), ('VQ-VAE (T2M-GPT)', 8.5, False, T2MC), ('Spatial VAE (NSLP-G)', 8.5, False, NSLC)], BOX_BG, BOX_LN) arrow(sl, 4.28, mid, 4.60) ex = cells(sl, 4.66, y + 0.14, 4, rows=2) text(sl, 4.45, y + 0.92, 1.9, [('Latent / Codebook', 9, False, INK), ('(compressed)', 8.5, False, MUTED)], align=PP_ALIGN.CENTER, h=0.4) arrow(sl, ex + 0.02, mid, ex + 0.34) dx = ex + 0.40 rbox(sl, dx, y + 0.02, 2.42, 0.82, [('Decoder', 11.5, True, INK), ('reconstructs the pose', 8.5, False, MUTED)], BOX_BG, BOX_LN) arrow(sl, dx + 2.44, mid, dx + 2.76) stickman(sl, dx + 2.82, y + 0.02) text(sl, dx + 2.60, y + 0.92, 1.0, [('Motion', 9, False, INK)], align=PP_ALIGN.CENTER, h=0.24) text(sl, 10.46, y + 0.02, 2.70, [('T2M-GPT ', 9.5, True, T2MC), ('discrete: 512-entry codebook,', 9, False, INK), ('4× temporal downsample', 9, False, INK), ('NSLP-G ', 9.5, True, NSLC), ('continuous: one Gaussian latent', 9, False, INK), ('per frame, no time compression', 9, False, INK)], h=1.3) # -------------------- linking arrow -------------------- a = sl.shapes.add_shape(MSO_SHAPE.DOWN_ARROW, Inches(6.42), Inches(AY + PH + 0.06), Inches(0.50), Inches(0.26)) a.fill.solid(); a.fill.fore_color.rgb = GREY a.line.fill.background(); a.shadow.inherit = False text(sl, 7.00, AY + PH + 0.05, 5.6, [('stage 1 is frozen; its decoder is reused below', 9, False, MUTED)], h=0.24) # =============================================== STEP 2 rbox(sl, 0.30, BY, 12.73, PH, [], WHITE, PANEL_LN, radius=0.05, lw=1.5) badge(sl, 0.48, BY + 0.13, 2) text(sl, 0.88, BY + 0.15, 6.4, [('Step 2: Frame Generation', 13, True, BADGE)], h=0.3) text(sl, 0.88, BY + 0.42, 6.4, [('text → latent, with step 1 frozen', 9.5, False, MUTED)], h=0.24) y = BY + 0.80 mid = y + 0.42 rbox(sl, 0.50, y + 0.02, 0.98, 0.82, [('Gloss /', 10, True, INK), ('sentence', 10, True, INK)], IO_BG, IO_LN) arrow(sl, 1.50, mid, 1.80) rbox(sl, 1.86, y + 0.02, 1.16, 0.82, [('Text', 10.5, True, INK), ('encoder', 10.5, True, INK)], BOX_BG, BOX_LN) arrow(sl, 3.04, mid, 3.34) rbox(sl, 3.40, y + 0.02, 2.14, 0.82, [('Generator', 11.5, True, INK), ('Autoregressive GPT (T2M-GPT)', 8.5, False, T2MC), ('Non-AR decoder (NSLP-G)', 8.5, False, NSLC)], BOX_BG, BOX_LN) arrow(sl, 5.56, mid, 5.86) ex = cells(sl, 5.92, y + 0.14, 3, rows=2) text(sl, 5.66, y + 0.92, 2.0, [('Predicted latents', 9, False, INK), ('/ tokens', 8.5, False, MUTED)], align=PP_ALIGN.CENTER, h=0.4) arrow(sl, ex + 0.02, mid, ex + 0.32) dx = ex + 0.38 rbox(sl, dx, y + 0.02, 1.62, 0.82, [('Step-1 Decoder', 10, True, INK), ('frozen', 8.5, False, MUTED)], BOX_BG, BOX_LN, lw=1.25) d = sl.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(dx), Inches(y + 0.02), Inches(1.62), Inches(0.82)) d.fill.background(); d.line.color.rgb = BADGE; d.line.width = Pt(1.1) d.line.dash_style = MSO_LINE_DASH_STYLE.DASH d.shadow.inherit = False arrow(sl, dx + 1.64, mid, dx + 1.94) stickman(sl, dx + 2.00, y + 0.02) text(sl, dx + 1.70, y + 0.92, 1.16, [('Output Motion', 9, False, INK)], align=PP_ALIGN.CENTER, h=0.24) text(sl, 10.46, y - 0.02, 2.70, [('T2M-GPT ', 9.5, True, T2MC), ('one token at a time; sampling', 9, False, INK), ('from the codebook; ', 9, False, INK), ('Cross-Entropy', 9, True, RED), ('NSLP-G ', 9.5, True, NSLC), ('all frames at once; regression', 9, False, INK), ('to the latent; ', 9, False, INK), ('MSE', 9, True, BADGE)], h=1.5) # =============================================== bottom strip SY = BY + PH + 0.18 rows = [('', 'T2M-GPT', 'NSLP-G'), ('Step-1 representation', 'discrete codebook (512)', 'continuous Gaussian, per frame'), ('Time compression', '4× downsample', 'none'), ('Step-2 decoding', 'autoregressive + sampling', 'non-autoregressive, parallel'), ('Step-2 objective', 'Cross-Entropy', 'MSE')] x0, wl, w1, hh = 0.30, 3.10, 4.81, 0.27 for i, (a_, b_, c_) in enumerate(rows): yy = SY + i * hh head = i == 0 for (xx, ww, tt, al) in ((x0, wl, a_, PP_ALIGN.LEFT), (x0 + wl, w1, b_, PP_ALIGN.CENTER), (x0 + wl + w1, w1, c_, PP_ALIGN.CENTER)): col = T2MC if (head and tt == 'T2M-GPT') else NSLC if (head and tt == 'NSLP-G') else INK s = rbox(sl, xx, yy, ww, hh, [(tt, 9.5, head, col)], HDR_BG if head else WHITE, IO_LN, lw=0.75, shape=MSO_SHAPE.RECTANGLE, align=al) os.makedirs(os.path.dirname(args.out) or '.', exist_ok=True) prs.save(args.out) print(f'wrote {args.out}') if __name__ == '__main__': main()