Spino pelvic relation in THR
spinopelvic relationship total hip arthroplasty
spinopelvic relationship total hip arthroplasty pelvic tilt acetabular cup

This set of four lateral radiographs (labeled A-D) illustrates the dynamic relationship between pelvic orientation and total hip arthroplasty (THA) component position in patients with varying spinopelvic mobility. Images A and B depict a patient with spinopelvic hypermobility in standing (A) and sitting (B) positions. Note the significant change in pelvic tilt (PT) and acetabular anteinclination (AI) when moving from standing to sitting, characterized by a more vertical cup orientation in the seated position. Images C and D depict a patient with spinopelvic stiffness in standing (C) and sitting (D) positions. In the stiff patient, the pelvic orientation remains relatively fixed, resulting in a more horizontal cup position in the sitting phase compared to the hypermobile patient. Key measurements shown include Pelvic Tilt (PT), the angle between a vertical line and a line from the center of the femoral head to the midpoint of the sacral endplate, and Anteinclination (AI), representing a combined parameter of cup anteversion and inclination. These images demonstrate how limited lumbar and pelvic movement (stiffness) can adversely affect functional component position during postural transitions.

This lateral standing radiograph of the full spine and pelvis illustrates two distinct methods for measuring pelvic tilt in the sagittal plane. The image depicts a patient with a total hip arthroplasty (THA), showing the femoral stem and acetabular cup in situ. Method 1 (Arthroplasty perspective) is marked by line 1, defining the Anterior Pelvic Plane Tilt (APPt) as the angle between the coronal plane and a line connecting the anterior superior iliac spine (ASIS) and the pubic symphysis. Method 2 (Spine surgeon perspective), marked by line 2, illustrates Spinopelvic Tilt (sPT), defined as the angle between a vertical reference line and a line from the center of the femoral heads to the midpoint of the S1 sacral endplate. The radiograph demonstrates clinical landmarks including the thoracic kyphosis, lumbar spine, sacrum, and iliac crests. These measurements are essential for assessing spinopelvic mobility and sagittal balance, particularly in preoperative planning for hip arthroplasty to minimize dislocation risk related to spinal alignment and pelvic retroversion.

A composite of three X-ray radiographs demonstrating the assessment of spinopelvic parameters and total hip replacement (THR) implant positioning. (A) Anteroposterior (AP) coronal radiograph of the pelvis showing a right-sided total hip arthroplasty. Yellow annotation lines indicate the measurement of acetabular cup inclination and anteversion relative to the transischial line. (B) and (C) are sagittal radiographs showing the lumbosacral spine and pelvis in sitting and standing positions, respectively. These images are annotated with surgical planning software lines (SurgiMap) to measure dynamic parameters: Lumbar Lordosis (LL), Sacral Slope (SS), Pelvic Incidence (PI), Spinopelvic Tilt (PT), Anterior Pelvic Plane Tilt (APPt), Pelvic Femoral Angle (PFA), and Acetabular Anteinclination (AI). The comparison illustrates how the lumbar spine flattens and the pelvis rotates posteriorly when moving from a standing to a sitting position, which is critical for evaluating risk factors like prosthetic impingement or dislocation in orthopedic follow-up.

This series of three anteroposterior (AP) pelvic radiographs demonstrates the impact of pelvic tilt and posture on total hip arthroplasty (THA) component orientation in a 64-year-old female. Image (a) shows the pelvis in a supine position, with the left THA acetabular cup displaying an inclination of 45° and anteversion of 25°. Image (b) shows the same patient in a standing position, where a marked posterior (backward) pelvic tilt is visible, causing a secondary increase in cup inclination to 59° and anteversion to 42°. This demonstrates the dynamic relationship between spinal-pelvic alignment and prosthetic orientation. Image (c) provides a clinical complication example: a post-THA radiograph showing an anterior dislocation of the left hip prosthesis, where the femoral head component is completely displaced from the acetabular cup. These images serve as educational material for orthopedic surgery, specifically focusing on preoperative planning, the effects of sagittal pelvic mobility on implant stability, and postoperative complication monitoring.

This composite of two diagnostic X-ray radiographs displays postoperative evaluations in an orthopedic context. Panel (a) is an anteroposterior radiograph of the pelvis and hips, demonstrating a right total hip arthroplasty (THA). The prosthesis consists of a cementless acetabular cup reinforced with screw fixation and a tapered wedge-type cementless femoral stem. The contralateral hip appears intact for comparison. Panel (b) shows a full-length lateral upright spine radiograph used for sagittal balance assessment. Annotated measurements illustrate global sagittal alignment, including the sagittal vertical axis (SVA), lumbar lordosis (LL) angle, and pelvic tilt (PT). The imaging highlights clinical findings of decreased lumbar lordosis (8°) and anterior trunk tilt (SVA 103 mm), indicating significant sagittal plane imbalance. These images demonstrate the relationship between hip replacement surgery and spinopelvic parameters, commonly analyzed in patients with complex degenerative conditions or Rapidly Destructive Coxopathy (RDC).
| Parameter | Definition | Normal Value |
|---|---|---|
| Pelvic Incidence (PI) | Angle between a perpendicular to the S1 endplate and a line from midpoint of S1 endplate to the center of femoral heads. Fixed anatomical parameter. | ~50-55° |
| Sacral Slope (SS) | Angle between the S1 superior endplate and a horizontal reference line | ~40° |
| Pelvic Tilt (PT) | Angle between a vertical line and a line from femoral head center to midpoint of S1 endplate | ~12-15° |
| Lumbar Lordosis (LL) | Sagittal curve of the lumbar spine (L1-S1) | ~50-60° |
| PI-LL mismatch | Difference; >10° is abnormal (flatback deformity) | <10° |


