Hi

Hey! How can I help you today?

Prepare a graph of menstrual cycle with levels of hormones through out normal menstrual cycle

Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Running Command

ls /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or not found"

Running Command

mkdir -p /home/daytona/workspace/menstrual-cycle && python3 << 'EOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches import numpy as np days = np.linspace(1, 28, 300) # --- FSH: rises early follicular, peaks just before ovulation (day 13), then drops --- def fsh(d): base = 5 # early follicular rise early = 4 * np.exp(-((d - 3)**2) / (2 * 3**2)) # pre-ovulatory surge surge = 7 * np.exp(-((d - 13)**2) / (2 * 0.8**2)) # luteal low luteal = np.where(d > 14, -1.5 * np.exp(-((d - 20)**2) / (2 * 4**2)), 0) return base + early + surge + luteal # --- LH: flat early, sharp mid-cycle surge at day 13, then drops --- def lh(d): base = 5 surge = 60 * np.exp(-((d - 13.5)**2) / (2 * 0.6**2)) return base + surge # --- Estrogen: rises through follicular phase, peaks at day 12, dips, then secondary luteal peak --- def estrogen(d): follicular_peak = 250 * np.exp(-((d - 12)**2) / (2 * 2.5**2)) luteal_peak = 130 * np.exp(-((d - 21)**2) / (2 * 3**2)) baseline = 40 return baseline + follicular_peak + luteal_peak # --- Progesterone: very low follicular, rises post-ovulation, peaks day 21, drops at end --- def progesterone(d): luteal = np.where(d > 14, 18 * np.exp(-((d - 21)**2) / (2 * 3.5**2)), 0.5 * np.ones_like(d)) return luteal FSH_vals = fsh(days) LH_vals = lh(days) E2_vals = estrogen(days) P4_vals = progesterone(days) fig = plt.figure(figsize=(14, 11), facecolor='#FAFAFA') fig.suptitle('The Normal Menstrual Cycle\nHormonal Changes Over 28 Days', fontsize=16, fontweight='bold', y=0.98, color='#1a1a2e') gs = fig.add_gridspec(3, 1, hspace=0.45, top=0.93, bottom=0.08) # ---- TOP PANEL: FSH & LH ---- ax1 = fig.add_subplot(gs[0]) ax1.plot(days, FSH_vals, color='#0077B6', lw=2.5, label='FSH') ax1.plot(days, LH_vals, color='#E63946', lw=2.5, label='LH') ax1.set_ylabel('Hormone level\n(IU/L)', fontsize=10) ax1.set_title('Gonadotropins (FSH & LH)', fontsize=11, fontweight='bold', color='#333') ax1.legend(loc='upper left', fontsize=10, framealpha=0.8) ax1.set_xlim(1, 28) ax1.set_ylim(0, 80) ax1.axvline(x=14, color='gray', linestyle='--', linewidth=1.2, alpha=0.7) ax1.annotate('Ovulation\n(Day 14)', xy=(14, 70), ha='center', fontsize=9, color='gray', style='italic') ax1.set_facecolor('#FAFAFA') ax1.spines['top'].set_visible(False) ax1.spines['right'].set_visible(False) ax1.tick_params(labelbottom=False) # ---- MIDDLE PANEL: Estrogen & Progesterone ---- ax2 = fig.add_subplot(gs[1]) ax2_twin = ax2.twinx() ax2.plot(days, E2_vals, color='#F4845F', lw=2.5, label='Estrogen (E₂)') ax2_twin.plot(days, P4_vals, color='#8338EC', lw=2.5, label='Progesterone (P₄)', linestyle='-') ax2.set_ylabel('Estrogen (E₂)\n(pg/mL)', fontsize=10, color='#F4845F') ax2_twin.set_ylabel('Progesterone (P₄)\n(ng/mL)', fontsize=10, color='#8338EC') ax2.set_title('Ovarian Hormones (Estrogen & Progesterone)', fontsize=11, fontweight='bold', color='#333') ax2.set_xlim(1, 28) ax2.set_ylim(0, 340) ax2_twin.set_ylim(0, 22) ax2.axvline(x=14, color='gray', linestyle='--', linewidth=1.2, alpha=0.7) ax2.tick_params(axis='y', labelcolor='#F4845F') ax2_twin.tick_params(axis='y', labelcolor='#8338EC') ax2.set_facecolor('#FAFAFA') ax2.spines['top'].set_visible(False) ax2.tick_params(labelbottom=False) lines1, labels1 = ax2.get_legend_handles_labels() lines2, labels2 = ax2_twin.get_legend_handles_labels() ax2.legend(lines1 + lines2, labels1 + labels2, loc='upper left', fontsize=10, framealpha=0.8) # ---- BOTTOM PANEL: Phase/endometrium annotations ---- ax3 = fig.add_subplot(gs[2]) ax3.set_xlim(1, 28) ax3.set_ylim(0, 3) ax3.axis('off') # Phase bands ax3.axvspan(1, 5, alpha=0.2, color='#FF6B6B') ax3.axvspan(5, 14, alpha=0.2, color='#06D6A0') ax3.axvspan(14, 28, alpha=0.2, color='#FFB703') ax3.text(3, 2.3, 'Menstruation\n(Days 1–5)', ha='center', va='center', fontsize=9.5, fontweight='bold', color='#CC0000') ax3.text(9.5, 2.3, 'Follicular / Proliferative Phase\n(Days 5–14)', ha='center', va='center', fontsize=9.5, fontweight='bold', color='#028960') ax3.text(21, 2.3, 'Luteal / Secretory Phase\n(Days 14–28)', ha='center', va='center', fontsize=9.5, fontweight='bold', color='#b37700') # Ovulation marker ax3.annotate('', xy=(14, 1.8), xytext=(14, 1.0), arrowprops=dict(arrowstyle='->', color='black', lw=2)) ax3.text(14, 0.6, 'Ovulation\n(Day 14)', ha='center', fontsize=9.5, color='black', fontweight='bold') # Endometrial changes ax3.text(3, 0.7, 'Endometrium\nshedding', ha='center', fontsize=8.5, color='#CC0000', style='italic') ax3.text(9.5, 0.7, 'Proliferation of\nendometrium', ha='center', fontsize=8.5, color='#028960', style='italic') ax3.text(21, 0.7, 'Secretory changes;\nglandular activity', ha='center', fontsize=8.5, color='#b37700', style='italic') ax3.set_title('Menstrual Cycle Phases & Endometrial Changes', fontsize=11, fontweight='bold', color='#333') # x-axis ticks on all panels for ax in [ax1, ax2]: ax.set_xticks([1, 5, 7, 10, 12, 13, 14, 16, 18, 21, 24, 28]) # Common x label fig.text(0.5, 0.02, 'Day of Menstrual Cycle', ha='center', fontsize=12, fontweight='bold') out_path = '/home/daytona/workspace/menstrual-cycle/menstrual_cycle_hormones.png' plt.savefig(out_path, dpi=180, bbox_inches='tight', facecolor='#FAFAFA') print(f"Saved to {out_path}") EOF

