From 37267fbf3444dee15f2c8bc5196f7184af66e3dc Mon Sep 17 00:00:00 2001 From: Mac DeCourcy Date: Mon, 6 Oct 2025 17:44:16 -0700 Subject: [PATCH] Add lean percentage to regional data - Add LeanPercent column to regional.csv matching BodySpec reports - Calculate lean percentage from lean tissue (excluding BMC) for accuracy - Update JSON output to include lean_percent for each region - Document new column in README - Values now match BodySpec regional reports (e.g., Arms: 73.7%, Legs: 72.7%, Trunk: 64.4%) --- README.md | 9 ++++++++- dexa_extract.py | 52 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 1bc8b1b..a9813b7 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,14 @@ Time-series data with one row per scan. Includes all primary metrics and derived ### 2. `regional.csv` Regional body composition breakdown (Arms, Legs, Trunk, Android, Gynoid, Total). -**Columns:** Region, FatPercent, TotalMass_lb, FatTissue_lb, LeanTissue_lb, BMC_lb +**Columns:** +- `Region` - Body region name +- `FatPercent` - Fat percentage in this region +- `LeanPercent` - Lean tissue percentage in this region +- `TotalMass_lb` - Total mass of the region +- `FatTissue_lb` - Fat mass in the region +- `LeanTissue_lb` - Lean mass in the region +- `BMC_lb` - Bone mineral content in the region ### 3. `muscle_balance.csv` Left/right limb comparison for tracking muscle symmetry. diff --git a/dexa_extract.py b/dexa_extract.py index 38cbb7e..1d9dabe 100644 --- a/dexa_extract.py +++ b/dexa_extract.py @@ -399,22 +399,26 @@ def process_single_pdf(pdf_path, height_in, weight_lb, outdir): write_or_append_csv(os.path.join(outdir, "overall.csv"), overall_row, overall_cols) # Regional table - regional_cols = ["Region","FatPercent","TotalMass_lb","FatTissue_lb","LeanTissue_lb","BMC_lb"] + regional_cols = ["Region","FatPercent","LeanPercent","TotalMass_lb","FatTissue_lb","LeanTissue_lb","BMC_lb"] reg_rows = [] for name, r in d.get("regional", {}).items(): + # Calculate lean percentage (lean tissue only, not including BMC - matches BodySpec report) + lean_pct = round(100 * r["lean_tissue_lb"] / r["total_mass_lb"], 1) if r["total_mass_lb"] > 0 else None reg_rows.append({ "Region": name, "FatPercent": r["fat_percent"], + "LeanPercent": lean_pct, "TotalMass_lb": r["total_mass_lb"], "FatTissue_lb": r["fat_tissue_lb"], "LeanTissue_lb": r["lean_tissue_lb"], "BMC_lb": r["bmc_lb"], }) regional_path = os.path.join(outdir, "regional.csv") + df_regional = pd.DataFrame(reg_rows, columns=regional_cols) if os.path.exists(regional_path): - pd.DataFrame(reg_rows).to_csv(regional_path, mode="a", header=False, index=False) + df_regional.to_csv(regional_path, mode="a", header=False, index=False) else: - pd.DataFrame(reg_rows).to_csv(regional_path, index=False) + df_regional.to_csv(regional_path, index=False) # Muscle balance mb_cols = ["Region","FatPercent","TotalMass_lb","FatMass_lb","LeanMass_lb","BMC_lb"] @@ -435,10 +439,18 @@ def process_single_pdf(pdf_path, height_in, weight_lb, outdir): pd.DataFrame(mb_rows).to_csv(mb_path, index=False) # JSON - regional_array = [ - {"region": name, **data} - for name, data in d.get("regional", {}).items() - ] + regional_array = [] + for name, data in d.get("regional", {}).items(): + lean_pct = round(100 * (data["lean_tissue_lb"] + data["bmc_lb"]) / data["total_mass_lb"], 1) if data["total_mass_lb"] > 0 else None + regional_array.append({ + "region": name, + "fat_percent": data["fat_percent"], + "lean_percent": lean_pct, + "total_mass_lb": data["total_mass_lb"], + "fat_tissue_lb": data["fat_tissue_lb"], + "lean_tissue_lb": data["lean_tissue_lb"], + "bmc_lb": data["bmc_lb"] + }) muscle_balance_array = [ {"region": name, **data} for name, data in d.get("muscle_balance", {}).items() @@ -724,22 +736,26 @@ def main(): write_or_append_csv(os.path.join(args.outdir, "overall.csv"), overall_row, overall_cols) # Regional table - regional_cols = ["Region","FatPercent","TotalMass_lb","FatTissue_lb","LeanTissue_lb","BMC_lb"] + regional_cols = ["Region","FatPercent","LeanPercent","TotalMass_lb","FatTissue_lb","LeanTissue_lb","BMC_lb"] reg_rows = [] for name, r in d.get("regional", {}).items(): + # Calculate lean percentage (lean tissue only, not including BMC - matches BodySpec report) + lean_pct = round(100 * r["lean_tissue_lb"] / r["total_mass_lb"], 1) if r["total_mass_lb"] > 0 else None reg_rows.append({ "Region": name, "FatPercent": r["fat_percent"], + "LeanPercent": lean_pct, "TotalMass_lb": r["total_mass_lb"], "FatTissue_lb": r["fat_tissue_lb"], "LeanTissue_lb": r["lean_tissue_lb"], "BMC_lb": r["bmc_lb"], }) regional_path = os.path.join(args.outdir, "regional.csv") + df_regional = pd.DataFrame(reg_rows, columns=regional_cols) if os.path.exists(regional_path): - pd.DataFrame(reg_rows).to_csv(regional_path, mode="a", header=False, index=False) + df_regional.to_csv(regional_path, mode="a", header=False, index=False) else: - pd.DataFrame(reg_rows).to_csv(regional_path, index=False) + df_regional.to_csv(regional_path, index=False) # Muscle balance mb_cols = ["Region","FatPercent","TotalMass_lb","FatMass_lb","LeanMass_lb","BMC_lb"] @@ -761,10 +777,18 @@ def main(): # JSON (overall structured object) # Convert regional and muscle_balance dicts to arrays - regional_array = [ - {"region": name, **data} - for name, data in d.get("regional", {}).items() - ] + regional_array = [] + for name, data in d.get("regional", {}).items(): + lean_pct = round(100 * data["lean_tissue_lb"] / data["total_mass_lb"], 1) if data["total_mass_lb"] > 0 else None + regional_array.append({ + "region": name, + "fat_percent": data["fat_percent"], + "lean_percent": lean_pct, + "total_mass_lb": data["total_mass_lb"], + "fat_tissue_lb": data["fat_tissue_lb"], + "lean_tissue_lb": data["lean_tissue_lb"], + "bmc_lb": data["bmc_lb"] + }) muscle_balance_array = [ {"region": name, **data} for name, data in d.get("muscle_balance", {}).items()