| Group | Classification | Pathology | Anteversion Target |
|---|---|---|---|
| 1A | Normal alignment + Normal mobility | APP ≈ FPP; normal kinematics | 20-25° (to APP) |
| 1B | Normal alignment + Stiff spine (SS change ≤10°) | APP ≈ FPP but no posterior tilt compensation | 30° (to FPP) - higher target to prevent posterior dislocation when sitting |
| 2A | Flatback deformity (PI-LL >10°) + Normal mobility | FPP differs from APP due to posterior pelvic tilt at baseline | 25-30° (to FPP) |
| 2B | Flatback deformity + Stiff spine | Both problems combined - FPP is retroverted AND stiff | 30° (to FPP) - must account for deformity AND stiffness |
Make a presentation summarizing hip-spine classification and surgical recommendations
mkdir -p /tmp/workspace/hip-spine-pptx && ls /tmp/workspace/hip-spine-pptx/
~/hip-spine-pptx/build_pptx.py
"""
Hip-Spine Classification & Surgical Recommendations in THR
Professional PPTX presentation builder
"""
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
from io import BytesIO
import json, subprocess, base64
# ── Colour palette ──────────────────────────────────────────────
NAVY = RGBColor(0x0D, 0x2B, 0x55) # deep navy – headers / title bg
TEAL = RGBColor(0x00, 0x7B, 0x8E) # teal accent
GOLD = RGBColor(0xE8, 0xA8, 0x00) # gold accent
LTBLUE = RGBColor(0xD6, 0xEA, 0xF3) # light blue – slide bg
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
DARK_GREY = RGBColor(0x33, 0x33, 0x33)
MID_GREY = RGBColor(0x66, 0x66, 0x66)
RED = RGBColor(0xC0, 0x39, 0x2B)
GREEN = RGBColor(0x1A, 0x7A, 0x3C)
ORANGE = RGBColor(0xE6, 0x7E, 0x22)
# group colours
G1A_COL = RGBColor(0x1A, 0x7A, 0x3C) # green
G1B_COL = RGBColor(0xE6, 0x7E, 0x22) # orange
G2A_COL = RGBColor(0x21, 0x6B, 0xA0) # blue
G2B_COL = RGBColor(0xC0, 0x39, 0x2B) # red
W = Inches(13.333)
H = Inches(7.5)
# ── Helper functions ─────────────────────────────────────────────
def new_prs():
prs = Presentation()
prs.slide_width = W
prs.slide_height = H
return prs
def blank_slide(prs):
return prs.slides.add_slide(prs.slide_layouts[6])
def bg_rect(slide, color, left=0, top=0, width=None, height=None):
"""Full or partial background rectangle."""
w = width if width is not None else W
h = height if height is not None else H
shp = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Emu(left), Emu(top), Emu(w), Emu(h))
shp.fill.solid()
shp.fill.fore_color.rgb = color
shp.line.fill.background()
shp.shadow.inherit = False
return shp
def textbox(slide, text, left, top, width, height,
font_name="Calibri", font_size=18, bold=False, italic=False,
color=DARK_GREY, align=PP_ALIGN.LEFT, wrap=True,
v_anchor=MSO_ANCHOR.TOP, margins=(0,0,0,0)):
tb = slide.shapes.add_textbox(Emu(left), Emu(top), Emu(width), Emu(height))
tf = tb.text_frame
tf.word_wrap = wrap
tf.vertical_anchor = v_anchor
tf.margin_left = Pt(margins[0])
tf.margin_right = Pt(margins[1])
tf.margin_top = Pt(margins[2])
tf.margin_bottom = Pt(margins[3])
p = tf.paragraphs[0]
p.alignment = align
run = p.add_run()
run.text = text
run.font.name = font_name
run.font.size = Pt(font_size)
run.font.bold = bold
run.font.italic= italic
run.font.color.rgb = color
return tb, tf
def add_para(tf, text, font_size=16, bold=False, italic=False,
color=DARK_GREY, align=PP_ALIGN.LEFT, level=0, indent_pt=0):
p = tf.add_paragraph()
p.alignment = align
p.level = level
if indent_pt:
from pptx.oxml.ns import qn
from lxml import etree
pPr = p._pPr
if pPr is None:
pPr = p._p.get_or_add_pPr()
pPr.set('indent', str(int(-indent_pt * 12700)))
pPr.set('marL', str(int( indent_pt * 12700)))
run = p.add_run()
run.text = text
run.font.size = Pt(font_size)
run.font.bold = bold
run.font.color.rgb = color
return p
def colored_rect_with_text(slide, rect_color, text, left, top, width, height,
font_size=14, bold=True, text_color=WHITE,
align=PP_ALIGN.CENTER):
shp = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Emu(left), Emu(top), Emu(width), Emu(height))
shp.fill.solid()
shp.fill.fore_color.rgb = rect_color
shp.line.fill.background()
shp.shadow.inherit = False
tf = shp.text_frame
tf.word_wrap = True
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(4)
p = tf.paragraphs[0]
p.alignment = align
run = p.add_run()
run.text = text
run.font.name = "Calibri"
run.font.size = Pt(font_size)
run.font.bold = bold
run.font.color.rgb = text_color
return shp
def divider_line(slide, y_emu, color=TEAL, width_pct=0.9):
from pptx.enum.shapes import MSO_CONNECTOR
x0 = int(W * (1 - width_pct) / 2)
x1 = int(W * (1 - (1 - width_pct) / 2))
ln = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT,
Emu(x0), Emu(y_emu), Emu(x1), Emu(y_emu))
ln.line.color.rgb = color
ln.line.width = Pt(1.5)
def slide_header(slide, title, subtitle=None, title_size=32):
"""Navy header bar at top with white title text."""
bg_rect(slide, LTBLUE)
# header bar
bg_rect(slide, NAVY, left=0, top=0, width=int(W), height=Inches(1.15))
# gold accent strip
bg_rect(slide, GOLD, left=0, top=int(Inches(1.15)), width=int(W), height=int(Inches(0.06)))
# title
textbox(slide, title,
left=Inches(0.5), top=Inches(0.12),
width=Inches(12.3), height=Inches(0.95),
font_size=title_size, bold=True, color=WHITE,
align=PP_ALIGN.LEFT, v_anchor=MSO_ANCHOR.MIDDLE,
margins=(4, 4, 2, 2))
if subtitle:
textbox(slide, subtitle,
left=Inches(0.5), top=Inches(1.2),
width=Inches(12.3), height=Inches(0.4),
font_size=16, bold=False, italic=True, color=MID_GREY,
align=PP_ALIGN.LEFT)
def footer(slide, text="Campbell's Operative Orthopaedics 15th Ed 2026 | Vigdorchik et al."):
bg_rect(slide, NAVY, left=0, top=int(H - Inches(0.35)), width=int(W), height=int(Inches(0.35)))
textbox(slide, text,
left=Inches(0.4), top=int(H - Inches(0.34)),
width=Inches(12.5), height=Inches(0.32),
font_size=9, color=WHITE, align=PP_ALIGN.LEFT,
v_anchor=MSO_ANCHOR.MIDDLE, margins=(2,2,0,0))
# ── SLIDE BUILDERS ───────────────────────────────────────────────
def slide_01_title(prs):
"""Title slide"""
slide = blank_slide(prs)
# full navy bg
bg_rect(slide, NAVY)
# gold bottom strip
bg_rect(slide, GOLD, left=0, top=int(H - Inches(0.6)), width=int(W), height=int(Inches(0.6)))
# teal accent left bar
bg_rect(slide, TEAL, left=0, top=0, width=int(Inches(0.55)), height=int(H))
# big title
textbox(slide, "Hip-Spine Relationship in\nTotal Hip Replacement",
left=Inches(0.9), top=Inches(1.8),
width=Inches(11), height=Inches(2.4),
font_size=44, bold=True, color=WHITE,
align=PP_ALIGN.LEFT, v_anchor=MSO_ANCHOR.MIDDLE)
# subtitle
textbox(slide, "Classification, Biomechanics & Surgical Recommendations",
left=Inches(0.9), top=Inches(4.1),
width=Inches(11), height=Inches(0.55),
font_size=22, bold=False, italic=True, color=GOLD,
align=PP_ALIGN.LEFT)
# source
textbox(slide, "Based on Campbell's Operative Orthopaedics 15th Ed 2026\nVigdorchik JM et al. | Dennis DA et al. 2024",
left=Inches(0.9), top=Inches(4.75),
width=Inches(11), height=Inches(0.7),
font_size=14, color=LTBLUE, align=PP_ALIGN.LEFT)
def slide_02_overview(prs):
"""Why it matters"""
slide = blank_slide(prs)
slide_header(slide, "Why the Hip-Spine Relationship Matters")
footer(slide)
# 3 big stat boxes
stats = [
("2x", "increased risk of\nearly hip dislocation\nwith prior spinal fusion", RED),
(">3x", "increased risk of\nrevision THR in\nspine-fused patients", ORANGE),
("Lewinnek\nZone Fails", "Most early dislocations occur\nwith components placed IN\nthe traditional safe zone", TEAL),
]
bx_w = Inches(3.7)
bx_h = Inches(2.4)
tops = int(Inches(1.8))
lefts = [Inches(0.5), Inches(4.8), Inches(9.1)]
for (num, desc, col), lft in zip(stats, lefts):
shp = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Emu(lft), Emu(tops), Emu(bx_w), Emu(bx_h))
shp.fill.solid()
shp.fill.fore_color.rgb = col
shp.line.fill.background()
shp.shadow.inherit = False
tf = shp.text_frame
tf.word_wrap = True
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_left = tf.margin_right = Pt(8)
tf.margin_top = tf.margin_bottom = Pt(6)
p0 = tf.paragraphs[0]
p0.alignment = PP_ALIGN.CENTER
r0 = p0.add_run()
r0.text = num
r0.font.size = Pt(36)
r0.font.bold = True
r0.font.color.rgb = WHITE
p1 = tf.add_paragraph()
p1.alignment = PP_ALIGN.CENTER
r1 = p1.add_run()
r1.text = desc
r1.font.size = Pt(14)
r1.font.bold = False
r1.font.color.rgb = WHITE
# Concept box
shp2 = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.5), Inches(4.45), Inches(12.3), Inches(2.5))
shp2.fill.solid()
shp2.fill.fore_color.rgb = RGBColor(0xE8, 0xF4, 0xF8)
shp2.line.color.rgb = TEAL
shp2.line.width = Pt(1.5)
shp2.shadow.inherit = False
tf2 = shp2.text_frame
tf2.word_wrap = True
tf2.vertical_anchor = MSO_ANCHOR.TOP
tf2.margin_left = tf2.margin_right = Pt(12)
tf2.margin_top = tf2.margin_bottom = Pt(8)
p = tf2.paragraphs[0]
p.alignment = PP_ALIGN.LEFT
r = p.add_run()
r.text = "Core Concept:"
r.font.size = Pt(16)
r.font.bold = True
r.font.color.rgb = NAVY
add_para(tf2, "The spine, pelvis, and hip form a kinematic chain. "
"Normal sitting requires posterior pelvic tilt (11-30 deg sacral slope change), "
"which increases functional cup anteversion by ~15.6 deg. "
"Spinal stiffness or deformity disrupts this compensation, "
"changing the FUNCTIONAL orientation of the acetabular component regardless of its anatomical position.",
font_size=15, color=DARK_GREY)
def slide_03_parameters(prs):
"""Key Spinopelvic Parameters"""
slide = blank_slide(prs)
slide_header(slide, "Key Spinopelvic Parameters")
footer(slide)
params = [
("Pelvic Incidence (PI)", "Angle: perpendicular to S1 endplate midpoint -> femoral head center.\nFIXED anatomical constant. ~50-55 deg.\nPI = PT + SS (fundamental equation)", NAVY),
("Sacral Slope (SS)", "Angle between S1 superior endplate and horizontal.\nNormal: ~40 deg standing.\nFalls 11-30 deg on sitting (normal).\nStiffness: change <=10 deg", TEAL),
("Pelvic Tilt (PT)", "Angle: vertical line vs line from femoral head to S1 midpoint.\nNormal: ~12-15 deg.\nIncreases with posterior pelvic rotation (sitting)", G2A_COL),
("PI-LL Mismatch", "Pelvic Incidence minus Lumbar Lordosis.\nNormal: <10 deg.\n>10 deg = FLATBACK DEFORMITY.\nCauses chronic posterior pelvic tilt", G2B_COL),
("APP vs FPP", "APP = Anatomic Pelvic Plane (ASIS + pubic symphysis).\nFPP = Functional Pelvic Plane (actual standing orientation).\nIn flatback: FPP =/= APP -- cup appears more anteverted than measured!", G1B_COL),
]
row_h = Inches(0.94)
top_start = Inches(1.35)
for i, (title, desc, col) in enumerate(params):
y = int(top_start) + i * int(row_h)
# colour tab
shp = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Inches(0.35), Emu(y + int(Inches(0.05))),
Inches(0.18), Emu(int(row_h) - int(Inches(0.1))))
shp.fill.solid()
shp.fill.fore_color.rgb = col
shp.line.fill.background()
shp.shadow.inherit = False
# title
textbox(slide, title,
left=Inches(0.65), top=Emu(y + int(Inches(0.05))),
width=Inches(3.1), height=Emu(int(row_h) - int(Inches(0.1))),
font_size=14, bold=True, color=col,
align=PP_ALIGN.LEFT, v_anchor=MSO_ANCHOR.MIDDLE)
# description
textbox(slide, desc,
left=Inches(3.85), top=Emu(y + int(Inches(0.05))),
width=Inches(9.1), height=Emu(int(row_h) - int(Inches(0.1))),
font_size=12, bold=False, color=DARK_GREY,
align=PP_ALIGN.LEFT, v_anchor=MSO_ANCHOR.MIDDLE, wrap=True)
# thin divider
if i < len(params) - 1:
divider_line(slide, y + int(row_h), color=RGBColor(0xCC,0xCC,0xCC), width_pct=0.95)
def slide_04_classification(prs):
"""Hip-Spine Classification Table"""
slide = blank_slide(prs)
slide_header(slide, "Hip-Spine Classification System (Vigdorchik et al.)")
footer(slide)
groups = [
{
"group": "1A",
"label": "Normal Alignment\n+ Normal Mobility",
"patho": "Normal anatomy. APP = FPP.\nNormal spinopelvic kinematics.\nSS change >10 deg from stand-to-sit.",
"target": "Anteversion: 20-25 deg\n(relative to APP)",
"risk": "Low dislocation risk",
"col": G1A_COL,
},
{
"group": "1B",
"label": "Normal Alignment\n+ STIFF Spine",
"patho": "APP = FPP BUT spine cannot tilt posteriorly.\nSS change <=10 deg on sit.\nHip must flex further to sit -- posterior impingement.",
"target": "Anteversion: 30 deg\n(relative to FPP/standing AP)\nHigher target -- protect vs posterior dislocation",
"risk": "Posterior dislocation risk in sitting",
"col": G1B_COL,
},
{
"group": "2A",
"label": "FLATBACK Deformity\n+ Normal Mobility",
"patho": "PI-LL >10 deg. Chronic posterior pelvic tilt.\nFPP =/= APP.\nFunctional cup anteversion > anatomical anteversion.",
"target": "Anteversion: 25-30 deg\n(relative to FPP, standing AP)\nAccount for retroverted FPP",
"risk": "Anterior dislocation risk in standing/extension",
"col": G2A_COL,
},
{
"group": "2B",
"label": "FLATBACK Deformity\n+ STIFF Spine",
"patho": "PI-LL >10 deg AND SS change <=10 deg.\nWorst combination: retroverted pelvis + no sit accommodation.\nMost complex planning required.",
"target": "Anteversion: 30 deg\n(relative to FPP)\nHighest risk -- dual mobility cup strongly recommended",
"risk": "HIGH risk -- both anterior & posterior dislocation",
"col": G2B_COL,
},
]
# column headers
headers = ["Group", "Classification", "Pathology", "Cup Anteversion Target", "Risk"]
col_widths = [Inches(0.75), Inches(2.0), Inches(3.8), Inches(3.2), Inches(3.0)]
col_x = [Inches(0.35)]
for cw in col_widths[:-1]:
col_x.append(col_x[-1] + cw)
hdr_y = Inches(1.32)
hdr_h = Inches(0.42)
# header bg
bg_rect(slide, NAVY, left=int(Inches(0.35)), top=int(hdr_y),
width=int(sum(col_widths)), height=int(hdr_h))
for hdr, cx, cw in zip(headers, col_x, col_widths):
textbox(slide, hdr, left=cx+int(Inches(0.05)), top=int(hdr_y),
width=cw-int(Inches(0.1)), height=int(hdr_h),
font_size=13, bold=True, color=WHITE, align=PP_ALIGN.CENTER,
v_anchor=MSO_ANCHOR.MIDDLE)
row_h = Inches(1.33)
row_start = Inches(1.74)
for i, g in enumerate(groups):
ry = int(row_start) + i * int(row_h)
row_bg = RGBColor(0xF5, 0xF5, 0xF5) if i % 2 == 0 else WHITE
bg_rect(slide, row_bg, left=int(Inches(0.35)), top=ry,
width=int(sum(col_widths)), height=int(row_h))
# group badge
shp = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
col_x[0] + int(Inches(0.05)), ry + int(Inches(0.25)),
int(col_widths[0]) - int(Inches(0.1)), int(Inches(0.82)))
shp.fill.solid()
shp.fill.fore_color.rgb = g["col"]
shp.line.fill.background()
shp.shadow.inherit = False
tf = shp.text_frame
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(2)
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
r = p.add_run()
r.text = g["group"]
r.font.size = Pt(22)
r.font.bold = True
r.font.color.rgb = WHITE
data = [g["label"], g["patho"], g["target"], g["risk"]]
cols_idx = [1, 2, 3, 4]
font_sizes = [13, 12, 12, 12]
bolds = [True, False, True, False]
for di, ci, fs, bd in zip(data, cols_idx, font_sizes, bolds):
textbox(slide, di,
left=col_x[ci]+int(Inches(0.05)), top=ry+int(Inches(0.06)),
width=col_widths[ci]-int(Inches(0.1)), height=int(row_h)-int(Inches(0.12)),
font_size=fs, bold=bd, color=DARK_GREY if ci != 4 else g["col"],
align=PP_ALIGN.LEFT, v_anchor=MSO_ANCHOR.MIDDLE, wrap=True)
# bottom border
if i < 3:
divider_line(slide, ry + int(row_h), color=RGBColor(0xCC,0xCC,0xCC), width_pct=0.94)
def slide_05_biomechanics(prs):
"""Biomechanics: standing vs sitting"""
slide = blank_slide(prs)
slide_header(slide, "Biomechanics: Stand-to-Sit Transition & Pelvic Compensation")
footer(slide)
# Left panel - Normal mobility
bg_rect(slide, RGBColor(0xE8, 0xF8, 0xEE), left=int(Inches(0.35)), top=int(Inches(1.35)),
width=int(Inches(5.9)), height=int(Inches(5.7)))
shp = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
int(Inches(0.35)), int(Inches(1.35)),
int(Inches(5.9)), int(Inches(0.38)))
shp.fill.solid()
shp.fill.fore_color.rgb = G1A_COL
shp.line.fill.background()
shp.shadow.inherit = False
textbox(slide, "NORMAL MOBILITY (SS change >10 deg)",
left=Inches(0.45), top=Inches(1.35), width=Inches(5.7), height=Inches(0.38),
font_size=14, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
normals = [
("Standing:", "Pelvis in neutral, SS ~40 deg, normal lumbar lordosis"),
("Sitting:", "Posterior pelvic tilt occurs, LL decreases, SS falls 11-30 deg"),
("Effect on cup:", "+15.6 deg functional anteversion on sitting"),
("Protection:", "Prevents anterior impingement in hip flexion"),
("Clinical result:", "Standard cup anteversion (20-25 deg) is adequate"),
]
tb_n, tf_n = textbox(slide, "",
left=Inches(0.5), top=Inches(1.82),
width=Inches(5.6), height=Inches(4.9),
font_size=14, v_anchor=MSO_ANCHOR.TOP)
tf_n.paragraphs[0].runs # clear
first = True
for label, val in normals:
if first:
p = tf_n.paragraphs[0]
first = False
else:
p = tf_n.add_paragraph()
p.alignment = PP_ALIGN.LEFT
r1 = p.add_run()
r1.text = label + " "
r1.font.size = Pt(13)
r1.font.bold = True
r1.font.color.rgb = G1A_COL
r2 = p.add_run()
r2.text = val
r2.font.size = Pt(13)
r2.font.bold = False
r2.font.color.rgb = DARK_GREY
# blank spacer
sp = tf_n.add_paragraph()
sp.add_run().text = ""
sp.runs[0].font.size = Pt(5)
# Right panel - Stiff spine
bg_rect(slide, RGBColor(0xFD, 0xF0, 0xE8), left=int(Inches(6.6)), top=int(Inches(1.35)),
width=int(Inches(6.4)), height=int(Inches(5.7)))
shp2 = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
int(Inches(6.6)), int(Inches(1.35)),
int(Inches(6.4)), int(Inches(0.38)))
shp2.fill.solid()
shp2.fill.fore_color.rgb = G1B_COL
shp2.line.fill.background()
shp2.shadow.inherit = False
textbox(slide, "SPINOPELVIC STIFFNESS (SS change <=10 deg)",
left=Inches(6.7), top=Inches(1.35), width=Inches(6.2), height=Inches(0.38),
font_size=14, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
stiffs = [
("Cause:", "Lumbar fusion, severe degenerative spondylosis"),
("Standing:", "Pelvis may be neutral"),
("Sitting:", "Pelvis CANNOT tilt posteriorly -- SS unchanged"),
("Effect on cup:", "No functional anteversion gain -- hip must flex more"),
("Risk:", "Posterior impingement -> posterior dislocation when sitting/rising"),
("Management:", "Increase cup anteversion target to 30 deg (FPP reference)"),
]
tb_s, tf_s = textbox(slide, "",
left=Inches(6.7), top=Inches(1.82),
width=Inches(6.1), height=Inches(4.9),
font_size=14, v_anchor=MSO_ANCHOR.TOP)
first = True
for label, val in stiffs:
if first:
p = tf_s.paragraphs[0]
first = False
else:
p = tf_s.add_paragraph()
p.alignment = PP_ALIGN.LEFT
r1 = p.add_run()
r1.text = label + " "
r1.font.size = Pt(13)
r1.font.bold = True
r1.font.color.rgb = G1B_COL
r2 = p.add_run()
r2.text = val
r2.font.size = Pt(13)
r2.font.bold = False
r2.font.color.rgb = DARK_GREY
sp = tf_s.add_paragraph()
sp.add_run().text = ""
sp.runs[0].font.size = Pt(5)
def slide_06_flatback(prs):
"""Flatback deformity"""
slide = blank_slide(prs)
slide_header(slide, "Flatback Deformity & Its Impact on THR")
footer(slide)
# PI equation box
shp = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.35), Inches(1.38),
Inches(12.6), Inches(0.72))
shp.fill.solid()
shp.fill.fore_color.rgb = NAVY
shp.line.fill.background()
shp.shadow.inherit = False
tf = shp.text_frame
tf.word_wrap = True
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_left = tf.margin_right = Pt(12)
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
r = p.add_run()
r.text = "PI = PT + SS (fixed equation) | Flatback = PI - LL >10 deg | Chronic posterior pelvic tilt = elevated PT -> apparent FPP anteversion"
r.font.size = Pt(15)
r.font.bold = True
r.font.color.rgb = GOLD
# Two scenario columns
scenarios = [
{
"title": "Flatback with NORMAL mobility (Group 2A)",
"col": G2A_COL,
"bg": RGBColor(0xE8, 0xF0, 0xFA),
"items": [
"Chronic posterior pelvic tilt due to PI-LL mismatch",
"FPP is retroverted relative to APP",
"Cup positioned at 15 deg to APP actually has >25 deg FPP anteversion",
"Risk: ANTERIOR dislocation in standing / extension",
"Target 25-30 deg anteversion relative to FPP (standing AP pelvis)",
"Must measure SS on standing lateral to determine FPP",
]
},
{
"title": "Flatback + STIFF spine (Group 2B) -- Highest Risk",
"col": G2B_COL,
"bg": RGBColor(0xFBE9E7),
"items": [
"Both: retroverted pelvis (FPP =/= APP) AND no sit accommodation",
"Standing: excessive functional anteversion -> anterior dislocation",
"Sitting: no posterior tilt -> posterior impingement -> posterior dislocation",
"Target 30 deg relative to FPP",
"Dual mobility cup strongly recommended",
"Consider combined spine + hip surgical planning",
]
},
]
for i, sc in enumerate(scenarios):
lft = Inches(0.35) + i * Inches(6.5)
# BG
bg_rect(slide, sc["bg"], left=int(lft), top=int(Inches(2.22)),
width=int(Inches(6.2)), height=int(Inches(4.75)))
# Header bar
hshp = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
int(lft), int(Inches(2.22)),
int(Inches(6.2)), int(Inches(0.44)))
hshp.fill.solid()
hshp.fill.fore_color.rgb = sc["col"]
hshp.line.fill.background()
hshp.shadow.inherit = False
textbox(slide, sc["title"],
left=lft + int(Inches(0.1)), top=Inches(2.22),
width=Inches(6.0), height=Inches(0.44),
font_size=13, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
# Items
tb, tf = textbox(slide, "",
left=lft + int(Inches(0.15)), top=Inches(2.75),
width=Inches(5.9), height=Inches(4.1),
font_size=13, v_anchor=MSO_ANCHOR.TOP, wrap=True)
first = True
for item in sc["items"]:
if first:
p = tf.paragraphs[0]
first = False
else:
p = tf.add_paragraph()
p.alignment = PP_ALIGN.LEFT
r = p.add_run()
r.text = " " + item
r.font.size = Pt(13)
r.font.color.rgb = DARK_GREY
sp = tf.add_paragraph()
sp.add_run().text = ""
sp.runs[0].font.size = Pt(4)
def slide_07_preop(prs):
"""Preoperative Evaluation"""
slide = blank_slide(prs)
slide_header(slide, "Preoperative Evaluation: Who Needs Spinopelvic Assessment?")
footer(slide)
# Flag section
flag_items = [
("History of lumbar fusion (any levels)", RED),
("Prior spinal deformity correction surgery", RED),
("Symptoms of flatback deformity (forward stooped posture)", ORANGE),
("Symptomatic lumbar stenosis or severe DDD", ORANGE),
("Previously failed THR with recurrent dislocation", RED),
("Significant sagittal imbalance on clinical exam", ORANGE),
]
textbox(slide, "Red Flags Requiring Full Spinopelvic Workup",
left=Inches(0.4), top=Inches(1.38),
width=Inches(5.8), height=Inches(0.38),
font_size=15, bold=True, color=NAVY)
for i, (item, col) in enumerate(flag_items):
y = Inches(1.82) + i * Inches(0.6)
dot = slide.shapes.add_shape(MSO_SHAPE.OVAL,
Inches(0.45), y + int(Inches(0.12)),
Inches(0.22), Inches(0.22))
dot.fill.solid()
dot.fill.fore_color.rgb = col
dot.line.fill.background()
dot.shadow.inherit = False
textbox(slide, item,
left=Inches(0.82), top=y,
width=Inches(5.3), height=Inches(0.5),
font_size=13, bold=False, color=DARK_GREY,
v_anchor=MSO_ANCHOR.MIDDLE)
# Divider
ln = slide.shapes.add_connector(
__import__('pptx.enum.shapes', fromlist=['MSO_CONNECTOR']).MSO_CONNECTOR.STRAIGHT,
Inches(6.5), Inches(1.35), Inches(6.5), Inches(7.1))
ln.line.color.rgb = RGBColor(0xCC,0xCC,0xCC)
ln.line.width = Pt(1.0)
# Imaging protocol
textbox(slide, "Imaging Protocol",
left=Inches(6.7), top=Inches(1.38),
width=Inches(6.1), height=Inches(0.38),
font_size=15, bold=True, color=NAVY)
imaging = [
("MINIMUM (all spine-risk patients)", [
"Lateral lumbar + pelvis -- STANDING",
"Lateral lumbar + pelvis -- SEATED (forward-flexed)",
"Standing AP pelvis",
], TEAL),
("MEASURE", [
"Sacral slope (SS) in standing AND sitting",
"Calculate SS change: >10 deg = normal; <=10 deg = stiff",
"PI-LL mismatch on standing lateral",
"APP vs FPP discrepancy",
], G2A_COL),
("ADVANCED (complex/revision)", [
"EOS imaging -- low-dose 3D spinopelvic parameters",
"CT scan -- assess femoral version, cup position",
"Robotic planning software with functional zone targeting",
], NAVY),
]
y = Inches(1.88)
for label, items, col in imaging:
textbox(slide, label,
left=Inches(6.7), top=y,
width=Inches(6.1), height=Inches(0.35),
font_size=13, bold=True, color=col)
y += Inches(0.35)
for it in items:
dot = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Inches(6.82), y + int(Inches(0.12)),
Inches(0.14), Inches(0.14))
dot.fill.solid()
dot.fill.fore_color.rgb = col
dot.line.fill.background()
dot.shadow.inherit = False
textbox(slide, it,
left=Inches(7.1), top=y,
width=Inches(5.8), height=Inches(0.38),
font_size=12, color=DARK_GREY, v_anchor=MSO_ANCHOR.MIDDLE)
y += Inches(0.38)
y += Inches(0.12)
def slide_08_surgical(prs):
"""Surgical Recommendations"""
slide = blank_slide(prs)
slide_header(slide, "Surgical Recommendations by Group")
footer(slide)
# Summary anteversion targets
targets = [
("1A", "20-25 deg\nto APP", G1A_COL),
("1B", "30 deg\nto FPP", G1B_COL),
("2A", "25-30 deg\nto FPP", G2A_COL),
("2B", "30 deg\nto FPP", G2B_COL),
]
for i, (grp, tgt, col) in enumerate(targets):
lft = Inches(0.35) + i * Inches(3.25)
shp = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
int(lft), int(Inches(1.38)),
int(Inches(3.05)), int(Inches(1.1)))
shp.fill.solid()
shp.fill.fore_color.rgb = col
shp.line.fill.background()
shp.shadow.inherit = False
tf = shp.text_frame
tf.word_wrap = True
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_left = tf.margin_right = Pt(6)
tf.margin_top = tf.margin_bottom = Pt(4)
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
r = p.add_run()
r.text = "Group " + grp
r.font.size = Pt(18)
r.font.bold = True
r.font.color.rgb = WHITE
p2 = tf.add_paragraph()
p2.alignment = PP_ALIGN.CENTER
r2 = p2.add_run()
r2.text = tgt
r2.font.size = Pt(14)
r2.font.bold = False
r2.font.color.rgb = WHITE
# General surgical principles
principles = [
("Cup Positioning", TEAL,
"Target FUNCTIONAL safe zone, not anatomical Lewinnek zone.\n"
"Use FPP as reference in groups 1B, 2A, 2B.\n"
"In Group 1A, APP is adequate (APP = FPP)."),
("Implant Choice", G2B_COL,
"Dual mobility cup: strongly recommended for all spine-fused patients.\n"
"Constrained liner: consider for severe spinopelvic imbalance.\n"
"Large femoral head (36 mm+): improves jump distance and stability."),
("Technology Assist", G2A_COL,
"Robotic-arm assisted THR: improves accuracy of functional zone targeting.\n"
"CT-based navigation: accounts for patient-specific pelvic orientation.\n"
"EOS imaging: pre-op 3D planning with low radiation dose."),
("Combined Planning", NAVY,
"Patients with >2-level lumbar fusion: coordinate with spine surgeon.\n"
"Plan hip AFTER spine if simultaneous correction needed.\n"
"Document cup orientation with post-op CT if high risk."),
]
for i, (title, col, desc) in enumerate(principles):
row = i // 2
col_idx = i % 2
lft = Inches(0.35) + col_idx * Inches(6.5)
top = Inches(2.62) + row * Inches(2.25)
bg_rect(slide, RGBColor(0xF5, 0xF5, 0xF5),
left=int(lft), top=int(top),
width=int(Inches(6.2)), height=int(Inches(2.1)))
hshp = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
int(lft), int(top),
int(Inches(6.2)), int(Inches(0.4)))
hshp.fill.solid()
hshp.fill.fore_color.rgb = col
hshp.line.fill.background()
hshp.shadow.inherit = False
textbox(slide, title,
left=lft + int(Inches(0.1)), top=top,
width=Inches(6.0), height=Inches(0.4),
font_size=14, bold=True, color=WHITE, v_anchor=MSO_ANCHOR.MIDDLE)
textbox(slide, desc,
left=lft + int(Inches(0.15)), top=top + int(Inches(0.44)),
width=Inches(5.9), height=Inches(1.6),
font_size=12, color=DARK_GREY, v_anchor=MSO_ANCHOR.TOP, wrap=True)
def slide_09_clinical(prs):
"""Clinical Scenarios"""
slide = blank_slide(prs)
slide_header(slide, "Clinical Scenarios: Applying the Classification")
footer(slide)
scenarios = [
{
"title": "Case 1 - Posterior Dislocation While Sitting",
"col": G1B_COL,
"patient": "65F, prior L3-S5 fusion, elective THR for OA",
"mechanism": "Standard cup anteversion placed (15-20 deg). Spine cannot tilt posteriorly on sitting. Hip must flex further. Posterior neck-cup impingement occurs.",
"outcome": "Posterior dislocation on first post-op sit-to-stand",
"classification": "Group 1B (normal alignment, stiff spine)",
"correction": "Should have targeted 30 deg anteversion to FPP. Dual mobility cup indicated.",
},
{
"title": "Case 2 - Anterior Dislocation in Standing",
"col": G2A_COL,
"patient": "70M, longstanding degenerative flatback (PI-LL 18 deg), elective THR",
"mechanism": "Cup placed at 20 deg to APP. Due to chronic posterior pelvic tilt, FPP is retroverted -- functional anteversion is actually 35 deg. In extension/standing, anterior dislocation.",
"outcome": "Anterior dislocation at 6 weeks post-op during standing from a low chair",
"classification": "Group 2A (flatback, normal mobility)",
"correction": "Should have referenced FPP on standing lateral. Target 25-30 deg to FPP.",
},
{
"title": "Case 3 - Recurrent Dislocation (Both Directions)",
"col": G2B_COL,
"patient": "72F, sagittal imbalance, L2-pelvis fusion, progressive OA THR",
"mechanism": "Flatback + stiff spine. Retroverted pelvis at baseline + no standing-to-sitting accommodation. Cup is too anteverted when standing AND impinges posteriorly when sitting.",
"outcome": "Two dislocations: one anterior (week 2), one posterior (week 8)",
"classification": "Group 2B (flatback + stiff spine) -- HIGHEST RISK",
"correction": "Pre-op spinopelvic workup, robotic targeting to FPP at 30 deg, dual mobility cup mandatory.",
},
]
for i, sc in enumerate(scenarios):
top = Inches(1.38) + i * Inches(2.0)
bg_rect(slide, RGBColor(0xF8, 0xF8, 0xF8),
left=int(Inches(0.35)), top=int(top),
width=int(Inches(12.6)), height=int(Inches(1.88)))
# left bar
bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
int(Inches(0.35)), int(top),
int(Inches(0.18)), int(Inches(1.88)))
bar.fill.solid()
bar.fill.fore_color.rgb = sc["col"]
bar.line.fill.background()
bar.shadow.inherit = False
# title
textbox(slide, sc["title"],
left=Inches(0.65), top=top,
width=Inches(12.1), height=Inches(0.38),
font_size=14, bold=True, color=sc["col"])
# 5 mini-fields in columns
fields = [
("Patient:", sc["patient"]),
("Mechanism:", sc["mechanism"]),
("Classification:", sc["classification"]),
("Correction:", sc["correction"]),
]
field_top = top + int(Inches(0.38))
col_w_l = Inches(0.9)
col_w_v = Inches(2.25)
n_cols = 4
for j, (lbl, val) in enumerate(fields):
fx = Inches(0.62) + j * Inches(3.1)
textbox(slide, lbl,
left=fx, top=field_top,
width=col_w_l, height=Inches(0.28),
font_size=10, bold=True, color=sc["col"])
textbox(slide, val,
left=fx, top=field_top + int(Inches(0.28)),
width=col_w_v + Inches(0.7), height=Inches(1.15),
font_size=10, color=DARK_GREY, wrap=True, v_anchor=MSO_ANCHOR.TOP)
if i < 2:
divider_line(slide, int(top) + int(Inches(1.88)),
color=RGBColor(0xCC,0xCC,0xCC), width_pct=0.95)
def slide_10_summary(prs):
"""Summary & Key Takeaways"""
slide = blank_slide(prs)
slide_header(slide, "Key Takeaways")
footer(slide)
takeaways = [
(TEAL, "Kinematic Chain",
"The spine, pelvis, and hip are one functional unit. Spinal pathology directly alters "
"the FUNCTIONAL orientation of the acetabular cup during activities of daily living."),
(G2B_COL, "Lewinnek Zone is Insufficient",
"The traditional 15+/-10 deg anteversion / 40+/-10 deg inclination safe zone was defined "
"in static, anatomical terms. Most dislocations in hip-spine patients occur within this zone."),
(G2A_COL, "Measure SS Change",
"A standing AND sitting lateral radiograph to calculate sacral slope change is the minimum "
"workup. SS change <=10 deg defines spinopelvic stiffness requiring anteversion correction."),
(G1B_COL, "Use FPP as Reference",
"In groups 1B, 2A, and 2B, always target cup anteversion relative to the Functional Pelvic "
"Plane (FPP), not the APP. Reference the standing AP pelvis in these patients."),
(NAVY, "Dual Mobility + Robotic",
"Dual mobility cups are the implant of choice for prior spinal fusion patients. "
"Robotic-assisted THR significantly improves accuracy of functional zone targeting."),
]
for i, (col, title, desc) in enumerate(takeaways):
top = Inches(1.38) + i * Inches(1.15)
# number badge
shp = slide.shapes.add_shape(MSO_SHAPE.OVAL,
int(Inches(0.35)), int(top) + int(Inches(0.15)),
int(Inches(0.55)), int(Inches(0.55)))
shp.fill.solid()
shp.fill.fore_color.rgb = col
shp.line.fill.background()
shp.shadow.inherit = False
tf = shp.text_frame
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
r = p.add_run()
r.text = str(i + 1)
r.font.size = Pt(16)
r.font.bold = True
r.font.color.rgb = WHITE
# title
textbox(slide, title,
left=Inches(1.05), top=top,
width=Inches(2.5), height=Inches(0.9),
font_size=15, bold=True, color=col, v_anchor=MSO_ANCHOR.MIDDLE)
# desc
textbox(slide, desc,
left=Inches(3.7), top=top,
width=Inches(9.25), height=Inches(0.9),
font_size=13, color=DARK_GREY, v_anchor=MSO_ANCHOR.MIDDLE, wrap=True)
if i < 4:
divider_line(slide, int(top) + int(Inches(1.05)),
color=RGBColor(0xDD,0xDD,0xDD), width_pct=0.94)
# ── MAIN ─────────────────────────────────────────────────────────
def main():
prs = new_prs()
slide_01_title(prs)
slide_02_overview(prs)
slide_03_parameters(prs)
slide_04_classification(prs)
slide_05_biomechanics(prs)
slide_06_flatback(prs)
slide_07_preop(prs)
slide_08_surgical(prs)
slide_09_clinical(prs)
slide_10_summary(prs)
out_path = "/tmp/workspace/hip-spine-pptx/HipSpine_THR_Classification.pptx"
buf = BytesIO()
prs.save(buf)
with open(out_path, "wb") as f:
f.write(buf.getvalue())
print(f"Saved: {out_path}")
import os
size_kb = os.path.getsize(out_path) // 1024
print(f"File size: {size_kb} KB | Slides: {len(prs.slides)}")
if __name__ == "__main__":
main()
cd /tmp/workspace/hip-spine-pptx && python build_pptx.py 2>&1
~/hip-spine-pptx/build_pptx.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/hip-spine-pptx/build_pptx.py'
cd /tmp/workspace/hip-spine-pptx && python build_pptx.py 2>&1
HipSpine THR Classification
Presentation · PPTX
| Slide | Content |
|---|---|
| 1 | Title slide |
| 2 | Why the hip-spine relationship matters (2x dislocation risk, Lewinnek zone failure) |
| 3 | Key spinopelvic parameters (PI, SS, PT, PI-LL mismatch, APP vs FPP) |
| 4 | Hip-Spine Classification Table (Groups 1A / 1B / 2A / 2B with pathology + anteversion targets) |
| 5 | Biomechanics - normal mobility vs spinopelvic stiffness (stand-to-sit) |
| 6 | Flatback deformity and its impact on THR (Groups 2A vs 2B) |
| 7 | Preoperative evaluation - red flags + imaging protocol |
| 8 | Surgical recommendations by group (anteversion targets, implant choice, robotic planning) |
| 9 | Three clinical scenarios (real-world application of the classification) |
| 10 | Key takeaways summary |
We increase anterversion in 2A ?
| Group | Pelvic Posture | Risk | Anteversion target | Direction of change |
|---|---|---|---|---|
| 1A | Normal | Neither | 20-25° to APP | Baseline |
| 1B | Normal but stiff | Posterior dislocation (sitting) | 30° to FPP | Increase vs 1A |
| 2A | Retroverted (flatback) | Anterior dislocation (standing) | 25-30° to FPP | Appears similar numerically but means LESS anatomical anteversion because FPP is already retroverted |
| 2B | Retroverted + stiff | Both directions | 30° to FPP | Complex — reference FPP to avoid over-anteversion |