Brooo.....nasal myiasis for 10 marks question for ent postgraduate level.....use articles and standard textbooks...with subheadings
nasal myiasis
nasal myiasis ENT postgraduate review clinical features treatment 2023 2024
PMID: 24385004
PMID: 33883762
PMID: 35232917
PMID: 30320018
| Category | Specific Factors |
|---|---|
| Local nasal pathology | Atrophic rhinitis (ozaena), nasal polyps, septal perforation, chronic sinusitis, nasal malignancies |
| Systemic disease | Diabetes mellitus, leprosy (facial anesthesia), alcoholism, psychiatric illness |
| Neurological | Coma, altered consciousness, cerebrovascular accidents, epilepsy |
| Poor hygiene | Homeless individuals, low socioeconomic status, rural settings |
| Iatrogenic | Nasogastric tube placement, tracheostomy, ICU patients |
| Facial wounds | Post-operative wounds, gangrenous or necrotic tissue |
| Symptom | Description |
|---|---|
| Nasal discharge | Seropurulent, foul-smelling, blood-tinged; pathognomonic when larvae are visible |
| Nasal obstruction | Bilateral or unilateral; may be severe |
| Epistaxis | Due to mucosal erosion by feeding larvae |
| Facial pain / headache | Particularly with sinus involvement |
| Sensation of movement | Characteristic crawling/tickling sensation inside the nose |
| Sneezing | Reflex response to larval activity |
| Nasal fetor | Strong, putrid odor |
| Periorbital swelling | With orbital extension via ethmoid sinuses |
| Complication | Mechanism |
|---|---|
| Septal perforation | Larval enzymatic destruction of cartilage/bone |
| Destruction of turbinates | Tissue necrosis |
| Sinusitis (acute/chronic) | Extension into paranasal sinuses |
| Orbital cellulitis / abscess | Ethmoid extension |
| Blindness | Optic nerve involvement |
| Meningitis / encephalitis | Cribriform plate penetration |
| Cavernous sinus thrombosis | Septic thrombophlebitis |
| Secondary septicemia | Bacterial superinfection |
| Deafness | Middle ear involvement via Eustachian tube (rare) |
| Death | Intracranial extension, sepsis |
| Feature | Key Point |
|---|---|
| Definition | Fly larval infestation of nasal cavity/sinuses |
| Commonest cause (India/Asia) | Chrysomya bezziana (screwworm) |
| Commonest cause (nasal botfly) | Oestrus ovis (sheep botfly) |
| Most important predisposing condition | Atrophic rhinitis |
| Pathognomonic symptom | Crawling sensation + visible larvae |
| Investigation of choice | Nasal endoscopy |
| Drug of choice | Ivermectin 200 mcg/kg oral |
| Primary treatment | Endoscopic removal + nasal irrigation |
| Dreaded complication | Intracranial extension, meningitis |
Examiner's tip: For 10 marks, the must-hits are: definition + etiology (species names), predisposing factors (atrophic rhinitis), clinical features (crawling sensation + visible larvae), endoscopic diagnosis, treatment (endoscopic removal + ivermectin), complications (intracranial extension), and prevention. Staging by Sharma is a high-yield addition.
Make this into a pdf plz
ls -la /home/daytona/workspace/
mkdir -p /home/daytona/workspace/nasal-myiasis
~/nasal-myiasis/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
OUTPUT = "/home/daytona/workspace/nasal-myiasis/Nasal_Myiasis_ENT_PG.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm,
leftMargin=2*cm,
topMargin=2*cm,
bottomMargin=2*cm,
title="Nasal Myiasis - ENT Postgraduate",
author="Orris Medical AI"
)
W = A4[0] - 4*cm # usable width
styles = getSampleStyleSheet()
# ── Custom styles ──────────────────────────────────────────────────────────────
TEAL = colors.HexColor("#1B6E8A")
DARK = colors.HexColor("#1A1A2E")
LTBL = colors.HexColor("#E8F4F8")
MIDBL = colors.HexColor("#B8DDE8")
HEAD_BG = colors.HexColor("#1B6E8A")
ROW_ALT = colors.HexColor("#F0F8FC")
title_style = ParagraphStyle(
"MainTitle", parent=styles["Title"],
fontSize=22, textColor=colors.white,
spaceAfter=4, spaceBefore=0,
alignment=TA_CENTER, fontName="Helvetica-Bold"
)
subtitle_style = ParagraphStyle(
"Subtitle", parent=styles["Normal"],
fontSize=11, textColor=colors.HexColor("#CCE8F0"),
spaceAfter=2, alignment=TA_CENTER, fontName="Helvetica"
)
h1_style = ParagraphStyle(
"H1", parent=styles["Heading1"],
fontSize=13, textColor=colors.white,
spaceBefore=14, spaceAfter=4,
fontName="Helvetica-Bold", leading=16
)
h2_style = ParagraphStyle(
"H2", parent=styles["Heading2"],
fontSize=11, textColor=TEAL,
spaceBefore=10, spaceAfter=3,
fontName="Helvetica-Bold", leading=14,
borderPad=2
)
h3_style = ParagraphStyle(
"H3", parent=styles["Heading3"],
fontSize=10, textColor=DARK,
spaceBefore=6, spaceAfter=2,
fontName="Helvetica-BoldOblique", leading=13
)
body_style = ParagraphStyle(
"Body", parent=styles["Normal"],
fontSize=9.5, textColor=DARK,
spaceAfter=5, spaceBefore=1,
fontName="Helvetica", leading=14,
alignment=TA_JUSTIFY
)
bullet_style = ParagraphStyle(
"Bullet", parent=body_style,
leftIndent=16, bulletIndent=4,
spaceAfter=3, leading=13
)
sub_bullet_style = ParagraphStyle(
"SubBullet", parent=body_style,
leftIndent=30, bulletIndent=18,
spaceAfter=2, leading=13
)
cite_style = ParagraphStyle(
"Cite", parent=body_style,
fontSize=8.5, textColor=colors.HexColor("#444466"),
leftIndent=12, fontName="Helvetica-Oblique",
spaceAfter=4, leading=12
)
ref_style = ParagraphStyle(
"Ref", parent=body_style,
fontSize=8.5, leftIndent=18, firstLineIndent=-18,
spaceAfter=3, leading=12
)
tip_style = ParagraphStyle(
"Tip", parent=body_style,
fontSize=9, textColor=colors.HexColor("#7B3F00"),
leftIndent=10, rightIndent=10,
fontName="Helvetica-Oblique", leading=13
)
def section_header(text):
"""Teal banner for numbered sections."""
tbl = Table([[Paragraph(text, h1_style)]], colWidths=[W])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [4]),
]))
return tbl
def make_table(headers, rows, col_widths=None):
"""Generic styled table."""
if col_widths is None:
col_widths = [W / len(headers)] * len(headers)
header_row = [Paragraph(f"<b>{h}</b>", ParagraphStyle(
"TH", parent=body_style, textColor=colors.white, fontSize=9,
fontName="Helvetica-Bold", alignment=TA_CENTER
)) for h in headers]
data = [header_row]
for i, row in enumerate(rows):
data.append([Paragraph(str(c), ParagraphStyle(
"TD", parent=body_style, fontSize=9,
background=ROW_ALT if i % 2 == 0 else colors.white
)) for c in row])
tbl = Table(data, colWidths=col_widths, repeatRows=1)
ts = TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS",(0,1), (-1,-1), [ROW_ALT, colors.white]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#AAAACC")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
])
tbl.setStyle(ts)
return tbl
def b(text): return f"<b>{text}</b>"
def i(text): return f"<i>{text}</i>"
def bi(text): return f"<b><i>{text}</i></b>"
story = []
# ═══════════════════════════════════════════════════════════════════
# TITLE BANNER
# ═══════════════════════════════════════════════════════════════════
title_banner = Table(
[[Paragraph("NASAL MYIASIS", title_style)],
[Paragraph("ENT Postgraduate — 10 Marks Model Answer", subtitle_style)],
[Paragraph("Based on Harrison's 22e · Dermatology 5e · Medical Microbiology 9e · PubMed Literature", subtitle_style)]],
colWidths=[W]
)
title_banner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#0D3D52")),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 14),
("RIGHTPADDING", (0,0), (-1,-1), 14),
]))
story.append(title_banner)
story.append(Spacer(1, 10))
# ═══════════════════════════════════════════════════════════════════
# 1. DEFINITION
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("1. DEFINITION"))
story.append(Spacer(1, 4))
story.append(Paragraph(
"Nasal myiasis is the infestation of the nasal cavity and paranasal sinuses by dipterous fly larvae (maggots). "
"The term derives from the Greek word " + i("myia") + " (fly). It is a subset of body cavity myiasis and represents "
"one of the most clinically significant forms of myiasis in ENT practice due to the potential for rapid tissue "
"destruction, intracranial extension, and life-threatening complications.",
body_style
))
story.append(Paragraph(
"In South Asia it is known as " + b("\"Peenash\"") + " and was historically regarded in Hindu mythology as a form "
"of divine punishment (Bosmia et al., " + i("J Relig Health,") + " 2017 — PMID: 24385004).",
cite_style
))
story.append(Paragraph(
b("Harrison's 22e:") + " " + i('"Larvae that infest decaying tissues may enter body cavities such as the mouth, '
'nose, ears, sinuses, anus, vagina, and lower urinary tract, particularly in unconscious or otherwise debilitated '
'patients. The consequences range from harmless colonization to destruction of the nose, meningitis, and deafness."'),
cite_style
))
# ═══════════════════════════════════════════════════════════════════
# 2. ETIOLOGY
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("2. ETIOLOGY AND CAUSATIVE ORGANISMS"))
story.append(Spacer(1, 4))
story.append(Paragraph("Nasal myiasis is caused by flies of the order " + b("Diptera") + ". Three behavioral categories:", body_style))
story.append(Paragraph(b("A. Specific (Obligatory) Myiasis") + " — larvae require a living host for development:", h3_style))
for item in [
i("Dermatobia hominis") + " (Human botfly) — neotropical regions of Central/South America",
i("Oestrus ovis") + " (Sheep nasal botfly) — deposits larvae directly into the nostril; important cause of nasal/nasopharyngeal myiasis",
]:
story.append(Paragraph("• " + item, bullet_style))
story.append(Paragraph(b("B. Semi-specific (Facultative) Myiasis") + " — breed in decaying matter; infest wounds when available:", h3_style))
for item in [
i("Chrysomya bezziana") + " (Old World screwworm) — Africa, Asia, Australia; " + b("most common cause of nasal myiasis in India and Asia"),
i("Cochliomyia hominivorax") + " (New World screwworm) — Americas",
i("Wohlfahrtia magnifica") + " (Flesh fly) — Europe and Asia",
i("Lucilia sericata / L. cuprina") + " (Greenbottle/Blowflies) — worldwide",
i("Calliphora") + " spp. (Bluebottle flies)",
i("Megaselia spiracularis") + " (Phorid fly) — rare nosocomial cases documented (Shimizu et al., " + i("Intern Med,") + " 2026 — PMID: 40803864)",
]:
story.append(Paragraph("• " + item, bullet_style))
story.append(Paragraph(b("C. Accidental Myiasis") + " — no host requirement; e.g., " + i("Musca domestica") + " (housefly).", h3_style))
story.append(Paragraph(
b("Key families:") + " Calliphoridae (" + i("Lucilia, Chrysomyia, Cochliomyia") + ") and Sarcophagidae — "
+ i("Medical Microbiology 9e") + " (Murray et al., Elsevier).",
cite_style
))
# ═══════════════════════════════════════════════════════════════════
# 3. EPIDEMIOLOGY
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("3. EPIDEMIOLOGY"))
story.append(Spacer(1, 4))
for item in [
"Worldwide distribution; higher prevalence in " + b("tropics and subtropics") + " of Asia, Africa, and the Americas",
"Seasonal variation: year-round in tropics; restricted to summer months in temperate zones",
b("India:") + " " + i("Chrysomya bezziana") + " accounts for the majority of cases",
"Nasal myiasis was the " + b("most common subtype") + " of ORL myiasis — 33 of 67 cases in a large case series (Rana et al., " + i("Ghana Med J,") + " 2020 — PMID: 33883762)",
"Increasing incidence in Western countries due to international travel",
"Affects all age groups; higher in males (outdoor exposure)",
]:
story.append(Paragraph("• " + item, bullet_style))
# ═══════════════════════════════════════════════════════════════════
# 4. PREDISPOSING FACTORS
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("4. PREDISPOSING FACTORS / RISK FACTORS"))
story.append(Spacer(1, 4))
pred_headers = ["Category", "Specific Factors"]
pred_rows = [
["Local nasal pathology", "Atrophic rhinitis (ozaena), nasal polyps, septal perforation, chronic sinusitis, nasal malignancies"],
["Systemic disease", "Diabetes mellitus, leprosy (facial anesthesia), alcoholism, psychiatric illness"],
["Neurological", "Coma, altered consciousness, cerebrovascular accidents, epilepsy"],
["Poor hygiene / socioeconomic", "Homeless individuals, low socioeconomic status, rural settings"],
["Iatrogenic", "Nasogastric tube placement, tracheostomy, ICU patients"],
["Facial wounds", "Post-operative wounds, gangrenous or necrotic tissue"],
]
story.append(make_table(pred_headers, pred_rows, col_widths=[W*0.32, W*0.68]))
story.append(Spacer(1, 4))
story.append(Paragraph(
b("Key point:") + " Atrophic rhinitis is the " + b("single most important predisposing condition in India") +
" — the wide, odorous nasal cavity with crusted necrotic tissue provides an ideal environment for fly oviposition.",
cite_style
))
story.append(Paragraph(
i('Dermatology 2-Volume Set 5e:') + " " + i('"In wound myiasis, an open wound or orifice attracts flies to deposit their eggs. '
'Any body area can be infested, and the most serious sequelae occur when the nasal cavity, sinuses, or scalp are involved."'),
cite_style
))
# ═══════════════════════════════════════════════════════════════════
# 5. PATHOGENESIS
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("5. PATHOGENESIS"))
story.append(Spacer(1, 4))
steps = [
("Step 1", "Gravid female fly attracted to foul-smelling, suppurating, or necrotic nasal tissue"),
("Step 2", "Eggs deposited at nares, nasal vestibule, or within the nasal cavity; some species larviposit directly"),
("Step 3", "Eggs hatch within 12–24 hours under warm, moist conditions"),
("Step 4", "First-instar larvae (~1 mm) penetrate mucosa and migrate deeper into nasal cavity"),
("Step 5", "Larvae pass through three instars (L1 → L2 → L3) over days–weeks, feeding on tissue"),
("Step 6", "Larvae produce proteolytic enzymes and mechanical trauma → progressive tissue destruction"),
("Step 7", "Secretions attract more flies — amplification by the " + b('"aggregation phenomenon"')),
("Step 8", "If untreated: penetration through cribriform plate (intracranial) or ethmoid sinuses (orbital)"),
("Step 9", "Third-instar larvae (L3) mature, exit host, drop to ground, and pupate"),
]
for step, desc in steps:
story.append(Paragraph(f"<b>{step}:</b> {desc}", bullet_style))
# ═══════════════════════════════════════════════════════════════════
# 6. CLINICAL FEATURES
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("6. CLINICAL FEATURES"))
story.append(Spacer(1, 4))
story.append(Paragraph(b("A. Symptoms"), h2_style))
sym_headers = ["Symptom", "Description"]
sym_rows = [
["Nasal discharge", "Seropurulent, foul-smelling, blood-tinged; pathognomonic when larvae are visible"],
["Nasal obstruction", "Bilateral or unilateral; may be severe"],
["Epistaxis", "Due to mucosal erosion by feeding larvae"],
["Facial pain / headache", "Particularly with sinus involvement"],
["Sensation of movement", "Characteristic crawling / tickling sensation inside the nose — highly specific symptom"],
["Sneezing", "Reflex response to larval mechanical activity"],
["Nasal fetor", "Strong, putrid odor from necrotic tissue and larval secretions"],
["Periorbital swelling", "With orbital extension via ethmoid sinuses (advanced)"],
]
story.append(make_table(sym_headers, sym_rows, col_widths=[W*0.3, W*0.7]))
story.append(Spacer(1, 6))
story.append(Paragraph(b("B. Signs"), h2_style))
for item in [
"Visible maggots or larvae on anterior rhinoscopy — may be white, cream-colored, or brownish",
"Mucosal congestion, edema, ulceration, and necrotic sloughing",
"Destruction of nasal septum, inferior turbinates, and lateral nasal wall in advanced cases",
"Involvement of maxillary, ethmoid, and frontal sinuses in severe cases",
"Preseptal or orbital cellulitis (rare but documented)",
"Meningitis, cavernous sinus thrombosis with intracranial extension",
]:
story.append(Paragraph("• " + item, bullet_style))
story.append(Spacer(1, 6))
story.append(Paragraph(b("C. Staging") + " (Sharma et al., " + i("J Laryngol Otol,") + " 1989 — Classical Reference)", h2_style))
stg_headers = ["Stage", "Extent of Involvement"]
stg_rows = [
["Stage I", "Confined to nasal vestibule and anterior nasal cavity"],
["Stage II", "Posterior nasal cavity, nasopharynx, paranasal sinuses"],
["Stage III", "Orbital involvement via ethmoid sinuses"],
["Stage IV", "Intracranial extension — life-threatening"],
]
story.append(make_table(stg_headers, stg_rows, col_widths=[W*0.2, W*0.8]))
# ═══════════════════════════════════════════════════════════════════
# 7. DIAGNOSIS
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("7. DIAGNOSIS"))
story.append(Spacer(1, 4))
story.append(Paragraph(b("A. Clinical Diagnosis"), h2_style))
for item in [
"History of exposure + predisposing factors + characteristic symptoms",
b("Anterior rhinoscopy:") + " Direct visualization of larvae — sufficient for diagnosis in most cases",
b("Nasal endoscopy (rigid/flexible):") + " Gold standard; delineates full extent; allows simultaneous removal; identifies posterior nasal cavity, nasopharynx, and sinus ostia involvement",
]:
story.append(Paragraph("• " + item, bullet_style))
story.append(Paragraph(b("B. Investigations"), h2_style))
inv_headers = ["Investigation", "Purpose / Findings"]
inv_rows = [
["CT paranasal sinuses", "Mandatory in advanced cases — assesses sinus involvement, bony destruction, orbital / intracranial extension"],
["MRI", "Superior for soft tissue delineation and intracranial extension assessment"],
["Larval identification (macroscopic + microscopic)", "Size, morphology, spiracles; histological cross-section shows chitinous spines; culture of pupae for species ID"],
["Wound swab culture", "Identify secondary bacterial pathogens"],
["CBC", "Peripheral eosinophilia may be present"],
["Blood glucose (RBS/FBS)", "Screen for undiagnosed diabetes mellitus"],
]
story.append(make_table(inv_headers, inv_rows, col_widths=[W*0.38, W*0.62]))
# ═══════════════════════════════════════════════════════════════════
# 8. DIFFERENTIAL DIAGNOSIS
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("8. DIFFERENTIAL DIAGNOSIS"))
story.append(Spacer(1, 4))
diffs = [
"Atrophic rhinitis (ozaena)",
"Foreign body nose",
"Rhinosporidiosis",
"Inverted papilloma",
"Nasal malignancy (especially chronic destructive presentations)",
"Granulomatosis with polyangiitis (Wegener's granulomatosis)",
"Rhinoscleroma",
"Nasal leishmaniasis",
"Allergic rhinitis (in early / immunocompetent cases — 14-patient case series, MDPI Trop Med 2025)",
]
# Two-column layout
half = (len(diffs)+1)//2
col1 = diffs[:half]
col2 = diffs[half:]
rows = []
for i2 in range(half):
c1 = "• " + col1[i2] if i2 < len(col1) else ""
c2 = "• " + col2[i2] if i2 < len(col2) else ""
rows.append([Paragraph(c1, body_style), Paragraph(c2, body_style)])
diff_tbl = Table(rows, colWidths=[W*0.5, W*0.5])
diff_tbl.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
]))
story.append(diff_tbl)
# ═══════════════════════════════════════════════════════════════════
# 9. TREATMENT
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("9. TREATMENT"))
story.append(Spacer(1, 4))
story.append(Paragraph("Treatment follows a " + b("three-pronged approach:") + " larval removal + local wound care + systemic therapy.", body_style))
story.append(Paragraph(b("A. Mechanical / Physical Removal (Primary Treatment)"), h2_style))
for item in [
b("Nasal endoscopic removal:") + " Under topical/local anesthesia with rigid endoscopy (0° and 30° Hopkins rod); larvae removed with Blakesley forceps or suction — " + b("mainstay of treatment;") + " multiple sessions may be required",
b("Nasal irrigation (douching):") + " Saline or chloroform-water (1% chloroform in saline) to flush larvae and debris; performed via syringe or irrigation cannula",
b("Turpentine oil packing:") + " Nasal packs soaked in turpentine oil suffocate larvae — traditional method, still used in resource-limited settings",
b("Chloroform in liquid paraffin:") + " Applied topically; paralyzes/kills larvae, facilitating removal",
]:
story.append(Paragraph("• " + item, bullet_style))
story.append(Paragraph(b("B. Pharmacological Treatment"), h2_style))
story.append(Paragraph(b("1. Ivermectin — Drug of Choice"), h3_style))
for item in [
b("Mechanism:") + " Glutamate-gated chloride channel agonist → irreversible paralysis of larvae",
b("Dose:") + " 200 mcg/kg single oral dose; repeat at 1–2 weeks in refractory cases",
b("Ivermectin nasal irrigation:") + " 1% ivermectin in propylene glycol diluted in saline — reaches posterior cavity and sinuses (Tay et al., " + i("BMJ Case Rep,") + " 2018)",
b("Combination therapy:") + " Oral + nasal irrigation used in refractory/advanced cases (Camron et al., " + i("Otolaryngol Case Rep,") + " 2023)",
b("Advantage:") + " Reaches larvae in inaccessible sinus locations",
]:
story.append(Paragraph("• " + item, bullet_style))
story.append(Paragraph(b("2. Other Topical Agents"), h3_style))
for item in [
"Chloroform-in-oil (1:9) — kills larvae on contact",
"Turpentine oil — traditional larvicidal agent",
"Saline irrigation — adjunct for washing out killed/stunned larvae",
]:
story.append(Paragraph("• " + item, bullet_style))
story.append(Paragraph(b("3. Antibiotics"), h3_style))
for item in [
"Broad-spectrum systemic antibiotics (amoxicillin-clavulanate, cephalosporins) for secondary bacterial infection — almost universally present",
"Anaerobic cover (metronidazole) for foul-smelling necrotic wounds",
]:
story.append(Paragraph("• " + item, bullet_style))
story.append(Paragraph(b("4. Anti-tetanus Prophylaxis"), h3_style))
story.append(Paragraph(
"Tetanus toxoid / immunoglobulin — " + i("Dermatology 2-Volume Set 5e:") + " " +
i('"Myiasis can be a portal of entry for Clostridium tetani, and vaccination of affected individuals should be considered."'),
cite_style
))
story.append(Paragraph(b("C. Surgical Treatment"), h2_style))
for item in [
b("FESS (Functional Endoscopic Sinus Surgery):") + " For larvae involving the paranasal sinuses",
"Surgical debridement of necrotic tissue",
"Orbital decompression — in cases with orbital extension",
"Neurosurgical consultation — for intracranial extension",
]:
story.append(Paragraph("• " + item, bullet_style))
story.append(Paragraph(b("D. Wound Care and Underlying Condition"), h2_style))
for item in [
"Daily dressing of nasal cavity with antiseptic-soaked ribbon gauze",
"Treatment of underlying predisposing condition (diabetes control, atrophic rhinitis management, etc.)",
]:
story.append(Paragraph("• " + item, bullet_style))
# ═══════════════════════════════════════════════════════════════════
# 10. COMPLICATIONS
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("10. COMPLICATIONS"))
story.append(Spacer(1, 4))
comp_headers = ["Complication", "Mechanism"]
comp_rows = [
["Septal perforation", "Larval enzymatic destruction of cartilage and bone"],
["Destruction of turbinates", "Tissue necrosis by proteolytic enzymes"],
["Acute/chronic sinusitis", "Extension into paranasal sinuses"],
["Orbital cellulitis / abscess", "Ethmoid sinus extension"],
["Blindness", "Optic nerve or globe involvement"],
["Meningitis / encephalitis", "Larval penetration through cribriform plate"],
["Cavernous sinus thrombosis", "Septic thrombophlebitis from intracranial extension"],
["Secondary septicemia", "Bacterial superinfection entering bloodstream"],
["Deafness", "Middle ear involvement via Eustachian tube (rare)"],
["Death", "Intracranial extension, septicemia, multi-organ failure"],
]
story.append(make_table(comp_headers, comp_rows, col_widths=[W*0.42, W*0.58]))
# ═══════════════════════════════════════════════════════════════════
# 11. PREVENTION
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("11. PREVENTION"))
story.append(Spacer(1, 4))
for item in [
"Adequate treatment of underlying nasal pathology, " + b("especially atrophic rhinitis") + " — the most modifiable risk factor",
"Personal and household hygiene; proper waste disposal",
"Fly control measures (window screens, insecticides, fly traps)",
"Regular nasal wound care in bedridden/ICU patients, particularly those with nasogastric tubes",
"Nursing care protocols to prevent fly access to vulnerable patients (mosquito nets, closed room)",
"Healthcare worker awareness in tropical regions (Rana et al., " + i("Ghana Med J,") + " 2020)",
"In endemic areas, clothing should be ironed after sun-drying to kill deposited eggs (" + i("Dermatology 5e") + ")",
"Insect repellents — reduce mosquito contact that may carry botfly eggs",
]:
story.append(Paragraph("• " + item, bullet_style))
# ═══════════════════════════════════════════════════════════════════
# 12. RECENT ADVANCES
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("12. RECENT ADVANCES / SPECIAL CONSIDERATIONS"))
story.append(Spacer(1, 4))
for item in [
b("Nosocomial nasal myiasis:") + " " + i("Megaselia spiracularis") + " (Phorid fly) has caused nasal myiasis in ICU-hospitalized patients — an emerging nosocomial pathogen (Shimizu et al., " + i("Intern Med,") + " 2026 — PMID: 40803864)",
b("Nasal myiasis mimicking allergic rhinitis:") + " 14-case series in immunocompetent adults presenting with rhinitis-like symptoms — underscores the need for endoscopic examination in treatment-resistant rhinitis (MDPI " + i("Tropical Medicine,") + " 2025)",
b("Altered-consciousness patients:") + " A high-risk group requiring active preventive nasal care protocols; Kuo et al. advocate nasal endoscopy monitoring in ICU stroke patients (" + i("J Central South Univ Med Sci,") + " 2021 — PMID: 35232917)",
b("Combination ivermectin therapy:") + " Oral + topical nasal irrigation shows superior larval clearance vs. either alone (Camron et al., " + i("Otolaryngol Case Rep,") + " 2023)",
b("Maggot therapy (paradox):") + " Sterile, FDA-approved " + i("Lucilia sericata") + " larvae are used as medical devices for wound debridement — highlighting the dual role of these organisms in medicine",
]:
story.append(Paragraph("• " + item, bullet_style))
# ═══════════════════════════════════════════════════════════════════
# SUMMARY TABLE
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 6))
story.append(section_header("QUICK SUMMARY — HIGH-YIELD FACTS"))
story.append(Spacer(1, 4))
sum_headers = ["Feature", "Key Point"]
sum_rows = [
["Definition", "Fly larval infestation of nasal cavity and/or paranasal sinuses"],
["Order", "Diptera"],
["Commonest cause (India/Asia)", "Chrysomya bezziana (Old World screwworm)"],
["Nasal botfly species", "Oestrus ovis (sheep nasal botfly)"],
["Most important predisposing factor","Atrophic rhinitis"],
["Pathognomonic symptom", "Crawling/tickling sensation inside nose + visible larvae"],
["Investigation of choice", "Rigid nasal endoscopy"],
["Imaging in advanced cases", "CT paranasal sinuses ± MRI"],
["Drug of choice", "Ivermectin 200 mcg/kg oral (± nasal irrigation)"],
["Primary treatment", "Endoscopic removal + nasal irrigation"],
["Staging system", "Sharma et al. (J Laryngol Otol, 1989): Stages I–IV"],
["Dreaded complication", "Intracranial extension → meningitis / death"],
]
story.append(make_table(sum_headers, sum_rows, col_widths=[W*0.42, W*0.58]))
# ═══════════════════════════════════════════════════════════════════
# EXAMINER TIP
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 8))
tip_box = Table(
[[Paragraph(
"📝 " + b("EXAMINER'S TIP:") + " For 10 marks, must-cover areas are: "
"definition + etiology (species names with regions), predisposing factors (atrophic rhinitis emphasis), "
"clinical features (crawling sensation + visible larvae), endoscopic diagnosis, Sharma's staging, "
"treatment (endoscopic removal + ivermectin dose), complications (intracranial extension), and prevention. "
"Mentioning " + i("Chrysomya bezziana") + " for India and " + i("Oestrus ovis") + " for nasal botfly earns extra credit.",
tip_style
)]],
colWidths=[W]
)
tip_box.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#FFF8E7")),
("BOX", (0,0), (-1,-1), 1.2, colors.HexColor("#E0A000")),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
]))
story.append(tip_box)
# ═══════════════════════════════════════════════════════════════════
# REFERENCES
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 8))
story.append(section_header("REFERENCES"))
story.append(Spacer(1, 4))
refs = [
b("Harrison's Principles of Internal Medicine, 22e") + " (McGraw-Hill, 2025) — Chapter 472: Ectoparasite Infestations",
b("Dermatology 2-Volume Set, 5e") + " (Griffiths et al., Elsevier) — Chapter 84: Cutaneous Myiasis",
b("Medical Microbiology 9e") + " (Murray et al., Elsevier, 2021) — Chapter 77: Myiasis-Causing Flies",
"Bosmia AN et al. Nasal Myiasis in Hinduism and Contemporary Otorhinolaryngology. " + i("J Relig Health.") + " 2017. PMID: 24385004",
"Rana AK et al. Otorhinolaryngological myiasis: the problem and its presentations in the weak and forgotten. " + i("Ghana Med J.") + " 2020. PMID: 33883762",
"Kuo H et al. Nasal myiasis in patients with disturbance of consciousness: A case report and literature review. " + i("J Central South Univ Med Sci.") + " 2021. PMID: 35232917",
"Salmanzadeh S et al. Nasal Myiasis: A Case Report. " + i("Iran J Public Health.") + " 2018. PMID: 30320018",
"Sharma H, Dayal D, Agrawal SP. Nasal myiasis: review of 10 years experience. " + i("J Laryngol Otol.") + " 1989;103:489–491",
"Tay SY et al. Treatment of nasal myiasis with ivermectin irrigation. " + i("BMJ Case Rep.") + " 2018",
"Shimizu S et al. Nosocomial Megaselia spiracularis Nasal Myiasis. " + i("Intern Med.") + " 2026. PMID: 40803864",
"Francesconi F, Lupi O. Myiasis. " + i("Clin Microbiol Rev.") + " 2012;25:79–105",
"Camron D et al. Combination ivermectin therapy to treat nasal myiasis: A case series. " + i("Otolaryngol Case Rep.") + " 2023;27:100535",
]
for idx, ref in enumerate(refs, 1):
story.append(Paragraph(f"{idx}. {ref}", ref_style))
# ═══════════════════════════════════════════════════════════════════
# FOOTER NOTE
# ═══════════════════════════════════════════════════════════════════
story.append(Spacer(1, 10))
story.append(HRFlowable(width=W, thickness=0.5, color=MIDBL))
story.append(Spacer(1, 4))
story.append(Paragraph(
"Generated by Orris Medical AI · June 2026 · For educational purposes only · Always consult current guidelines for clinical practice",
ParagraphStyle("Footer", parent=body_style, fontSize=7.5, textColor=colors.grey, alignment=TA_CENTER)
))
# ═══════════════════════════════════════════════════════════════════
# BUILD
# ═══════════════════════════════════════════════════════════════════
doc.build(story)
print(f"PDF generated: {OUTPUT}")
cd /home/daytona/workspace/nasal-myiasis && python generate_pdf.py
Nasal Myiasis ENT PG
PDF Document · PDF
Rhinolith..... standard textbooks, articles....and also make a pdf....10mark question for post graduate examination
rhinolith nasal calculus
rhinolith nasal calculus ENT review clinical features pathogenesis treatment 2022 2023 2024
PMID: 30598232
PMID: 24744080
https://pmc.ncbi.nlm.nih.gov/articles/PMC8039103
PMID: 23188861
https://onlinelibrary.wiley.com/doi/full/10.1002/ccr3.8679
rhinolithiasis clinical features endoscopy treatment
~/nasal-myiasis/generate_rhinolith_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
OUTPUT = "/home/daytona/workspace/nasal-myiasis/Rhinolith_ENT_PG.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=2*cm, bottomMargin=2*cm,
title="Rhinolith - ENT Postgraduate 10 Marks",
author="Orris Medical AI"
)
W = A4[0] - 4*cm
# ── Colour palette ──────────────────────────────────────────────────
TEAL = colors.HexColor("#1A5E7A")
DARK = colors.HexColor("#1A1A2E")
ROW_ALT = colors.HexColor("#EDF6FA")
GOLD = colors.HexColor("#E0A000")
# ── Styles ──────────────────────────────────────────────────────────
S = getSampleStyleSheet()
def PS(name, **kw):
base = kw.pop("parent", S["Normal"])
return ParagraphStyle(name, parent=base, **kw)
title_s = PS("TitleS", fontSize=22, textColor=colors.white,
fontName="Helvetica-Bold", alignment=TA_CENTER, spaceAfter=4)
sub_s = PS("SubS", fontSize=10, textColor=colors.HexColor("#CCE8F0"),
fontName="Helvetica", alignment=TA_CENTER, spaceAfter=3)
h1_s = PS("H1S", fontSize=13, textColor=colors.white,
fontName="Helvetica-Bold", leading=16, spaceBefore=0, spaceAfter=0)
h2_s = PS("H2S", fontSize=11, textColor=TEAL,
fontName="Helvetica-Bold", leading=14, spaceBefore=10, spaceAfter=3)
h3_s = PS("H3S", fontSize=10, textColor=DARK,
fontName="Helvetica-BoldOblique", leading=13, spaceBefore=6, spaceAfter=2)
body_s = PS("BodyS", fontSize=9.5, textColor=DARK,
fontName="Helvetica", leading=14, spaceAfter=5, alignment=TA_JUSTIFY)
bullet_s = PS("BulletS", parent=body_s, leftIndent=16, bulletIndent=4,
spaceAfter=3, leading=13)
sub_b_s = PS("SubBS", parent=body_s, leftIndent=30, bulletIndent=18,
spaceAfter=2, leading=13, fontSize=9)
cite_s = PS("CiteS", parent=body_s, fontSize=8.8,
textColor=colors.HexColor("#444466"),
leftIndent=12, fontName="Helvetica-Oblique",
spaceAfter=4, leading=12)
ref_s = PS("RefS", parent=body_s, fontSize=8.5,
leftIndent=18, firstLineIndent=-18,
spaceAfter=3, leading=12)
tip_s = PS("TipS", parent=body_s, fontSize=9,
textColor=colors.HexColor("#7B3F00"),
fontName="Helvetica-Oblique", leftIndent=10, rightIndent=10, leading=13)
footer_s = PS("FooterS", parent=body_s, fontSize=7.5,
textColor=colors.grey, alignment=TA_CENTER)
th_s = PS("THS", parent=body_s, textColor=colors.white,
fontSize=9, fontName="Helvetica-Bold", alignment=TA_CENTER)
td_s = PS("TDS", parent=body_s, fontSize=9)
def b(t): return f"<b>{t}</b>"
def i(t): return f"<i>{t}</i>"
def bi(t): return f"<b><i>{t}</i></b>"
def sec(text):
tbl = Table([[Paragraph(text, h1_s)]], colWidths=[W])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), TEAL),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 10),
]))
return tbl
def mktable(headers, rows, cw=None):
if cw is None:
cw = [W/len(headers)]*len(headers)
hrow = [Paragraph(f"<b>{h}</b>", th_s) for h in headers]
data = [hrow]
for idx, row in enumerate(rows):
bg = ROW_ALT if idx % 2 == 0 else colors.white
data.append([Paragraph(str(c), PS(f"TD{idx}", parent=td_s)) for c in row])
t = Table(data, colWidths=cw, repeatRows=1)
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), TEAL),
("ROWBACKGROUNDS",(0,1),(-1,-1), [ROW_ALT, colors.white]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#AAAACC")),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 5),
("VALIGN", (0,0),(-1,-1), "TOP"),
]))
return t
def tip_box(text):
t = Table([[Paragraph(text, tip_s)]], colWidths=[W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), colors.HexColor("#FFF8E7")),
("BOX", (0,0),(-1,-1), 1.2, GOLD),
("TOPPADDING", (0,0),(-1,-1), 8),
("BOTTOMPADDING", (0,0),(-1,-1), 8),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 10),
]))
return t
def info_box(text):
t = Table([[Paragraph(text, cite_s)]], colWidths=[W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), colors.HexColor("#E8F4F8")),
("BOX", (0,0),(-1,-1), 0.8, TEAL),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("LEFTPADDING", (0,0),(-1,-1), 8),
("RIGHTPADDING", (0,0),(-1,-1), 8),
]))
return t
story = []
sp = lambda n=6: Spacer(1, n)
# ═══════════════════════════ TITLE ═══════════════════════════════════
banner = Table(
[[Paragraph("RHINOLITH (NASAL CALCULUS)", title_s)],
[Paragraph("ENT Postgraduate Examination — 10 Marks Model Answer", sub_s)],
[Paragraph("Sources: Scott-Brown's Otorhinolaryngology · KJ Lee's Essential Otolaryngology · PubMed Literature", sub_s)]],
colWidths=[W]
)
banner.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), colors.HexColor("#0D3D52")),
("TOPPADDING", (0,0),(-1,-1), 12),
("BOTTOMPADDING", (0,0),(-1,-1), 12),
("LEFTPADDING", (0,0),(-1,-1), 14),
("RIGHTPADDING", (0,0),(-1,-1), 14),
]))
story += [banner, sp(10)]
# ═══════════════════════════ 1. DEFINITION ════════════════════════════
story += [sec("1. DEFINITION"), sp(4)]
story.append(Paragraph(
"A " + b("rhinolith") + " (from Greek " + i("rhino") + " = nose; " + i("lithos") + " = stone) is a "
"calcareous concretion that forms within the nasal cavity by slow deposition of mineral salts "
"around a central nidus over a period of months to years. The resulting calcified mass occupies "
"the nasal cavity and can cause progressive local tissue damage.",
body_s
))
story.append(Paragraph(
"The condition is termed " + b("rhinolithiasis.") + " The first well-documented case was reported by "
b("Bartholin in 1654.") + " A possible earlier description exists from " + b("Mathias di Gardi in 1502.") +
" Over 600 cases had been reported in literature by 1988 (PMC11585727, 2024).",
cite_s
))
story.append(info_box(
b("Scott-Brown's Otorhinolaryngology Head & Neck Surgery (8th ed):") +
" \"With prolonged indwelling, a foreign body granulation develops and may result in rhinolith formation. "
"Rhinoliths consist of salts of calcium, magnesium phosphate and carbonate and are radio-opaque.\""
))
story.append(info_box(
b("KJ Lee's Essential Otolaryngology:") +
" \"Rhinolith: calcareous concretions around intranasal foreign bodies within the nasal cavity, "
"usually in anterior nasal cavity.\""
))
# ═══════════════════════════ 2. EPIDEMIOLOGY ═════════════════════════
story += [sp(4), sec("2. EPIDEMIOLOGY"), sp(4)]
for item in [
"Incidence: approximately " + b("1 in 10,000") + " of ENT patients — an uncommon condition",
b("Age:") + " Typically diagnosed between " + b("8 and 25 years;") + " may present at any age; longest reported case: 80+ years retention (Kermanshahi & Jassar, " + i("BMJ Case Rep,") + " 2012 — PMID: 23188861)",
b("Sex:") + " Higher incidence in " + b("females") + " — 73.2% female in the largest published series (Aksakal, " + i("Auris Nasus Larynx,") + " 2019 — PMID: 30598232)",
b("Side:") + " " + b("Unilateral") + " in the vast majority of cases; right nasal cavity more commonly affected (attributed to prevalence of right-handed individuals who introduce foreign bodies with their dominant hand)",
b("Location:") + " Most commonly found on the " + b("floor of the nasal cavity,") + " approximately halfway between the anterior and posterior parts; most frequent site is between the " + b("inferior concha and the nasal septum") + " (n=21/23 in Aksakal series)",
"Worldwide distribution; no specific geographic predilection",
]:
story.append(Paragraph("• " + item, bullet_s))
# ═══════════════════════════ 3. ETIOLOGY / NIDUS ═════════════════════
story += [sp(4), sec("3. ETIOLOGY AND NIDUS"), sp(4)]
story.append(Paragraph(
"Every rhinolith forms around a central " + b("nidus") + " — an initial seed that becomes encrusted "
"with mineral salts over time. The nidus is classified as:",
body_s
))
story.append(Paragraph(b("A. Exogenous Nidus (More Common)"), h2_s))
story.append(Paragraph(
"An externally introduced foreign body that is retained in the nasal cavity:",
body_s
))
for item in [
"Pebbles, seeds, beans, pieces of wood, paper, rubber, sponge",
"Metallic objects — coins, bolts, pins, beads (e.g., 80-year retained bolt — Kermanshahi 2012)",
"Button batteries (urgent — cause immediate chemical injury)",
"Dental materials — teeth, dental cement, tooth roots displaced from the oral cavity",
"Hardened mucus plugs or blood clots (transitional between exogenous and endogenous)",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("B. Endogenous Nidus (Less Common)"), h2_s))
story.append(Paragraph(
"Materials originating from within the body:",
body_s
))
for item in [
"Inspissated (dried) mucus or pus",
"Blood clots",
"Sequestrated bone fragments",
"Dental roots / ectopic teeth",
"Inspissated secretions from chronic rhinosinusitis",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(
b("Nidus detection rate:") + " In the Aksakal (2019) series of 23 cases, nidus was identifiable in only "
b("6 patients (26%)") + " — suggesting that the original nidus is often completely encrusted or dissolved "
"by the time of presentation.",
cite_s
))
# ═══════════════════════════ 4. COMPOSITION ══════════════════════════
story += [sp(4), sec("4. COMPOSITION"), sp(4)]
story.append(Paragraph(
"Rhinoliths are composed of inorganic mineral salts deposited in layers around the nidus:",
body_s
))
comp_headers = ["Mineral Salt", "Chemical Formula", "Notes"]
comp_rows = [
["Calcium phosphate", "Ca₃(PO₄)₂", "Most abundant component in most rhinoliths"],
["Calcium carbonate", "CaCO₃", "Common; contributes to white/grey colour"],
["Magnesium phosphate", "Mg₃(PO₄)₂", "Present in variable amounts"],
["Magnesium carbonate", "MgCO₃", "Present in variable amounts"],
["Iron / Ferric oxide", "Fe₂O₃", "Imparts brownish/dark discolouration"],
["Organic matrix", "Protein/mucus", "10–25% of rhinolith weight; forms scaffold"],
]
story.append(mktable(comp_headers, comp_rows, cw=[W*0.30, W*0.25, W*0.45]))
story.append(sp(4))
story.append(Paragraph(
"The mineral composition is essentially identical to that of urinary and salivary calculi. "
"Mineralogical analysis by Yildirim et al. (" + i("Am J Rhinol,") + " 2008) confirmed calcium phosphate "
"and carbonate as the dominant constituents, with iron salts responsible for the characteristic "
b("dark brown/black outer surface") + " of mature rhinoliths.",
body_s
))
# ═══════════════════════════ 5. PATHOGENESIS ═════════════════════════
story += [sp(4), sec("5. PATHOGENESIS"), sp(4)]
steps = [
("Stage 1 — Nidus Introduction",
"A foreign body enters or forms within the nasal cavity and is not expelled by normal mucociliary clearance or sneezing."),
("Stage 2 — Inflammatory Response",
"The retained nidus triggers a local inflammatory reaction with granulation tissue formation, mucosal oedema, and increased mucus secretion."),
("Stage 3 — Salt Deposition",
"Nasal secretions (rich in calcium, magnesium, phosphate, and carbonate ions) bathe the nidus. Under alkaline pH conditions favoured by bacterial activity, these salts precipitate and deposit concentrically on the nidus surface."),
("Stage 4 — Laminated Growth",
"Deposition proceeds in laminated layers (similar to urinary calculi). Growth is very slow — typically over months to years. The rhinolith enlarges progressively, filling the nasal cavity."),
("Stage 5 — Mucosal Damage",
"As the rhinolith enlarges, it erodes surrounding mucosa, turbinates, septum, and ultimately bone. Foul-smelling discharge results from necrotic tissue and secondary bacterial/fungal colonisation."),
("Stage 6 — Complications",
"If untreated: septal perforation, turbinate destruction, maxillary sinus erosion, palatal perforation, orbital complications (very rare)."),
]
for title, desc in steps:
story.append(Paragraph(b(title + ": ") + desc, bullet_s))
story.append(sp(4))
story.append(info_box(
b("Key biochemical principle:") + " Alkaline nasal pH (favoured by secondary infection with urease-producing organisms such as " +
i("Klebsiella,") + " " + i("Proteus,") + " and " + i("Pseudomonas") + ") accelerates calcium and magnesium salt precipitation, "
"promoting faster rhinolith growth."
))
# ═══════════════════════════ 6. CLASSIFICATION ════════════════════════
story += [sp(4), sec("6. CLASSIFICATION"), sp(4)]
story.append(Paragraph(b("A. Based on Origin of Nidus:"), h2_s))
for item in [
b("Exogenous rhinolith:") + " Nidus is an externally introduced foreign body (more common)",
b("Endogenous rhinolith:") + " Nidus is endogenous material (blood clot, mucus, dental root)",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("B. Based on Size:"), h2_s))
for item in [
b("Small rhinolith:") + " < 1 cm — confined to anterior nasal cavity; easier removal",
b("Medium rhinolith:") + " 1–3 cm — may extend to middle meatus",
b("Giant rhinolith:") + " > 3 cm — fills entire nasal cavity; may distort surrounding structures (Xu et al., 2016 — PMID: 29798280)",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("C. Based on Consistency:"), h2_s))
for item in [
b("Soft/friable rhinolith:") + " Early stage; can be removed in pieces",
b("Hard, calcified rhinolith:") + " Mature; may require fragmentation before removal",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("D. Based on Location:"), h2_s))
for item in [
b("Anterior rhinolith:") + " Floor of anterior nasal cavity — most common",
b("Posterior rhinolith:") + " Near choanae; may prolapse into nasopharynx",
b("Sinolith:") + " Within paranasal sinuses (e.g., sphenoid sinolith — Ozcan et al., " + i("J Craniofac Surg,") + " 2013 — PMID: 23524798)",
]:
story.append(Paragraph("• " + item, bullet_s))
# ═══════════════════════════ 7. CLINICAL FEATURES ════════════════════
story += [sp(4), sec("7. CLINICAL FEATURES"), sp(4)]
story.append(Paragraph(b("A. Symptoms"), h2_s))
story.append(Paragraph(
"Symptoms are " + b("insidious in onset") + " and progressive, reflecting the slow growth of the rhinolith. "
"The clinical presentation is dictated by the type, size, and duration of the rhinolith.",
body_s
))
sym_headers = ["Symptom", "Frequency (Aksakal 2019, n=23)", "Notes"]
sym_rows = [
["Nasal obstruction (unilateral)", "100%", "Most common; classically unilateral — pathognomonic pointer"],
["Rhinorrhoea", "82.6%", "Mucopurulent, foul-smelling; may be blood-stained"],
["Nasal malodour (cacosmia)", "78.2%", "Due to necrotic tissue and secondary infection; may be noticed by others"],
["Oral malodour (halitosis)", "26%", "Postnasal drip carrying infected secretions"],
["Headache", "26%", "Referred pain from sinus involvement"],
["Epistaxis", "17.3%", "From mucosal erosion by rhinolith surface"],
["Facial pain", "4.3%", "Periorbital or malar — sinus involvement"],
["Epiphora", "Rare", "Nasolacrimal duct obstruction by large rhinolith"],
["Asymptomatic", "Occasional", "Incidental finding on X-ray or pre-operative imaging"],
]
story.append(mktable(sym_headers, sym_rows, cw=[W*0.30, W*0.28, W*0.42]))
story.append(sp(6))
story.append(Paragraph(b("B. Signs on Examination"), h2_s))
for item in [
b("Anterior rhinoscopy:") + " Greyish-white, brownish, or black irregular mass in the nasal cavity; hard on probing; may be partially covered by granulation tissue or crusting",
b("Nasal endoscopy:") + " Reveals exact location, size, and relationship to surrounding structures; identifies secondary mucosal changes, polyps, or sinus disease",
b("Mucosal changes:") + " Surrounding mucosa is oedematous, inflamed, ulcerated; granulation tissue often present",
b("Septal deviation:") + " Commonest associated sinonasal pathology (43.4% — Aksakal 2019)",
b("Inferior turbinate hypoplasia:") + " With very long-standing rhinolith (Derosas et al., " + i("Ear Nose Throat J,") + " 2008)",
b("Palatal bulge:") + " In giant rhinoliths pressing through the nasal floor",
]:
story.append(Paragraph("• " + item, bullet_s))
# ═══════════════════════════ 8. DIAGNOSIS ═════════════════════════════
story += [sp(4), sec("8. DIAGNOSIS"), sp(4)]
story.append(Paragraph(b("A. Clinical Diagnosis"), h2_s))
for item in [
b("History:") + " Unilateral nasal obstruction + foul-smelling discharge, often for months/years; history of foreign body insertion (especially in children)",
b("Anterior rhinoscopy:") + " First-line; often reveals the mass directly",
b("Rigid nasal endoscopy:") + " " + b("Gold standard") + " for diagnosis; delineates extent; guides surgical planning",
b("Probing:") + " Hard, gritty, irregular surface on probe palpation distinguishes rhinolith from soft tissue masses",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("B. Radiological Investigation"), h2_s))
inv_headers = ["Investigation", "Findings / Role"]
inv_rows = [
["X-ray PNS (lateral / PA view)",
"Rhinolith appears as a RADIO-OPAQUE mass in the nasal cavity — first-line, inexpensive screening; sensitivity ~80–90%"],
["CT scan of nose and PNS (coronal + axial)",
"Gold standard imaging; shows exact location, size, bone erosion, sinus involvement, relationship to orbit/skull base; essential pre-operatively for large rhinoliths"],
["MRI",
"Superior soft-tissue detail; used when malignancy or intracranial extension is suspected"],
["Orthopantomogram (OPG)",
"Useful when dental root is suspected as nidus; shows relation to dentition"],
]
story.append(mktable(inv_headers, inv_rows, cw=[W*0.36, W*0.64]))
story.append(sp(4))
story.append(info_box(
b("Radiological hallmark:") + " Rhinolith is RADIO-OPAQUE due to calcium and magnesium salts. "
"CT demonstrates a hyperdense (high attenuation) mass with a laminated or irregular pattern. "
"The nidus (if metallic) appears as a central hyperdense focus. "
"(Scott-Brown's Otorhinolaryngology, 8th ed.; Yildirim et al., " + i("Am J Rhinol,") + " 2008)"
))
story.append(Paragraph(b("C. Laboratory / Histopathological Analysis"), h2_s))
for item in [
b("Mineralogical analysis:") + " Confirms composition (calcium phosphate/carbonate, magnesium salts) — Yildirim et al. (" + i("Am J Rhinol,") + " 2008)",
b("Histopathology:") + " Laminated calcified structure with central nidus; surrounding granulation tissue with mixed inflammatory infiltrate",
b("Microbiology:") + " Swab for secondary infection — common organisms: " + i("Klebsiella pneumoniae, Pseudomonas aeruginosa, Proteus mirabilis,") + " anaerobes",
b("Actinomycosis association:") + " " + i("Actinomyces israelii") + " has been isolated from rhinoliths (Li & Zhang, 2013 — PMID: 23650717); sulphur granules on Gram stain",
]:
story.append(Paragraph("• " + item, bullet_s))
# ═══════════════════════════ 9. DIFFERENTIAL DIAGNOSIS ════════════════
story += [sp(4), sec("9. DIFFERENTIAL DIAGNOSIS"), sp(4)]
dd_headers = ["Condition", "Distinguishing Features"]
dd_rows = [
["Nasal foreign body (recent)", "No calcification; younger child; recent history of insertion; soft on probing"],
["Nasal polyp", "Soft, grey, insensate, non-tender; bilateral; not radio-opaque; moves on probing"],
["Antrochoanal polyp", "Arises from maxillary sinus; posterior extension; soft; not radio-opaque"],
["Osteoma", "Lobulated, ivory-hard, bony density on CT; attached to bony wall; no nidus"],
["Nasal malignancy", "Irregular, friable, bleeds on touch; destructive on CT; biopsy confirms"],
["Inverted papilloma", "Unilateral; irregular surface; CT shows nasal/sinus involvement; needs biopsy"],
["Mucocele", "Cystic expansion of sinus; non-calcified on CT; no nidus"],
["Fungal ball (aspergilloma)", "Hyperdense on CT; within sinus; characteristic fungal hyphae on histopathology"],
["Rhinoscleroma", "Chronic granulomatous; bilateral; Mikulicz cells on biopsy; Klebsiella rhinoscleromatis"],
["Choanal atresia", "Failure to pass nasal catheter; presents in neonates/infants; bony/membranous plate on CT"],
]
story.append(mktable(dd_headers, dd_rows, cw=[W*0.32, W*0.68]))
story.append(sp(4))
story.append(info_box(
b("Key differentiator from KJ Lee's (Differential Diagnosis of PNS Lesions):") +
" Rhinolith is listed under " + b("Anatomic/Structural") + " alongside nasal foreign body and mucocele. "
"The combination of radio-opacity + hard mass + foul discharge + unilateral obstruction "
"is virtually pathognomonic of rhinolith."
))
# ═══════════════════════════ 10. TREATMENT ════════════════════════════
story += [sp(4), sec("10. TREATMENT"), sp(4)]
story.append(Paragraph(
"The " + b("definitive treatment of rhinolith is surgical removal") + " of the calcified mass. "
"The approach depends on size, location, consistency, and associated sinonasal pathology.",
body_s
))
story.append(Paragraph(b("A. Pre-operative Assessment"), h2_s))
for item in [
"CT scan of PNS to define size, location, bony erosion, sinus involvement",
"Nasal endoscopy to assess posterior extent and mucosal condition",
"Swab for culture and sensitivity; commence pre-operative antibiotics if secondary infection present",
"Haematological work-up (CBC, coagulation screen, blood glucose)",
"Consent for possible concurrent septoplasty, FESS, or antral washout",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("B. Anaesthesia"), h2_s))
for item in [
b("General anaesthesia:") + " Preferred — provides airway protection, prevents aspiration of fragments, allows thorough surgical access",
b("Local anaesthesia:") + " May be used for small anterior rhinoliths in cooperative adults (topical cocaine 4% + infiltration)",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("C. Surgical Techniques"), h2_s))
story.append(Paragraph(b("1. Endoscopic Removal (Method of Choice)"), h3_s))
for item in [
"Rigid nasal endoscopy (0° and 30° Hopkins rod) under general anaesthesia",
"Nasal decongestant pack inserted first (adrenaline 1:10,000 + topical lignocaine) to improve visualisation",
"Rhinolith grasped with Blakesley forceps or Tilley-Dench forceps and removed intact if small",
b("Fragmentation before removal:") + " Large or hard rhinoliths are broken in situ using:",
]:
story.append(Paragraph("• " + item, bullet_s))
for item in [
"Mechanical fragmentation with curved forceps or Hajek punch",
"Nasal chisel and mallet (under direct vision)",
"Ultrasonic or laser fragmentation (emerging technique)",
]:
story.append(Paragraph(" ‒ " + item, sub_b_s))
story.append(Paragraph(b("2. Anterior Nares Approach"), h3_s))
for item in [
"For small, soft, anterior rhinoliths — direct removal through anterior nares under direct vision",
"Hook or bent Jobson-Horne probe placed behind the mass and pulled anteriorly (Scott-Brown's, 8th ed.)",
"Balloon catheter (Fogarty) technique: passed behind rhinolith, inflated, withdrawn",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("3. Lateral Rhinotomy"), h3_s))
for item in [
"Reserved for very large, irregular, hard rhinoliths that cannot be removed endoscopically",
"External incision along the lateral wall of the nose with mucosal elevation and mass removal",
"Allows maximal access; rarely required in modern endoscopic era",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("4. Caldwell-Luc Approach"), h3_s))
for item in [
"If rhinolith extends into the maxillary sinus or causes significant maxillary sinus disease",
"Combined with endoscopic antrostomy in contemporary practice",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("D. Concomitant Procedures"), h2_s))
story.append(Paragraph(
"Additional sinonasal surgery performed at the same time as rhinolith removal (Aksakal 2019 series):",
body_s
))
conc_headers = ["Concomitant Surgery", "Frequency (Aksakal 2019, n=23)"]
conc_rows = [
["Septoplasty", "5 cases (21.7%)"],
["Septorhinoplasty", "1 case (4.3%)"],
["Antrochoanal polyp excision","1 case (4.3%)"],
["Adenoidectomy", "1 case (4.3%)"],
["FESS", "As required for sinus disease"],
]
story.append(mktable(conc_headers, conc_rows, cw=[W*0.55, W*0.45]))
story.append(sp(4))
story.append(Paragraph(b("E. Post-operative Care"), h2_s))
for item in [
"Nasal irrigation with saline (twice daily) for mucosal healing",
"Broad-spectrum antibiotics for 5–7 days (amoxicillin-clavulanate / cephalosporins)",
"Antifungals if fungal colonisation identified",
"Nasal decongestants (short-term)",
"Regular outpatient endoscopic review",
"Histopathology and mineralogy of the removed specimen",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("F. Conservative Management (Rarely Indicated)"), h2_s))
story.append(Paragraph(
"In the case reported by Kermanshahi & Jassar (PMID: 23188861), an 80+ year old rhinolith in an elderly "
"patient was managed conservatively due to risk of anaesthesia, with regular outpatient monitoring. "
"This is the exception, not the rule.",
cite_s
))
# ═══════════════════════════ 11. COMPLICATIONS ════════════════════════
story += [sp(4), sec("11. COMPLICATIONS"), sp(4)]
story.append(Paragraph(b("A. Local Complications (from rhinolith itself):"), h2_s))
comp_local = [
["Septal perforation", "Pressure erosion of nasal septum by expanding rhinolith"],
["Turbinate destruction", "Atrophy/hypoplasia of inferior turbinate from chronic pressure"],
["Maxillary sinus medial wall erosion", "Extension of rhinolith into antrum; causes chronic maxillary sinusitis (Varadharajan et al., BMJ Case Rep, 2014 — PMID: 24744080)"],
["Palatal perforation", "Giant rhinolith eroding through nasal floor into oral cavity"],
["Nasolacrimal duct obstruction", "Epiphora, dacryocystitis"],
["Actinomycosis", "Secondary colonisation by Actinomyces israelii reported (PMID: 23650717)"],
["Orbital complications", "Very rare; via ethmoid or medial orbital wall erosion"],
]
story.append(mktable(["Complication", "Mechanism"], comp_local, cw=[W*0.40, W*0.60]))
story.append(sp(6))
story.append(Paragraph(b("B. Complications of Surgery:"), h2_s))
for item in [
"Intra-operative bleeding",
"Aspiration of rhinolith fragment (risk minimised under GA with throat pack)",
"Damage to nasal septum, turbinates, or nasolacrimal duct",
"Failure to remove complete rhinolith (leaving fragments) — risk of recurrence",
"Synechiae (adhesions) post-operatively",
]:
story.append(Paragraph("• " + item, bullet_s))
# ═══════════════════════════ 12. PROGNOSIS & PREVENTION ══════════════
story += [sp(4), sec("12. PROGNOSIS AND PREVENTION"), sp(4)]
story.append(Paragraph(b("Prognosis:"), h2_s))
for item in [
"Excellent after complete surgical removal",
"No recurrence if entire rhinolith + nidus removed",
"Mucosal healing usually complete within weeks",
"Septal perforation or turbinate hypoplasia, if present, may require secondary corrective surgery",
]:
story.append(Paragraph("• " + item, bullet_s))
story.append(Paragraph(b("Prevention:"), h2_s))
for item in [
"Prompt removal of nasal foreign bodies — do not allow prolonged retention",
"Parental and school education about dangers of nasal foreign body insertion in children",
"Regular nasal examination in patients presenting with unilateral nasal obstruction",
b("High index of suspicion") + " in any patient with long-standing unilateral nasal symptoms — rhinolith should be a top differential",
]:
story.append(Paragraph("• " + item, bullet_s))
# ═══════════════════════════ SUMMARY TABLE ════════════════════════════
story += [sp(4), sec("QUICK SUMMARY — HIGH-YIELD FACTS"), sp(4)]
sum_headers = ["Feature", "Key Point"]
sum_rows = [
["Definition", "Calcareous concretion in nasal cavity around a central nidus"],
["First described", "Bartholin, 1654"],
["Incidence", "~1 in 10,000 ENT patients"],
["Commonest nidus", "Exogenous foreign body (retained)"],
["Composition", "Calcium & magnesium phosphate and carbonate salts"],
["Commonest site", "Floor of nasal cavity between inferior concha and septum"],
["Sex predilection", "Female > Male (73% female — Aksakal 2019)"],
["Pathognomonic feature", "Unilateral nasal obstruction + foul discharge + radio-opaque mass"],
["Imaging hallmark", "Radio-opaque mass on X-ray; hyperdense on CT"],
["Investigation of choice", "CT PNS + rigid nasal endoscopy"],
["Treatment of choice", "Endoscopic removal under general anaesthesia"],
["When lateral rhinotomy needed", "Very large, hard, irregular rhinolith — cannot be removed endoscopically"],
["Key association", "Actinomycosis (Actinomyces israelii)"],
["Dreaded complication", "Palatal perforation, maxillary sinus erosion"],
]
story.append(mktable(sum_headers, sum_rows, cw=[W*0.42, W*0.58]))
# ═══════════════════════════ EXAMINER TIP ════════════════════════════
story += [sp(8)]
story.append(tip_box(
"📝 " + b("EXAMINER'S TIP:") + " For 10 marks, must-covers are: definition + etymology + history (Bartholin 1654), "
"classification of nidus (endogenous vs exogenous), composition (calcium/magnesium phosphate + carbonate), "
"clinical features (Aksakal 2019 series — 100% nasal obstruction, 78% cacosmia), "
"investigations (radio-opaque on X-ray, CT as gold standard), "
"differential diagnosis (osteoma vs polyp vs rhinolith), "
"treatment (endoscopic removal under GA; lateral rhinotomy for giant rhinoliths), "
"and complications (actinomycosis association is a favourite PG question)."
))
# ═══════════════════════════ REFERENCES ══════════════════════════════
story += [sp(8), sec("REFERENCES"), sp(4)]
refs = [
b("Scott-Brown's Otorhinolaryngology Head & Neck Surgery, 8th ed.") +
" (Gleeson et al., CRC Press) — Vol 2, Chapter 34: Nasal Foreign Bodies",
b("KJ Lee's Essential Otolaryngology, 11th ed.") +
" (McGraw-Hill, 2016) — Differential Diagnosis of Paranasal Sinus Neoplasms",
"Aksakal C. Rhinolith: Examining the clinical, radiological and surgical features of 23 cases. "
+ i("Auris Nasus Larynx.") + " 2019;46(4). PMID: 30598232",
"Kermanshahi MS, Jassar P. A bolt from the blew: rhinolith in the nose for more than 80 years. "
+ i("BMJ Case Rep.") + " 2012. PMID: 23188861",
"Varadharajan K et al. Rhinolith causing unilateral chronic maxillary rhinosinusitis. "
+ i("BMJ Case Rep.") + " 2014. PMID: 24744080",
"Li Z, Zhang S. A case of rhinolith and actinomycosis. "
+ i("Lin Chuang Er Bi Yan Hou Tou Jing Wai Ke Za Zhi.") + " 2013. PMID: 23650717",
"Ozcan C et al. Sinolith: a rare isolated sphenoid sinus lesion. "
+ i("J Craniofac Surg.") + " 2013;24(2). PMID: 23524798",
"Xu W et al. Giant rhinolith: a case report. "
+ i("Lin Chuang Er Bi Yan Hou Tou Jing Wai Ke Za Zhi.") + " 2016. PMID: 29798280",
"Yildirim N, Arslanoglu A, Sahar M, Yildirim A. Rhinolithiasis: clinical, radiological, and "
"mineralogical features. " + i("Am J Rhinol.") + " 2008;22:78–81",
"Eliachar I, Schalit M. Rhinolithiasis: report of eight cases. "
+ i("Arch Otolaryngol.") + " 1970;91:88–90",
"Paediatric rhinolith: Case report and literature review. "
+ i("PMC11585727") + " (2024)",
"Ran X et al. Analysis of six cases concerning clinical features of rhinolith. "
+ i("Lin Chuang Er Bi Yan Hou Tou Jing Wai Ke Za Zhi.") + " 2014. PMID: 25752124",
]
for idx, ref in enumerate(refs, 1):
story.append(Paragraph(f"{idx}. {ref}", ref_s))
# ═══════════════════════════ FOOTER ══════════════════════════════════
story += [sp(10), HRFlowable(width=W, thickness=0.5, color=colors.HexColor("#B8DDE8")), sp(4)]
story.append(Paragraph(
"Generated by Orris Medical AI · June 2026 · For educational purposes only · Always verify with current clinical guidelines",
footer_s
))
doc.build(story)
print(f"PDF generated: {OUTPUT}")
cd /home/daytona/workspace/nasal-myiasis && python generate_rhinolith_pdf.py
~/nasal-myiasis/generate_rhinolith_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/nasal-myiasis/generate_rhinolith_pdf.py'
cd /home/daytona/workspace/nasal-myiasis && python generate_rhinolith_pdf.py
~/nasal-myiasis/generate_rhinolith_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/nasal-myiasis/generate_rhinolith_pdf.py'
cd /home/daytona/workspace/nasal-myiasis && python -c " import ast, sys with open('generate_rhinolith_pdf.py') as f: src = f.read() try: ast.parse(src) print('Syntax OK') except SyntaxError as e: print(f'SyntaxError at line {e.lineno}: {e.msg}') # print surrounding lines lines = src.splitlines() start = max(0, e.lineno-3) for i, l in enumerate(lines[start:e.lineno+2], start+1): print(f'{i}: {l}') "
Rhinolith..... standard textbooks, articles.......10mark question for post graduate examination