Here is the graph:

menstrual cycle hormones

Image · PNG


What the Graph Shows

The graph is divided into 3 panels across a standard 28-day cycle:

Panel 1 - Gonadotropins (FSH & LH)

  • FSH (blue): Rises slightly in the early follicular phase to stimulate follicle development, co-peaks with LH at ovulation, then falls during the luteal phase.
  • LH (red): Remains low throughout, then produces a sharp mid-cycle surge at ~Day 13 - this surge triggers ovulation at Day 14.

Panel 2 - Ovarian Hormones (Estrogen & Progesterone)

  • Estrogen/E₂ (orange): Rises progressively during the follicular phase, reaching its first peak around Day 12 (triggering the LH surge). After ovulation it dips briefly, then rises to a smaller secondary peak around Day 21 from the corpus luteum.
  • Progesterone/P₄ (purple): Near zero during the follicular phase. Rises sharply after ovulation and peaks around Day 21 under the influence of the corpus luteum. Falls at the end of the luteal phase as the corpus luteum regresses, causing menstruation.

Panel 3 - Phases & Endometrial Changes

PhaseDaysDominant HormoneEndometrium
Menstruation1-5None (hormone withdrawal)Shedding
Follicular / Proliferative5-14EstrogenRegeneration & growth
OvulationDay 14LH surge-
Luteal / Secretory14-28ProgesteroneSecretory glandular changes
Based on: Costanzo Physiology 7th Ed. & Ganong's Review of Medical Physiology 26th Ed.

