[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6XFBSY49AC4FFAVY4M6QZ26":3,"skill-same:01M6XFBSY49AC4FFAVY4M6QZ26":89},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":47,"commitSha":48,"skillDirectory":49,"forksCount":14,"repositoryHtmlUrl":50,"repositoryHomepageUrl":6,"ownerHtmlUrl":51,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":52,"install":53,"resources":66},"01M6XFBSY49AC4FFAVY4M6QZ26","pharmacology-wetlab",null,"Computational analysis of pharmacology wet-lab experiments. Western blot densitometry, xenograft tumor growth inhibition, pharmaceutical stability modeling (Arrhenius), radiolabeled antibody biodistribution, MIRD dosimetry, and adverse event grading. For drug databases use chembl-database or fda-database; for molecular docking use diffdock.","fadrienne\u002FMyGitHub","fadrienne","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F197874571?v=4","01M6X64V8VWCRSMFZ4EPS2BWXG","Francesca Adrienne","01M6X64VD5MW4F9PHDWQKS7ZAF",0,"MIT","https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fopenscience\u002Fbackend\u002Fcli\u002Fskills\u002Fbiology\u002Fpharmacology-wetlab\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"pharmacology wetlab","pharmacology","computational","for","lab","event","biology","chembl","molecular","modeling","stability","docking","databases","fda","use","growth","analysis","diffdock","database","experiments","2026-11-10 10:12:30",false,"---\nname: pharmacology-wetlab\ndescription: Computational analysis of pharmacology wet-lab experiments. Western blot densitometry, xenograft tumor growth inhibition, pharmaceutical stability modeling (Arrhenius), radiolabeled antibody biodistribution, MIRD dosimetry, and adverse event grading. For drug databases use chembl-database or fda-database; for molecular docking use diffdock.\ncategory: biology\nlicense: MIT license\nmetadata:\n    skill-author: InkVell Inc.\n---\n\n# Pharmacology Wet-Lab: Experimental Data Analysis\n\n## Overview\n\nPharmacology Wet-Lab provides computational tools for analyzing data from pharmacology experiments. This skill covers western blot densitometry and quantification, xenograft tumor growth inhibition analysis, pharmaceutical stability modeling using Arrhenius kinetics, radiolabeled antibody biodistribution calculations, MIRD-based dosimetry, adverse event grading against CTCAE criteria, and dose-response curve fitting for IC50\u002FEC50 determination.\n\n## When to Use This Skill\n\n- Quantifying protein expression from western blot images\n- Analyzing xenograft tumor growth data and calculating TGI%\n- Predicting pharmaceutical shelf life from accelerated stability data\n- Processing radiolabeled antibody biodistribution data (%ID\u002Fg)\n- Estimating absorbed radiation doses (MIRD dosimetry)\n- Grading adverse events against CTCAE or VCOG-CTCAE scales\n- Fitting dose-response curves for IC50\u002FEC50 determination\n- Calculating combination indices (Chou-Talalay method)\n\n**Related Skills:** For drug database queries use `chembl-database` or `fda-database`. For molecular docking use `diffdock`. For survival analysis use `scikit-survival`.\n\n## Installation\n\n```bash\nuv pip install opencv-python scipy pandas numpy matplotlib lifelines\n```\n\n## Quick Start\n\n```python\nimport numpy as np\nfrom scipy.optimize import curve_fit\n\n# 4-Parameter Logistic for dose-response (IC50)\ndef four_pl(x, bottom, top, ic50, hill):\n    return bottom + (top - bottom) \u002F (1 + (x \u002F ic50) ** hill)\n\nconcentrations = np.array([0.001, 0.01, 0.1, 1, 10, 100])  # uM\nviability = np.array([98, 95, 82, 45, 12, 3])  # % viability\n\npopt, pcov = curve_fit(four_pl, concentrations, viability,\n                       p0=[0, 100, 1, 1], maxfev=10000)\nprint(f\"IC50: {popt[2]:.3f} uM\")\nprint(f\"Hill coefficient: {popt[3]:.2f}\")\n```\n\n## Core Capabilities\n\n### 1. Western Blot Densitometry\n\nQuantify protein bands from western blot images.\n\n```python\nimport cv2\nimport numpy as np\n\ndef quantify_western_blot(image_path, n_lanes, band_height=50):\n    \"\"\"Quantify western blot band intensities.\n\n    Args:\n        image_path: path to blot image\n        n_lanes: number of lanes\n        band_height: expected band height in pixels\n    \"\"\"\n    image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)\n    if image is None:\n        raise FileNotFoundError(f\"Cannot load {image_path}\")\n\n    # Invert (bands are dark on light background)\n    inverted = 255 - image\n    h, w = inverted.shape\n\n    # Divide into lanes\n    lane_width = w \u002F\u002F n_lanes\n    lane_intensities = []\n\n    for i in range(n_lanes):\n        x_start = i * lane_width + int(lane_width * 0.1)\n        x_end = (i + 1) * lane_width - int(lane_width * 0.1)\n        lane = inverted[:, x_start:x_end]\n\n        # Find band (peak in vertical intensity profile)\n        profile = lane.mean(axis=1)\n\n        # Background subtraction (rolling ball approximation)\n        from scipy.ndimage import minimum_filter1d\n        background = minimum_filter1d(profile, size=100)\n        corrected = profile - background\n        corrected = np.clip(corrected, 0, None)\n\n        # Band detection\n        from scipy.signal import find_peaks\n        peaks, props = find_peaks(corrected, height=corrected.max()*0.1,\n                                   distance=band_height)\n\n        # Integrate band intensity (area under curve)\n        total_intensity = 0\n        for peak in peaks:\n            start = max(0, peak - band_height \u002F\u002F 2)\n            end = min(len(corrected), peak + band_height \u002F\u002F 2)\n            band_area = corrected[start:end].sum()\n            total_intensity += band_area\n\n        lane_intensities.append({\n            'lane': i + 1,\n            'raw_intensity': total_intensity,\n            'n_bands': len(peaks),\n            'peak_positions': list(peaks)\n        })\n\n    # Normalize to loading control (first lane or specified)\n    import pandas as pd\n    df = pd.DataFrame(lane_intensities)\n    control_intensity = df.iloc[0]['raw_intensity']\n    df['normalized'] = df['raw_intensity'] \u002F control_intensity\n    df['fold_change'] = df['normalized']\n\n    print(\"Lane intensities:\")\n    print(df[['lane', 'raw_intensity', 'normalized', 'fold_change']])\n\n    return df\n\ndef calculate_fold_change(target_intensities, loading_control_intensities):\n    \"\"\"Calculate normalized fold change with loading control.\n\n    Args:\n        target_intensities: list of target protein band intensities\n        loading_control_intensities: list of loading control (e.g., actin) intensities\n    \"\"\"\n    target = np.array(target_intensities)\n    control = np.array(loading_control_intensities)\n\n    # Normalize target to loading control\n    normalized = target \u002F control\n\n    # Fold change relative to first sample\n    fold_change = normalized \u002F normalized[0]\n\n    return fold_change\n```\n\n### 2. Xenograft Tumor Growth Analysis\n\nAnalyze in vivo tumor growth and treatment efficacy.\n\n```python\nimport numpy as np\nimport pandas as pd\nfrom scipy import stats\n\ndef calculate_tgi(tumor_volumes, groups, time_points, control_group='Vehicle'):\n    \"\"\"Calculate Tumor Growth Inhibition (TGI%).\n\n    TGI% = (1 - (Tt - T0) \u002F (Ct - C0)) * 100\n    where T=treatment, C=control, t=final, 0=initial\n\n    Args:\n        tumor_volumes: DataFrame with columns [animal_id, group, time, volume]\n        control_group: name of control group\n    \"\"\"\n    results = []\n    final_time = max(time_points)\n\n    # Control group growth\n    ctrl = tumor_volumes[tumor_volumes['group'] == control_group]\n    ctrl_initial = ctrl[ctrl['time'] == 0]['volume'].mean()\n    ctrl_final = ctrl[ctrl['time'] == final_time]['volume'].mean()\n    ctrl_growth = ctrl_final - ctrl_initial\n\n    for group in tumor_volumes['group'].unique():\n        if group == control_group:\n            continue\n\n        treat = tumor_volumes[tumor_volumes['group'] == group]\n        treat_initial = treat[treat['time'] == 0]['volume'].mean()\n        treat_final = treat[treat['time'] == final_time]['volume'].mean()\n        treat_growth = treat_final - treat_initial\n\n        tgi = (1 - treat_growth \u002F ctrl_growth) * 100\n\n        # Statistical comparison at final timepoint\n        ctrl_final_vals = ctrl[ctrl['time'] == final_time]['volume'].values\n        treat_final_vals = treat[treat['time'] == final_time]['volume'].values\n        stat, pval = stats.mannwhitneyu(ctrl_final_vals, treat_final_vals,\n                                         alternative='two-sided')\n\n        results.append({\n            'group': group,\n            'TGI%': tgi,\n            'mean_volume_final': treat_final,\n            'control_volume_final': ctrl_final,\n            'p_value': pval,\n            'n_animals': len(treat['animal_id'].unique())\n        })\n\n    df = pd.DataFrame(results)\n    print(\"Tumor Growth Inhibition:\")\n    print(df[['group', 'TGI%', 'mean_volume_final', 'p_value']])\n    return df\n\ndef growth_delay(tumor_volumes, groups, doubling_volume):\n    \"\"\"Calculate tumor growth delay (time to reach doubling volume).\n\n    Args:\n        doubling_volume: target volume for comparison (e.g., 2x initial)\n    \"\"\"\n    results = []\n    for group in tumor_volumes['group'].unique():\n        grp = tumor_volumes[tumor_volumes['group'] == group]\n        times_to_double = []\n\n        for animal in grp['animal_id'].unique():\n            animal_data = grp[grp['animal_id'] == animal].sort_values('time')\n            reached = animal_data[animal_data['volume'] >= doubling_volume]\n            if len(reached) > 0:\n                times_to_double.append(reached.iloc[0]['time'])\n\n        if times_to_double:\n            results.append({\n                'group': group,\n                'median_time_to_double': np.median(times_to_double),\n                'mean_time_to_double': np.mean(times_to_double),\n                'n_reached': len(times_to_double)\n            })\n\n    return pd.DataFrame(results)\n```\n\n### 3. Pharmaceutical Stability\n\nPredict shelf life from accelerated stability data.\n\n```python\nimport numpy as np\nfrom scipy.optimize import curve_fit\n\ndef arrhenius_stability(temperatures_C, rate_constants):\n    \"\"\"Fit Arrhenius equation to predict shelf life.\n\n    k = A * exp(-Ea \u002F RT)\n\n    Args:\n        temperatures_C: storage temperatures in Celsius\n        rate_constants: degradation rate constants at each temperature\n    \"\"\"\n    T_kelvin = np.array(temperatures_C) + 273.15\n    k = np.array(rate_constants)\n\n    # Linearize: ln(k) = ln(A) - Ea\u002F(R*T)\n    ln_k = np.log(k)\n    inv_T = 1 \u002F T_kelvin\n\n    # Linear regression\n    slope, intercept, r_value, p_value, std_err = stats.linregress(inv_T, ln_k)\n\n    R = 8.314  # J\u002F(mol*K)\n    Ea = -slope * R \u002F 1000  # kJ\u002Fmol\n    A = np.exp(intercept)\n\n    print(f\"Activation energy: {Ea:.1f} kJ\u002Fmol\")\n    print(f\"Pre-exponential factor: {A:.4e}\")\n    print(f\"R²: {r_value**2:.4f}\")\n\n    # Predict rate at storage temperature (25°C)\n    T_storage = 25 + 273.15\n    k_25 = A * np.exp(-Ea * 1000 \u002F (R * T_storage))\n\n    # Shelf life (time to 10% degradation, first-order kinetics)\n    # C\u002FC0 = exp(-k*t) → t = -ln(0.9) \u002F k\n    shelf_life_days = -np.log(0.9) \u002F k_25\n    shelf_life_months = shelf_life_days \u002F 30.44\n\n    print(f\"\\nPredicted at 25°C:\")\n    print(f\"  Rate constant: {k_25:.6f} day⁻¹\")\n    print(f\"  Shelf life (to 90%): {shelf_life_months:.0f} months ({shelf_life_days:.0f} days)\")\n\n    return {\n        'Ea_kJ_mol': Ea,\n        'A': A,\n        'k_25C': k_25,\n        'shelf_life_months': shelf_life_months,\n        'shelf_life_days': shelf_life_days\n    }\n\ndef fit_degradation_kinetics(time_points, concentrations, order=1):\n    \"\"\"Fit degradation kinetics (zero, first, or second order).\"\"\"\n    time = np.array(time_points)\n    conc = np.array(concentrations)\n\n    if order == 0:\n        # C = C0 - k*t\n        slope, intercept, r, p, se = stats.linregress(time, conc)\n        k = -slope\n        r_sq = r**2\n    elif order == 1:\n        # ln(C) = ln(C0) - k*t\n        slope, intercept, r, p, se = stats.linregress(time, np.log(conc))\n        k = -slope\n        r_sq = r**2\n    elif order == 2:\n        # 1\u002FC = 1\u002FC0 + k*t\n        slope, intercept, r, p, se = stats.linregress(time, 1\u002Fconc)\n        k = slope\n        r_sq = r**2\n\n    print(f\"Order {order}: k = {k:.6f}, R² = {r_sq:.4f}\")\n    return {'k': k, 'r_squared': r_sq, 'order': order}\n```\n\n### 4. Radiolabeled Antibody Biodistribution\n\nProcess biodistribution data from radiolabeled compounds.\n\n```python\nimport numpy as np\nimport pandas as pd\n\ndef calculate_biodistribution(tissue_counts, tissue_weights_g, injection_dose,\n                               decay_correction_factor=1.0):\n    \"\"\"Calculate %ID\u002Fg from tissue radioactivity counts.\n\n    Args:\n        tissue_counts: dict of tissue_name -> counts per minute (CPM)\n        tissue_weights_g: dict of tissue_name -> weight in grams\n        injection_dose: total injected dose in CPM\n        decay_correction_factor: factor to correct for radioactive decay\n    \"\"\"\n    results = []\n    for tissue in tissue_counts:\n        cpm = tissue_counts[tissue] * decay_correction_factor\n        weight = tissue_weights_g[tissue]\n\n        id_per_g = (cpm \u002F weight) \u002F injection_dose * 100  # %ID\u002Fg\n\n        results.append({\n            'tissue': tissue,\n            'CPM': tissue_counts[tissue],\n            'weight_g': weight,\n            'percent_ID_per_g': id_per_g\n        })\n\n    df = pd.DataFrame(results).sort_values('percent_ID_per_g', ascending=False)\n\n    # Tumor-to-blood ratio (if both present)\n    if 'tumor' in tissue_counts and 'blood' in tissue_counts:\n        tumor_uptake = df[df['tissue'] == 'tumor']['percent_ID_per_g'].values[0]\n        blood_uptake = df[df['tissue'] == 'blood']['percent_ID_per_g'].values[0]\n        ratio = tumor_uptake \u002F blood_uptake if blood_uptake > 0 else float('inf')\n        print(f\"Tumor-to-blood ratio: {ratio:.2f}\")\n\n    print(df[['tissue', 'percent_ID_per_g']].to_string(index=False))\n    return df\n```\n\n### 5. MIRD Dosimetry\n\nEstimate absorbed radiation dose using MIRD formalism.\n\n```python\nimport numpy as np\nfrom scipy.integrate import trapezoid\n\ndef mird_dosimetry(time_points_h, activity_values_MBq, s_values, organ_masses_kg):\n    \"\"\"Estimate absorbed dose using MIRD formalism.\n\n    Dose = cumulative_activity * S_value\n\n    Args:\n        time_points_h: time points in hours\n        activity_values_MBq: dict of organ -> list of activity values at each time\n        s_values: dict of (source, target) -> S-value in mGy\u002F(MBq*h)\n        organ_masses_kg: dict of organ -> mass in kg\n    \"\"\"\n    results = []\n    time = np.array(time_points_h)\n\n    for organ, activities in activity_values_MBq.items():\n        act = np.array(activities)\n\n        # Time-integrated activity (cumulative activity, A_tilde)\n        cumulative_activity = trapezoid(act, time)  # MBq*h\n\n        # Self-dose\n        s_self = s_values.get((organ, organ), 0)\n        dose_self = cumulative_activity * s_self  # mGy\n\n        # Cross-dose from other organs\n        dose_cross = 0\n        for source_organ, source_activities in activity_values_MBq.items():\n            if source_organ != organ:\n                s_cross = s_values.get((source_organ, organ), 0)\n                source_cumulative = trapezoid(np.array(source_activities), time)\n                dose_cross += source_cumulative * s_cross\n\n        total_dose = dose_self + dose_cross\n\n        results.append({\n            'organ': organ,\n            'cumulative_activity_MBq_h': cumulative_activity,\n            'self_dose_mGy': dose_self,\n            'cross_dose_mGy': dose_cross,\n            'total_dose_mGy': total_dose\n        })\n\n    import pandas as pd\n    df = pd.DataFrame(results)\n    print(df[['organ', 'cumulative_activity_MBq_h', 'total_dose_mGy']])\n    return df\n```\n\n### 6. Adverse Event Grading\n\nGrade adverse events against CTCAE v5.0 criteria.\n\n```python\nimport pandas as pd\n\n# CTCAE v5.0 grading examples (subset)\nCTCAE_CRITERIA = {\n    'neutrophil_count': {\n        1: lambda x: 1500 \u003C= x \u003C 2000,  # Grade 1: LLN - 1500\u002Fmm3\n        2: lambda x: 1000 \u003C= x \u003C 1500,  # Grade 2: 1000-1500\n        3: lambda x: 500 \u003C= x \u003C 1000,   # Grade 3: 500-1000\n        4: lambda x: x \u003C 500,           # Grade 4: \u003C500\n    },\n    'platelet_count': {\n        1: lambda x: 75000 \u003C= x \u003C 150000,\n        2: lambda x: 50000 \u003C= x \u003C 75000,\n        3: lambda x: 25000 \u003C= x \u003C 50000,\n        4: lambda x: x \u003C 25000,\n    },\n    'hemoglobin': {\n        1: lambda x: 10 \u003C= x \u003C 12,      # g\u002FdL\n        2: lambda x: 8 \u003C= x \u003C 10,\n        3: lambda x: x \u003C 8,\n        4: lambda x: False,  # Life-threatening\n    },\n    'ALT': {\n        1: lambda x: 1 \u003C= x\u002F40 \u003C 3,     # x ULN (ULN=40)\n        2: lambda x: 3 \u003C= x\u002F40 \u003C 5,\n        3: lambda x: 5 \u003C= x\u002F40 \u003C 20,\n        4: lambda x: x\u002F40 >= 20,\n    }\n}\n\ndef grade_adverse_event(parameter, value):\n    \"\"\"Grade an adverse event by CTCAE v5.0.\"\"\"\n    if parameter not in CTCAE_CRITERIA:\n        return {'grade': None, 'error': f'Unknown parameter: {parameter}'}\n\n    criteria = CTCAE_CRITERIA[parameter]\n    for grade in [4, 3, 2, 1]:  # Check highest grade first\n        if criteria[grade](value):\n            return {'parameter': parameter, 'value': value, 'grade': grade}\n\n    return {'parameter': parameter, 'value': value, 'grade': 0}\n\ndef assess_dlt(adverse_events, dlt_grades={'hematologic': 4, 'non_hematologic': 3}):\n    \"\"\"Assess dose-limiting toxicity from adverse event list.\"\"\"\n    dlts = []\n    for ae in adverse_events:\n        if ae['grade'] >= dlt_grades.get('hematologic', 4):\n            dlts.append(ae)\n    return dlts\n```\n\n### 7. Dose-Response Curves\n\nFit dose-response data for IC50\u002FEC50 determination.\n\n```python\nimport numpy as np\nfrom scipy.optimize import curve_fit\n\ndef four_pl(x, bottom, top, ec50, hill):\n    \"\"\"4-Parameter Logistic dose-response.\"\"\"\n    return bottom + (top - bottom) \u002F (1 + (x \u002F ec50) ** hill)\n\ndef fit_dose_response(concentrations, responses, response_type='inhibition'):\n    \"\"\"Fit dose-response curve and calculate IC50\u002FEC50.\n\n    Args:\n        concentrations: drug concentrations\n        responses: measured responses (viability %, activity, etc.)\n        response_type: 'inhibition' (response decreases) or 'activation'\n    \"\"\"\n    conc = np.array(concentrations)\n    resp = np.array(responses)\n\n    if response_type == 'inhibition':\n        p0 = [resp.min(), resp.max(), np.median(conc), 1.0]\n    else:\n        p0 = [resp.min(), resp.max(), np.median(conc), 1.0]\n\n    bounds = ([0, 0, 0, 0.1], [200, 200, conc.max() * 10, 10])\n\n    popt, pcov = curve_fit(four_pl, conc, resp, p0=p0, bounds=bounds, maxfev=10000)\n    perr = np.sqrt(np.diag(pcov))\n\n    bottom, top, ec50, hill = popt\n\n    # R-squared\n    predicted = four_pl(conc, *popt)\n    ss_res = np.sum((resp - predicted) ** 2)\n    ss_tot = np.sum((resp - np.mean(resp)) ** 2)\n    r_squared = 1 - ss_res \u002F ss_tot\n\n    label = 'IC50' if response_type == 'inhibition' else 'EC50'\n    print(f\"{label}: {ec50:.4f} ± {perr[2]:.4f}\")\n    print(f\"Hill coefficient: {hill:.2f}\")\n    print(f\"Bottom: {bottom:.1f}%, Top: {top:.1f}%\")\n    print(f\"R²: {r_squared:.4f}\")\n\n    return {label: ec50, 'hill': hill, 'bottom': bottom, 'top': top,\n            'r_squared': r_squared, 'params': popt}\n\ndef combination_index(drug1_ic50, drug2_ic50, combo_ic50_d1, combo_ic50_d2):\n    \"\"\"Calculate Chou-Talalay Combination Index.\n\n    CI \u003C 1: synergistic, CI = 1: additive, CI > 1: antagonistic\n    \"\"\"\n    ci = (combo_ic50_d1 \u002F drug1_ic50) + (combo_ic50_d2 \u002F drug2_ic50)\n    interpretation = 'synergistic' if ci \u003C 0.9 else ('antagonistic' if ci > 1.1 else 'additive')\n    print(f\"CI = {ci:.3f} ({interpretation})\")\n    return {'CI': ci, 'interpretation': interpretation}\n```\n\n## Typical Workflows\n\n### Workflow 1: Quantify Western Blot Bands and Calculate Fold Change\n\n```python\n# Target protein bands\ntarget = [1500, 3200, 4800, 2100]  # Arbitrary units per lane\nactin = [2000, 1950, 2100, 1900]   # Loading control\n\nfc = calculate_fold_change(target, actin)\nfor i, f in enumerate(fc):\n    print(f\"Lane {i+1}: fold change = {f:.2f}\")\n```\n\n### Workflow 2: Analyze Xenograft Tumor Growth and Calculate TGI%\n\n```python\nimport pandas as pd\n\ndata = pd.DataFrame({\n    'animal_id': ['V1','V1','V2','V2','T1','T1','T2','T2'],\n    'group': ['Vehicle','Vehicle','Vehicle','Vehicle','Drug','Drug','Drug','Drug'],\n    'time': [0, 14, 0, 14, 0, 14, 0, 14],\n    'volume': [100, 800, 120, 750, 110, 350, 105, 380]\n})\n\ntgi = calculate_tgi(data, data['group'].unique(), [0, 14])\n```\n\n### Workflow 3: Predict Shelf Life from Accelerated Stability Data\n\n```python\nfrom scipy import stats\n\ntemps = [40, 50, 60, 70]  # Celsius\nrates = [0.001, 0.003, 0.01, 0.03]  # day^-1\n\nresult = arrhenius_stability(temps, rates)\nprint(f\"Predicted shelf life at 25°C: {result['shelf_life_months']:.0f} months\")\n```\n\n## Best Practices\n\n1. **Western blots** — always normalize to loading control (beta-actin, GAPDH, total protein); report fold change, not raw intensity\n2. **Xenograft TGI** — require minimum 8-10 animals per group; report both TGI% and statistical significance; use caliper measurements consistently\n3. **Stability studies** — use at least 3 temperatures for Arrhenius fitting; verify linearity of ln(k) vs 1\u002FT; ICH guidelines require 25°C\u002F60%RH long-term\n4. **Dose-response** — use at least 6 concentrations spanning 3 log units; include 0 and saturating dose; fit with Hill slope unconstrained\n5. **Combination index** — measure IC50 of each drug alone and in fixed-ratio combination; CI only valid at Fa (fraction affected) near 0.5\n6. **Adverse events** — use CTCAE v5.0 for human clinical trials; VCOG-CTCAE for veterinary studies\n\n## Troubleshooting\n\n**Problem:** Dose-response fit gives unreasonable IC50\n**Solution:** Ensure concentrations span the IC50 (responses should range from ~10% to ~90% effect). Add more points around the inflection. Check if response truly follows sigmoidal pattern.\n\n**Problem:** Western blot quantification inconsistent\n**Solution:** Ensure uniform exposure across lanes. Use ECL with linear dynamic range. Avoid saturated bands. Use total protein stain (Ponceau S) instead of housekeeping gene for normalization.\n\n**Problem:** Arrhenius fit has low R²\n**Solution:** Verify degradation follows the assumed kinetic order. Check for multiple degradation mechanisms at different temperatures. Exclude temperatures where phase transitions occur.\n\n**Problem:** TGI% is negative\n**Solution:** Treatment group grew faster than control. Verify group assignments. Check for handling errors. This can occur with growth factors or hormones.\n\n## Resources\n\n- [CTCAE v5.0 Grading Criteria](https:\u002F\u002Fctep.cancer.gov\u002Fprotocoldevelopment\u002Felectronic_applications\u002Fctc.htm)\n- [ICH Stability Guidelines (Q1A-Q1E)](https:\u002F\u002Fwww.ich.org\u002Fpage\u002Fquality-guidelines)\n- [Chou-Talalay Method](https:\u002F\u002Fdoi.org\u002F10.1124\u002Fpharmrev.58.3.621)\n- [MIRD Primer](https:\u002F\u002Fdoi.org\u002F10.2967\u002Fjnumed.107.046300)\n","# Pharmacology Wet-Lab: Experimental Data Analysis\n\n## Overview\n\nPharmacology Wet-Lab provides computational tools for analyzing data from pharmacology experiments. This skill covers western blot densitometry and quantification, xenograft tumor growth inhibition analysis, pharmaceutical stability modeling using Arrhenius kinetics, radiolabeled antibody biodistribution calculations, MIRD-based dosimetry, adverse event grading against CTCAE criteria, and dose-response curve fitting for IC50\u002FEC50 determination.\n\n## When to Use This Skill\n\n- Quantifying protein expression from western blot images\n- Analyzing xenograft tumor growth data and calculating TGI%\n- Predicting pharmaceutical shelf life from accelerated stability data\n- Processing radiolabeled antibody biodistribution data (%ID\u002Fg)\n- Estimating absorbed radiation doses (MIRD dosimetry)\n- Grading adverse events against CTCAE or VCOG-CTCAE scales\n- Fitting dose-response curves for IC50\u002FEC50 determination\n- Calculating combination indices (Chou-Talalay method)\n\n**Related Skills:** For drug database queries use `chembl-database` or `fda-database`. For molecular docking use `diffdock`. For survival analysis use `scikit-survival`.\n\n## Installation\n\n```bash\nuv pip install opencv-python scipy pandas numpy matplotlib lifelines\n```\n\n## Quick Start\n\n```python\nimport numpy as np\nfrom scipy.optimize import curve_fit\n\n# 4-Parameter Logistic for dose-response (IC50)\ndef four_pl(x, bottom, top, ic50, hill):\n    return bottom + (top - bottom) \u002F (1 + (x \u002F ic50) ** hill)\n\nconcentrations = np.array([0.001, 0.01, 0.1, 1, 10, 100])  # uM\nviability = np.array([98, 95, 82, 45, 12, 3])  # % viability\n\npopt, pcov = curve_fit(four_pl, concentrations, viability,\n                       p0=[0, 100, 1, 1], maxfev=10000)\nprint(f\"IC50: {popt[2]:.3f} uM\")\nprint(f\"Hill coefficient: {popt[3]:.2f}\")\n```\n\n## Core Capabilities\n\n### 1. Western Blot Densitometry\n\nQuantify protein bands from western blot images.\n\n```python\nimport cv2\nimport numpy as np\n\ndef quantify_western_blot(image_path, n_lanes, band_height=50):\n    \"\"\"Quantify western blot band intensities.\n\n    Args:\n        image_path: path to blot image\n        n_lanes: number of lanes\n        band_height: expected band height in pixels\n    \"\"\"\n    image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)\n    if image is None:\n        raise FileNotFoundError(f\"Cannot load {image_path}\")\n\n    # Invert (bands are dark on light background)\n    inverted = 255 - image\n    h, w = inverted.shape\n\n    # Divide into lanes\n    lane_width = w \u002F\u002F n_lanes\n    lane_intensities = []\n\n    for i in range(n_lanes):\n        x_start = i * lane_width + int(lane_width * 0.1)\n        x_end = (i + 1) * lane_width - int(lane_width * 0.1)\n        lane = inverted[:, x_start:x_end]\n\n        # Find band (peak in vertical intensity profile)\n        profile = lane.mean(axis=1)\n\n        # Background subtraction (rolling ball approximation)\n        from scipy.ndimage import minimum_filter1d\n        background = minimum_filter1d(profile, size=100)\n        corrected = profile - background\n        corrected = np.clip(corrected, 0, None)\n\n        # Band detection\n        from scipy.signal import find_peaks\n        peaks, props = find_peaks(corrected, height=corrected.max()*0.1,\n                                   distance=band_height)\n\n        # Integrate band intensity (area under curve)\n        total_intensity = 0\n        for peak in peaks:\n            start = max(0, peak - band_height \u002F\u002F 2)\n            end = min(len(corrected), peak + band_height \u002F\u002F 2)\n            band_area = corrected[start:end].sum()\n            total_intensity += band_area\n\n        lane_intensities.append({\n            'lane': i + 1,\n            'raw_intensity': total_intensity,\n            'n_bands': len(peaks),\n            'peak_positions': list(peaks)\n        })\n\n    # Normalize to loading control (first lane or specified)\n    import pandas as pd\n    df = pd.DataFrame(lane_intensities)\n    control_intensity = df.iloc[0]['raw_intensity']\n    df['normalized'] = df['raw_intensity'] \u002F control_intensity\n    df['fold_change'] = df['normalized']\n\n    print(\"Lane intensities:\")\n    print(df[['lane', 'raw_intensity', 'normalized', 'fold_change']])\n\n    return df\n\ndef calculate_fold_change(target_intensities, loading_control_intensities):\n    \"\"\"Calculate normalized fold change with loading control.\n\n    Args:\n        target_intensities: list of target protein band intensities\n        loading_control_intensities: list of loading control (e.g., actin) intensities\n    \"\"\"\n    target = np.array(target_intensities)\n    control = np.array(loading_control_intensities)\n\n    # Normalize target to loading control\n    normalized = target \u002F control\n\n    # Fold change relative to first sample\n    fold_change = normalized \u002F normalized[0]\n\n    return fold_change\n```\n\n### 2. Xenograft Tumor Growth Analysis\n\nAnalyze in vivo tumor growth and treatment efficacy.\n\n```python\nimport numpy as np\nimport pandas as pd\nfrom scipy import stats\n\ndef calculate_tgi(tumor_volumes, groups, time_points, control_group='Vehicle'):\n    \"\"\"Calculate Tumor Growth Inhibition (TGI%).\n\n    TGI% = (1 - (Tt - T0) \u002F (Ct - C0)) * 100\n    where T=treatment, C=control, t=final, 0=initial\n\n    Args:\n        tumor_volumes: DataFrame with columns [animal_id, group, time, volume]\n        control_group: name of control group\n    \"\"\"\n    results = []\n    final_time = max(time_points)\n\n    # Control group growth\n    ctrl = tumor_volumes[tumor_volumes['group'] == control_group]\n    ctrl_initial = ctrl[ctrl['time'] == 0]['volume'].mean()\n    ctrl_final = ctrl[ctrl['time'] == final_time]['volume'].mean()\n    ctrl_growth = ctrl_final - ctrl_initial\n\n    for group in tumor_volumes['group'].unique():\n        if group == control_group:\n            continue\n\n        treat = tumor_volumes[tumor_volumes['group'] == group]\n        treat_initial = treat[treat['time'] == 0]['volume'].mean()\n        treat_final = treat[treat['time'] == final_time]['volume'].mean()\n        treat_growth = treat_final - treat_initial\n\n        tgi = (1 - treat_growth \u002F ctrl_growth) * 100\n\n        # Statistical comparison at final timepoint\n        ctrl_final_vals = ctrl[ctrl['time'] == final_time]['volume'].values\n        treat_final_vals = treat[treat['time'] == final_time]['volume'].values\n        stat, pval = stats.mannwhitneyu(ctrl_final_vals, treat_final_vals,\n                                         alternative='two-sided')\n\n        results.append({\n            'group': group,\n            'TGI%': tgi,\n            'mean_volume_final': treat_final,\n            'control_volume_final': ctrl_final,\n            'p_value': pval,\n            'n_animals': len(treat['animal_id'].unique())\n        })\n\n    df = pd.DataFrame(results)\n    print(\"Tumor Growth Inhibition:\")\n    print(df[['group', 'TGI%', 'mean_volume_final', 'p_value']])\n    return df\n\ndef growth_delay(tumor_volumes, groups, doubling_volume):\n    \"\"\"Calculate tumor growth delay (time to reach doubling volume).\n\n    Args:\n        doubling_volume: target volume for comparison (e.g., 2x initial)\n    \"\"\"\n    results = []\n    for group in tumor_volumes['group'].unique():\n        grp = tumor_volumes[tumor_volumes['group'] == group]\n        times_to_double = []\n\n        for animal in grp['animal_id'].unique():\n            animal_data = grp[grp['animal_id'] == animal].sort_values('time')\n            reached = animal_data[animal_data['volume'] >= doubling_volume]\n            if len(reached) > 0:\n                times_to_double.append(reached.iloc[0]['time'])\n\n        if times_to_double:\n            results.append({\n                'group': group,\n                'median_time_to_double': np.median(times_to_double),\n                'mean_time_to_double': np.mean(times_to_double),\n                'n_reached': len(times_to_double)\n            })\n\n    return pd.DataFrame(results)\n```\n\n### 3. Pharmaceutical Stability\n\nPredict shelf life from accelerated stability data.\n\n```python\nimport numpy as np\nfrom scipy.optimize import curve_fit\n\ndef arrhenius_stability(temperatures_C, rate_constants):\n    \"\"\"Fit Arrhenius equation to predict shelf life.\n\n    k = A * exp(-Ea \u002F RT)\n\n    Args:\n        temperatures_C: storage temperatures in Celsius\n        rate_constants: degradation rate constants at each temperature\n    \"\"\"\n    T_kelvin = np.array(temperatures_C) + 273.15\n    k = np.array(rate_constants)\n\n    # Linearize: ln(k) = ln(A) - Ea\u002F(R*T)\n    ln_k = np.log(k)\n    inv_T = 1 \u002F T_kelvin\n\n    # Linear regression\n    slope, intercept, r_value, p_value, std_err = stats.linregress(inv_T, ln_k)\n\n    R = 8.314  # J\u002F(mol*K)\n    Ea = -slope * R \u002F 1000  # kJ\u002Fmol\n    A = np.exp(intercept)\n\n    print(f\"Activation energy: {Ea:.1f} kJ\u002Fmol\")\n    print(f\"Pre-exponential factor: {A:.4e}\")\n    print(f\"R²: {r_value**2:.4f}\")\n\n    # Predict rate at storage temperature (25°C)\n    T_storage = 25 + 273.15\n    k_25 = A * np.exp(-Ea * 1000 \u002F (R * T_storage))\n\n    # Shelf life (time to 10% degradation, first-order kinetics)\n    # C\u002FC0 = exp(-k*t) → t = -ln(0.9) \u002F k\n    shelf_life_days = -np.log(0.9) \u002F k_25\n    shelf_life_months = shelf_life_days \u002F 30.44\n\n    print(f\"\\nPredicted at 25°C:\")\n    print(f\"  Rate constant: {k_25:.6f} day⁻¹\")\n    print(f\"  Shelf life (to 90%): {shelf_life_months:.0f} months ({shelf_life_days:.0f} days)\")\n\n    return {\n        'Ea_kJ_mol': Ea,\n        'A': A,\n        'k_25C': k_25,\n        'shelf_life_months': shelf_life_months,\n        'shelf_life_days': shelf_life_days\n    }\n\ndef fit_degradation_kinetics(time_points, concentrations, order=1):\n    \"\"\"Fit degradation kinetics (zero, first, or second order).\"\"\"\n    time = np.array(time_points)\n    conc = np.array(concentrations)\n\n    if order == 0:\n        # C = C0 - k*t\n        slope, intercept, r, p, se = stats.linregress(time, conc)\n        k = -slope\n        r_sq = r**2\n    elif order == 1:\n        # ln(C) = ln(C0) - k*t\n        slope, intercept, r, p, se = stats.linregress(time, np.log(conc))\n        k = -slope\n        r_sq = r**2\n    elif order == 2:\n        # 1\u002FC = 1\u002FC0 + k*t\n        slope, intercept, r, p, se = stats.linregress(time, 1\u002Fconc)\n        k = slope\n        r_sq = r**2\n\n    print(f\"Order {order}: k = {k:.6f}, R² = {r_sq:.4f}\")\n    return {'k': k, 'r_squared': r_sq, 'order': order}\n```\n\n### 4. Radiolabeled Antibody Biodistribution\n\nProcess biodistribution data from radiolabeled compounds.\n\n```python\nimport numpy as np\nimport pandas as pd\n\ndef calculate_biodistribution(tissue_counts, tissue_weights_g, injection_dose,\n                               decay_correction_factor=1.0):\n    \"\"\"Calculate %ID\u002Fg from tissue radioactivity counts.\n\n    Args:\n        tissue_counts: dict of tissue_name -> counts per minute (CPM)\n        tissue_weights_g: dict of tissue_name -> weight in grams\n        injection_dose: total injected dose in CPM\n        decay_correction_factor: factor to correct for radioactive decay\n    \"\"\"\n    results = []\n    for tissue in tissue_counts:\n        cpm = tissue_counts[tissue] * decay_correction_factor\n        weight = tissue_weights_g[tissue]\n\n        id_per_g = (cpm \u002F weight) \u002F injection_dose * 100  # %ID\u002Fg\n\n        results.append({\n            'tissue': tissue,\n            'CPM': tissue_counts[tissue],\n            'weight_g': weight,\n            'percent_ID_per_g': id_per_g\n        })\n\n    df = pd.DataFrame(results).sort_values('percent_ID_per_g', ascending=False)\n\n    # Tumor-to-blood ratio (if both present)\n    if 'tumor' in tissue_counts and 'blood' in tissue_counts:\n        tumor_uptake = df[df['tissue'] == 'tumor']['percent_ID_per_g'].values[0]\n        blood_uptake = df[df['tissue'] == 'blood']['percent_ID_per_g'].values[0]\n        ratio = tumor_uptake \u002F blood_uptake if blood_uptake > 0 else float('inf')\n        print(f\"Tumor-to-blood ratio: {ratio:.2f}\")\n\n    print(df[['tissue', 'percent_ID_per_g']].to_string(index=False))\n    return df\n```\n\n### 5. MIRD Dosimetry\n\nEstimate absorbed radiation dose using MIRD formalism.\n\n```python\nimport numpy as np\nfrom scipy.integrate import trapezoid\n\ndef mird_dosimetry(time_points_h, activity_values_MBq, s_values, organ_masses_kg):\n    \"\"\"Estimate absorbed dose using MIRD formalism.\n\n    Dose = cumulative_activity * S_value\n\n    Args:\n        time_points_h: time points in hours\n        activity_values_MBq: dict of organ -> list of activity values at each time\n        s_values: dict of (source, target) -> S-value in mGy\u002F(MBq*h)\n        organ_masses_kg: dict of organ -> mass in kg\n    \"\"\"\n    results = []\n    time = np.array(time_points_h)\n\n    for organ, activities in activity_values_MBq.items():\n        act = np.array(activities)\n\n        # Time-integrated activity (cumulative activity, A_tilde)\n        cumulative_activity = trapezoid(act, time)  # MBq*h\n\n        # Self-dose\n        s_self = s_values.get((organ, organ), 0)\n        dose_self = cumulative_activity * s_self  # mGy\n\n        # Cross-dose from other organs\n        dose_cross = 0\n        for source_organ, source_activities in activity_values_MBq.items():\n            if source_organ != organ:\n                s_cross = s_values.get((source_organ, organ), 0)\n                source_cumulative = trapezoid(np.array(source_activities), time)\n                dose_cross += source_cumulative * s_cross\n\n        total_dose = dose_self + dose_cross\n\n        results.append({\n            'organ': organ,\n            'cumulative_activity_MBq_h': cumulative_activity,\n            'self_dose_mGy': dose_self,\n            'cross_dose_mGy': dose_cross,\n            'total_dose_mGy': total_dose\n        })\n\n    import pandas as pd\n    df = pd.DataFrame(results)\n    print(df[['organ', 'cumulative_activity_MBq_h', 'total_dose_mGy']])\n    return df\n```\n\n### 6. Adverse Event Grading\n\nGrade adverse events against CTCAE v5.0 criteria.\n\n```python\nimport pandas as pd\n\n# CTCAE v5.0 grading examples (subset)\nCTCAE_CRITERIA = {\n    'neutrophil_count': {\n        1: lambda x: 1500 \u003C= x \u003C 2000,  # Grade 1: LLN - 1500\u002Fmm3\n        2: lambda x: 1000 \u003C= x \u003C 1500,  # Grade 2: 1000-1500\n        3: lambda x: 500 \u003C= x \u003C 1000,   # Grade 3: 500-1000\n        4: lambda x: x \u003C 500,           # Grade 4: \u003C500\n    },\n    'platelet_count': {\n        1: lambda x: 75000 \u003C= x \u003C 150000,\n        2: lambda x: 50000 \u003C= x \u003C 75000,\n        3: lambda x: 25000 \u003C= x \u003C 50000,\n        4: lambda x: x \u003C 25000,\n    },\n    'hemoglobin': {\n        1: lambda x: 10 \u003C= x \u003C 12,      # g\u002FdL\n        2: lambda x: 8 \u003C= x \u003C 10,\n        3: lambda x: x \u003C 8,\n        4: lambda x: False,  # Life-threatening\n    },\n    'ALT': {\n        1: lambda x: 1 \u003C= x\u002F40 \u003C 3,     # x ULN (ULN=40)\n        2: lambda x: 3 \u003C= x\u002F40 \u003C 5,\n        3: lambda x: 5 \u003C= x\u002F40 \u003C 20,\n        4: lambda x: x\u002F40 >= 20,\n    }\n}\n\ndef grade_adverse_event(parameter, value):\n    \"\"\"Grade an adverse event by CTCAE v5.0.\"\"\"\n    if parameter not in CTCAE_CRITERIA:\n        return {'grade': None, 'error': f'Unknown parameter: {parameter}'}\n\n    criteria = CTCAE_CRITERIA[parameter]\n    for grade in [4, 3, 2, 1]:  # Check highest grade first\n        if criteria[grade](value):\n            return {'parameter': parameter, 'value': value, 'grade': grade}\n\n    return {'parameter': parameter, 'value': value, 'grade': 0}\n\ndef assess_dlt(adverse_events, dlt_grades={'hematologic': 4, 'non_hematologic': 3}):\n    \"\"\"Assess dose-limiting toxicity from adverse event list.\"\"\"\n    dlts = []\n    for ae in adverse_events:\n        if ae['grade'] >= dlt_grades.get('hematologic', 4):\n            dlts.append(ae)\n    return dlts\n```\n\n### 7. Dose-Response Curves\n\nFit dose-response data for IC50\u002FEC50 determination.\n\n```python\nimport numpy as np\nfrom scipy.optimize import curve_fit\n\ndef four_pl(x, bottom, top, ec50, hill):\n    \"\"\"4-Parameter Logistic dose-response.\"\"\"\n    return bottom + (top - bottom) \u002F (1 + (x \u002F ec50) ** hill)\n\ndef fit_dose_response(concentrations, responses, response_type='inhibition'):\n    \"\"\"Fit dose-response curve and calculate IC50\u002FEC50.\n\n    Args:\n        concentrations: drug concentrations\n        responses: measured responses (viability %, activity, etc.)\n        response_type: 'inhibition' (response decreases) or 'activation'\n    \"\"\"\n    conc = np.array(concentrations)\n    resp = np.array(responses)\n\n    if response_type == 'inhibition':\n        p0 = [resp.min(), resp.max(), np.median(conc), 1.0]\n    else:\n        p0 = [resp.min(), resp.max(), np.median(conc), 1.0]\n\n    bounds = ([0, 0, 0, 0.1], [200, 200, conc.max() * 10, 10])\n\n    popt, pcov = curve_fit(four_pl, conc, resp, p0=p0, bounds=bounds, maxfev=10000)\n    perr = np.sqrt(np.diag(pcov))\n\n    bottom, top, ec50, hill = popt\n\n    # R-squared\n    predicted = four_pl(conc, *popt)\n    ss_res = np.sum((resp - predicted) ** 2)\n    ss_tot = np.sum((resp - np.mean(resp)) ** 2)\n    r_squared = 1 - ss_res \u002F ss_tot\n\n    label = 'IC50' if response_type == 'inhibition' else 'EC50'\n    print(f\"{label}: {ec50:.4f} ± {perr[2]:.4f}\")\n    print(f\"Hill coefficient: {hill:.2f}\")\n    print(f\"Bottom: {bottom:.1f}%, Top: {top:.1f}%\")\n    print(f\"R²: {r_squared:.4f}\")\n\n    return {label: ec50, 'hill': hill, 'bottom': bottom, 'top': top,\n            'r_squared': r_squared, 'params': popt}\n\ndef combination_index(drug1_ic50, drug2_ic50, combo_ic50_d1, combo_ic50_d2):\n    \"\"\"Calculate Chou-Talalay Combination Index.\n\n    CI \u003C 1: synergistic, CI = 1: additive, CI > 1: antagonistic\n    \"\"\"\n    ci = (combo_ic50_d1 \u002F drug1_ic50) + (combo_ic50_d2 \u002F drug2_ic50)\n    interpretation = 'synergistic' if ci \u003C 0.9 else ('antagonistic' if ci > 1.1 else 'additive')\n    print(f\"CI = {ci:.3f} ({interpretation})\")\n    return {'CI': ci, 'interpretation': interpretation}\n```\n\n## Typical Workflows\n\n### Workflow 1: Quantify Western Blot Bands and Calculate Fold Change\n\n```python\n# Target protein bands\ntarget = [1500, 3200, 4800, 2100]  # Arbitrary units per lane\nactin = [2000, 1950, 2100, 1900]   # Loading control\n\nfc = calculate_fold_change(target, actin)\nfor i, f in enumerate(fc):\n    print(f\"Lane {i+1}: fold change = {f:.2f}\")\n```\n\n### Workflow 2: Analyze Xenograft Tumor Growth and Calculate TGI%\n\n```python\nimport pandas as pd\n\ndata = pd.DataFrame({\n    'animal_id': ['V1','V1','V2','V2','T1','T1','T2','T2'],\n    'group': ['Vehicle','Vehicle','Vehicle','Vehicle','Drug','Drug','Drug','Drug'],\n    'time': [0, 14, 0, 14, 0, 14, 0, 14],\n    'volume': [100, 800, 120, 750, 110, 350, 105, 380]\n})\n\ntgi = calculate_tgi(data, data['group'].unique(), [0, 14])\n```\n\n### Workflow 3: Predict Shelf Life from Accelerated Stability Data\n\n```python\nfrom scipy import stats\n\ntemps = [40, 50, 60, 70]  # Celsius\nrates = [0.001, 0.003, 0.01, 0.03]  # day^-1\n\nresult = arrhenius_stability(temps, rates)\nprint(f\"Predicted shelf life at 25°C: {result['shelf_life_months']:.0f} months\")\n```\n\n## Best Practices\n\n1. **Western blots** — always normalize to loading control (beta-actin, GAPDH, total protein); report fold change, not raw intensity\n2. **Xenograft TGI** — require minimum 8-10 animals per group; report both TGI% and statistical significance; use caliper measurements consistently\n3. **Stability studies** — use at least 3 temperatures for Arrhenius fitting; verify linearity of ln(k) vs 1\u002FT; ICH guidelines require 25°C\u002F60%RH long-term\n4. **Dose-response** — use at least 6 concentrations spanning 3 log units; include 0 and saturating dose; fit with Hill slope unconstrained\n5. **Combination index** — measure IC50 of each drug alone and in fixed-ratio combination; CI only valid at Fa (fraction affected) near 0.5\n6. **Adverse events** — use CTCAE v5.0 for human clinical trials; VCOG-CTCAE for veterinary studies\n\n## Troubleshooting\n\n**Problem:** Dose-response fit gives unreasonable IC50\n**Solution:** Ensure concentrations span the IC50 (responses should range from ~10% to ~90% effect). Add more points around the inflection. Check if response truly follows sigmoidal pattern.\n\n**Problem:** Western blot quantification inconsistent\n**Solution:** Ensure uniform exposure across lanes. Use ECL with linear dynamic range. Avoid saturated bands. Use total protein stain (Ponceau S) instead of housekeeping gene for normalization.\n\n**Problem:** Arrhenius fit has low R²\n**Solution:** Verify degradation follows the assumed kinetic order. Check for multiple degradation mechanisms at different temperatures. Exclude temperatures where phase transitions occur.\n\n**Problem:** TGI% is negative\n**Solution:** Treatment group grew faster than control. Verify group assignments. Check for handling errors. This can occur with growth factors or hormones.\n\n## Resources\n\n- [CTCAE v5.0 Grading Criteria](https:\u002F\u002Fctep.cancer.gov\u002Fprotocoldevelopment\u002Felectronic_applications\u002Fctc.htm)\n- [ICH Stability Guidelines (Q1A-Q1E)](https:\u002F\u002Fwww.ich.org\u002Fpage\u002Fquality-guidelines)\n- [Chou-Talalay Method](https:\u002F\u002Fdoi.org\u002F10.1124\u002Fpharmrev.58.3.621)\n- [MIRD Primer](https:\u002F\u002Fdoi.org\u002F10.2967\u002Fjnumed.107.046300)\n",{"name":5,"license":44,"category":25,"metadata":45,"description":7},"MIT license",{"skill-author":46},"InkVell Inc.","main","4aa39efd1cdab0b4657ecee16c92a92011a5b10f","openscience\u002Fbackend\u002Fcli\u002Fskills\u002Fbiology\u002Fpharmacology-wetlab","https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub","https:\u002F\u002Fgithub.com\u002Ffadrienne","2026-10-28 00:43:09",{"source":8,"skillName":5,"commands":54,"prompt":64,"zipAvailable":65},[55,58,61],{"tool":56,"command":57},"npx","npx skills add fadrienne\u002FMyGitHub --skill pharmacology-wetlab",{"tool":59,"command":60},"bunx","bunx skills add fadrienne\u002FMyGitHub --skill pharmacology-wetlab",{"tool":62,"command":63},"pnpm","pnpm dlx skills add fadrienne\u002FMyGitHub --skill pharmacology-wetlab","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fopenscience\u002Fbackend\u002Fcli\u002Fskills\u002Fbiology\u002Fpharmacology-wetlab\u002FSKILL.md",true,[67,73,77,81,85],{"path":68,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":71,"sourceHtmlUrl":72},"scripts\u002Fbiodistribution.py","SCRIPT","脚本",10706,"https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fopenscience\u002Fbackend\u002Fcli\u002Fskills\u002Fbiology\u002Fpharmacology-wetlab\u002Fscripts\u002Fbiodistribution.py",{"path":74,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":75,"sourceHtmlUrl":76},"scripts\u002Fdose_response.py",10785,"https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fopenscience\u002Fbackend\u002Fcli\u002Fskills\u002Fbiology\u002Fpharmacology-wetlab\u002Fscripts\u002Fdose_response.py",{"path":78,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":79,"sourceHtmlUrl":80},"scripts\u002Fstability_prediction.py",13203,"https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fopenscience\u002Fbackend\u002Fcli\u002Fskills\u002Fbiology\u002Fpharmacology-wetlab\u002Fscripts\u002Fstability_prediction.py",{"path":82,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":83,"sourceHtmlUrl":84},"scripts\u002Fwestern_blot.py",10138,"https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fopenscience\u002Fbackend\u002Fcli\u002Fskills\u002Fbiology\u002Fpharmacology-wetlab\u002Fscripts\u002Fwestern_blot.py",{"path":86,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":87,"sourceHtmlUrl":88},"scripts\u002Fxenograft_tgi.py",9645,"https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fopenscience\u002Fbackend\u002Fcli\u002Fskills\u002Fbiology\u002Fpharmacology-wetlab\u002Fscripts\u002Fxenograft_tgi.py",[90,121,146,180,207,234],{"skillNo":91,"name":92,"nameZh":6,"description":93,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":94,"categories":95,"keywords":101,"publishedAt":39},"01M7167B28PK20HP7R7ZED2DY0","statistical-power","Sample size calculation, power analysis, and effect size conversion for scientific studies. Use BEFORE data collection (a priori) to determine required N, or after (post-hoc) to compute achieved power. Covers parametric tests (t-test, ANOVA, chi-square, proportions, correlation, regression), simulation-based power for complex\u002Fclustered\u002Fnested designs, and power curves. Use this skill when someone asks 'how many samples do I need', 'what is my power', 'what effect size can I detect', or any variant. Pairs with \u002Fexperimental-design (choose design first, then compute power) and \u002Fstatistical-analysis (analyze data after collection).","https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fscientific-agent-skills\u002Fskills\u002Fstatistical-power\u002FSKILL.md",[96],{"categoryNo":97,"categoryName":98,"taxonomyType":99,"taxonomyTypeDesc":100},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[102,103,104,105,106,107,108,109,110,111,112,35,113,114,115,116,117,118,119,120],"power","statistical power","effect","scipy","regression","choose","design","uv","when","Bash","collection","calculation","ANOVA","conversion","analyze","t-test","chi-square","simulation","sample",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":130,"publishedAt":39},"01M71677EBCM8EPCE9BZCK8505","statistical-analysis","Guided statistical analysis with test selection and reporting. Use when you need help choosing appropriate tests for your data, assumption checking, power analysis, and APA-formatted results. Best for academic research reporting, test selection guidance. For implementing specific models programmatically use statsmodels.","https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fscientific-agent-skills\u002Fskills\u002Fstatistical-analysis\u002FSKILL.md",[127],{"categoryNo":128,"categoryName":129,"taxonomyType":99,"taxonomyTypeDesc":100},"01JBSEEDCAT000000000000008","测试与质量",[35,131,102,132,133,134,110,135,136,137,22,138,33,139,140,141,142,143,144,145],"statistical analysis","academic research","implementing","selection","you","reporting","data","tests","with","test","research","results","statsmodels","models","help",{"skillNo":147,"name":148,"nameZh":6,"description":149,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":150,"categories":151,"keywords":161,"publishedAt":39},"01M71673R93XAHFH81DBNVNG7H","scientific-writing","Core skill for the deep research and writing tool. Write scientific manuscripts in full paragraphs (never bullet points). Use two-stage process with (1) section outlines with key points using research-lookup then (2) convert to flowing prose. IMRAD structure, citations (APA\u002FAMA\u002FVancouver), figures\u002Ftables, reporting guidelines (CONSORT\u002FSTROBE\u002FPRISMA), for research papers and journal submissions.","https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fscientific-agent-skills\u002Fskills\u002Fscientific-writing\u002FSKILL.md",[152,155,156],{"categoryNo":153,"categoryName":154,"taxonomyType":99,"taxonomyTypeDesc":100},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":97,"categoryName":98,"taxonomyType":99,"taxonomyTypeDesc":100},{"categoryNo":157,"categoryName":158,"taxonomyType":159,"taxonomyTypeDesc":160},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[162,163,164,165,166,167,168,169,170,171,111,172,173,174,175,176,177,178,136,179],"writing","scientific","scientific writing","Citations","deep","deep research","never","papers","journal","false","api","prisma","structure","openrouter","Outlines","core skill","read","features",{"skillNo":181,"name":182,"nameZh":6,"description":183,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":184,"categories":185,"keywords":190,"publishedAt":39},"01M7166ZPKXBF4EBPHVTJPQBB1","scientific-visualization","Meta-skill for publication-ready figures. Use when creating journal submission figures requiring multi-panel layouts, significance annotations, error bars, colorblind-safe palettes, and specific journal formatting (Nature, Science, Cell). Orchestrates matplotlib\u002Fseaborn\u002Fplotly with publication styles. For quick exploration use seaborn or plotly directly.","https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fscientific-agent-skills\u002Fskills\u002Fscientific-visualization\u002FSKILL.md",[186,189],{"categoryNo":187,"categoryName":188,"taxonomyType":99,"taxonomyTypeDesc":100},"01JBSEEDCAT000000000000013","分析与可视化",{"categoryNo":97,"categoryName":98,"taxonomyType":99,"taxonomyTypeDesc":100},[191,163,192,193,194,195,196,197,198,170,110,199,200,201,202,203,204,205,22,206],"visualization","scientific visualization","error","ready","quick","exploration","meta-skill","Meta","safe","plotly","annotations","formatting","Publication","figures","panel","submission",{"skillNo":208,"name":209,"nameZh":6,"description":210,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":211,"categories":212,"keywords":219,"publishedAt":39},"01M7166VXJWQSSC3RQZP60WBGR","research-code","Debug, profile, review, and restructure Python research and analysis code. Use when analysis scripts crash, produce wrong results, run too slowly, or need a correctness review. Covers: reading tracebacks and diagnosing root causes, hypothesis-driven debugging, reviewing code for common statistical\u002Fnumerical errors, profiling data pipelines, improving reproducibility (seeds, environments, determinism), and structuring analysis scripts for sharing. Distinct from \u002Fexploratory-data-analysis (which understands data) and \u002Fstatistical-analysis (which chooses and runs tests) — this skill focuses on the code itself.","https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fscientific-agent-skills\u002Fskills\u002Fresearch-code\u002FSKILL.md",[213,216],{"categoryNo":214,"categoryName":215,"taxonomyType":99,"taxonomyTypeDesc":100},"01JBSEEDCAT000000000000021","资料检索与研究",{"categoryNo":217,"categoryName":218,"taxonomyType":159,"taxonomyTypeDesc":160},"01JBSEEDCAT000000000000030","开发工程师",[220,221,141,222,223,224,225,109,226,110,111,35,227,228,229,230,231,232,178,233],"code","research code","which","memory","data-analysis","errors","debugging","common","review","data pipelines","run","scripts","line","write",{"skillNo":235,"name":236,"nameZh":6,"description":237,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":238,"licenseFileUrl":6,"sourceHtmlUrl":239,"categories":240,"keywords":247,"publishedAt":39},"01M7166CF5HSR7SVZYZFWTDX55","markdown-mermaid-writing","Comprehensive markdown and Mermaid diagram writing skill. Use when creating any scientific document, report, analysis, or visualization. Establishes text-based diagrams as the default documentation standard with full style guides (markdown + mermaid), 24 diagram type references, and 9 document templates.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fscientific-agent-skills\u002Fskills\u002Fmarkdown-mermaid-writing\u002FSKILL.md",[241,244,245,246],{"categoryNo":242,"categoryName":243,"taxonomyType":99,"taxonomyTypeDesc":100},"01JBSEEDCAT000000000000002","文档处理",{"categoryNo":153,"categoryName":154,"taxonomyType":99,"taxonomyTypeDesc":100},{"categoryNo":97,"categoryName":98,"taxonomyType":99,"taxonomyTypeDesc":100},{"categoryNo":157,"categoryName":158,"taxonomyType":159,"taxonomyTypeDesc":160},[162,248,249,250,251,252,191,253,254,110,111,35,255,256,257,258,259,260,163,178],"markdown mermaid writing","mermaid","markdown","community","diagrams","project","default","team","guides","text","documentation","references","diagram"]