Is fsh graph is crt

Good question! The FSH curve in the graph is mostly correct but has a subtle inaccuracy. Let me explain:
What the graph shows:
  • FSH rises early in the follicular phase, co-peaks with LH at ~Day 13, then drops in the luteal phase.
What is correct:
  • FSH does rise in the early follicular phase to recruit and stimulate follicle growth.
  • FSH does have a co-surge with LH just before ovulation (~Day 12-13) - this is physiologically accurate.
  • FSH is suppressed during the luteal phase - correct.
What could be more accurate:
  • The early follicular FSH rise should be more of a gentle, sustained elevation from Day 1-10 (not a sharp early peak), as FSH steadily drives follicle maturation.
  • The mid-cycle FSH surge is real but smaller and shorter than the LH surge - the LH:FSH surge ratio is roughly 3:1 to 10:1. The graph captures this correctly.
  • There is a small but important rise in FSH at the very end of the luteal phase (Day 25-28) - this is the "intercycle FSH rise" that begins recruiting the next cohort of follicles before menstruation. This is often missing from simplified diagrams.
Summary: The graph is largely correct for a teaching/overview purpose. The one missing element is the late luteal/early intercycle FSH rise around Day 25-28, which is an important physiologic detail seen in more detailed textbook diagrams (e.g., Ganong's, Costanzo).
Would you like me to regenerate the graph with a more precise FSH curve including that late-cycle rise?

Regenerate the graph with corrected FSH intercycle rise

Running Command

python3 << 'EOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import numpy as np days = np.linspace(1, 28, 400) # --- FSH: CORRECTED --- # 1. Gentle sustained rise from Day 1-10 (follicular recruitment) # 2. Small co-surge with LH at Day 12-13 (smaller than LH) # 3. Suppressed mid-luteal phase # 4. Late luteal INTERCYCLE RISE starting ~Day 25 (recruits next cohort) def fsh(d): baseline = 5 # Gentle follicular rise (broad, sustained Days 1-10) follicular = 5 * np.exp(-((d - 6)**2) / (2 * 5**2)) # Small mid-cycle co-surge (Day 12-13, much smaller than LH) cosurge = 9 * np.exp(-((d - 13)**2) / (2 * 0.7**2)) # Luteal suppression dip suppression = -3 * np.exp(-((d - 20)**2) / (2 * 4**2)) # Late luteal / intercycle rise (Day 25-28, begins recruitment of next cycle) intercycle = 6 * np.exp(-((d - 27.5)**2) / (2 * 1.5**2)) return baseline + follicular + cosurge + suppression + intercycle # --- LH: sharp mid-cycle surge at Day 13 --- def lh(d): baseline = 5 surge = 55 * np.exp(-((d - 13.5)**2) / (2 * 0.6**2)) return baseline + surge # --- Estrogen: follicular peak Day 12, dip, luteal secondary peak Day 21 --- def estrogen(d): follicular_peak = 240 * np.exp(-((d - 12)**2) / (2 * 2.5**2)) luteal_peak = 120 * np.exp(-((d - 21)**2) / (2 * 3**2)) return 40 + follicular_peak + luteal_peak # --- Progesterone: low follicular, peaks Day 21, drops end of luteal --- def progesterone(d): luteal = np.where(d > 14, 18 * np.exp(-((d - 21)**2) / (2 * 3.5**2)), 0.4 * np.ones_like(d)) return luteal FSH_vals = fsh(days) LH_vals = lh(days) E2_vals = estrogen(days) P4_vals = progesterone(days) fig = plt.figure(figsize=(14, 12), facecolor='#FAFAFA') fig.suptitle('The Normal Menstrual Cycle — Hormonal Changes Over 28 Days\n(with Corrected FSH Intercycle Rise)', fontsize=15, fontweight='bold', y=0.99, color='#1a1a2e') gs = fig.add_gridspec(3, 1, hspace=0.50, top=0.94, bottom=0.07) # ============================================================ # PANEL 1: FSH & LH # ============================================================ ax1 = fig.add_subplot(gs[0]) ax1.plot(days, FSH_vals, color='#0077B6', lw=2.8, label='FSH', zorder=3) ax1.plot(days, LH_vals, color='#E63946', lw=2.8, label='LH', zorder=3) ax1.fill_between(days, FSH_vals, alpha=0.10, color='#0077B6') ax1.fill_between(days, LH_vals, alpha=0.08, color='#E63946') # Annotate intercycle FSH rise ax1.annotate('Intercycle FSH rise\n(recruits next cohort)', xy=(27, fsh(np.array([27]))[0]), xytext=(23, 30), fontsize=8.5, color='#0077B6', arrowprops=dict(arrowstyle='->', color='#0077B6', lw=1.4), ha='center') # Annotate LH surge ax1.annotate('LH surge\n(triggers ovulation)', xy=(13.5, 62), xytext=(17.5, 65), fontsize=8.5, color='#E63946', arrowprops=dict(arrowstyle='->', color='#E63946', lw=1.4)) ax1.set_ylabel('Hormone level (IU/L)', fontsize=10) ax1.set_title('Gonadotropins — FSH & LH', fontsize=11, fontweight='bold', color='#333') ax1.legend(loc='upper left', fontsize=10, framealpha=0.85) ax1.set_xlim(1, 28) ax1.set_ylim(0, 85) ax1.axvline(x=14, color='#555', linestyle='--', linewidth=1.2, alpha=0.6) ax1.text(14.3, 78, 'Ovulation', fontsize=8.5, color='#555', style='italic') ax1.set_facecolor('#FAFAFA') ax1.spines['top'].set_visible(False) ax1.spines['right'].set_visible(False) ax1.tick_params(labelbottom=False) ax1.set_xticks([1,5,7,10,12,13,14,16,18,21,24,25,28]) # ============================================================ # PANEL 2: Estrogen & Progesterone # ============================================================ ax2 = fig.add_subplot(gs[1]) ax2b = ax2.twinx() ax2.plot(days, E2_vals, color='#F4845F', lw=2.8, label='Estrogen (E₂)') ax2.fill_between(days, E2_vals, alpha=0.10, color='#F4845F') ax2b.plot(days, P4_vals, color='#7B2D8B', lw=2.8, label='Progesterone (P₄)') ax2b.fill_between(days, P4_vals, alpha=0.10, color='#7B2D8B') # Annotations ax2.annotate('Pre-ovulatory\nE₂ peak', xy=(12, 280), xytext=(7.5, 295), fontsize=8.5, color='#C85000', arrowprops=dict(arrowstyle='->', color='#C85000', lw=1.3)) ax2.annotate('Luteal E₂\nsecondary peak', xy=(21, 160), xytext=(18, 235), fontsize=8.5, color='#C85000', arrowprops=dict(arrowstyle='->', color='#C85000', lw=1.3)) ax2b.annotate('P₄ peak\n(Day 21)', xy=(21, 18), xytext=(23.5, 15), fontsize=8.5, color='#7B2D8B', arrowprops=dict(arrowstyle='->', color='#7B2D8B', lw=1.3)) ax2.set_ylabel('Estrogen E₂ (pg/mL)', fontsize=10, color='#F4845F') ax2b.set_ylabel('Progesterone P₄ (ng/mL)', fontsize=10, color='#7B2D8B') ax2.set_title('Ovarian Hormones — Estrogen & Progesterone', fontsize=11, fontweight='bold', color='#333') ax2.set_xlim(1, 28) ax2.set_ylim(0, 360) ax2b.set_ylim(0, 24) ax2.axvline(x=14, color='#555', linestyle='--', linewidth=1.2, alpha=0.6) ax2.tick_params(axis='y', labelcolor='#F4845F') ax2b.tick_params(axis='y', labelcolor='#7B2D8B') ax2.set_facecolor('#FAFAFA') ax2.spines['top'].set_visible(False) ax2.tick_params(labelbottom=False) ax2.set_xticks([1,5,7,10,12,13,14,16,18,21,24,25,28]) lines1, labels1 = ax2.get_legend_handles_labels() lines2, labels2 = ax2b.get_legend_handles_labels() ax2.legend(lines1 + lines2, labels1 + labels2, loc='upper left', fontsize=10, framealpha=0.85) # ============================================================ # PANEL 3: Phase annotations # ============================================================ ax3 = fig.add_subplot(gs[2]) ax3.set_xlim(1, 28) ax3.set_ylim(0, 3.2) ax3.axis('off') # Phase spans ax3.axvspan(1, 5, alpha=0.20, color='#FF6B6B') ax3.axvspan(5, 14, alpha=0.20, color='#06D6A0') ax3.axvspan(14, 28, alpha=0.20, color='#FFB703') ax3.text(3, 2.6, 'Menstruation\n(Days 1–5)', ha='center', fontsize=9, fontweight='bold', color='#CC0000') ax3.text(9.5, 2.6, 'Follicular / Proliferative Phase\n(Days 5–14)', ha='center', fontsize=9, fontweight='bold', color='#028960') ax3.text(21, 2.6, 'Luteal / Secretory Phase\n(Days 14–28)', ha='center', fontsize=9, fontweight='bold', color='#b37700') ax3.annotate('', xy=(14, 2.0), xytext=(14, 1.3), arrowprops=dict(arrowstyle='->', color='black', lw=2)) ax3.text(14, 1.0, 'Ovulation\n(Day 14)', ha='center', fontsize=9, color='black', fontweight='bold') ax3.text(3, 0.4, 'Endometrium\nshedding', ha='center', fontsize=8.5, color='#CC0000', style='italic') ax3.text(9.5, 0.4, 'Proliferation &\ngrowth', ha='center', fontsize=8.5, color='#028960', style='italic') ax3.text(19, 0.4, 'Secretory changes\n& glandular activity', ha='center', fontsize=8.5, color='#b37700', style='italic') ax3.text(26, 0.4, 'FSH intercycle\nrise begins', ha='center', fontsize=8, color='#0077B6', style='italic', fontweight='bold') ax3.set_title('Menstrual Cycle Phases & Endometrial Changes', fontsize=11, fontweight='bold', color='#333') fig.text(0.5, 0.01, 'Day of Menstrual Cycle', ha='center', fontsize=12, fontweight='bold') out = '/home/daytona/workspace/menstrual-cycle/menstrual_cycle_corrected.png' plt.savefig(out, dpi=180, bbox_inches='tight', facecolor='#FAFAFA') print(f"Saved: {out}") EOF

Here is the corrected graph:

menstrual cycle corrected

Image · PNG


Corrections made to FSH:

FeaturePrevious (incorrect)Corrected
Early follicular shapeSharp early peak (Day 3)Gentle, sustained broad rise (Days 1-10)
Mid-cycle co-surgePresent (correct)Retained - smaller than LH surge
Luteal phaseFlat suppressionMild suppression, then rises
Late luteal rise (Day 25-28)MissingAdded - intercycle FSH rise

Why the intercycle FSH rise matters:

  • As progesterone and estrogen fall at the end of the luteal phase (Days 25-28), their negative feedback on the pituitary is removed.
  • FSH begins rising before menstruation even starts - this is what recruits the next cohort of antral follicles for the upcoming cycle.
  • This is an important physiological detail described in Ganong's and Costanzo, and is why follicle recruitment is already underway before the previous cycle ends.
This is a shared conversation. Sign in to Orris to start your own chat.