FScanpy-package/FScanpy_Demo.ipynb

938 lines
82 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# FScanpy \n",
"\n",
"This notebook demonstrates how to use FScanpy with real test data for complete PRF site prediction analysis, including:\n",
"\n",
"## 🎯 Complete Workflow\n",
"1. **Load Test Data** - Use built-in real test data\n",
"2. **FScanR Analysis** - Identify potential PRF sites from BLASTX results\n",
"3. **Sequence Extraction** - Extract sequences around PRF sites\n",
"4. **FScanpy Prediction** - Use machine learning models to predict probabilities\n",
"5. **Results Visualization** - Generate prediction result plots using built-in plotting functions\n",
"6. **Sequence-level Prediction Demo** - Sliding window analysis of complete sequences\n",
"\n",
"## 📊 Data Description\n",
"- **blastx_example.xlsx**: Real BLASTX alignment results\n",
"- **mrna_example.fasta**: Real mRNA sequence data\n",
"- **region_example.csv**: Sample for individual site prediction"
]
},
{
"cell_type": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"## 📚 FScanpy Function Usage Guide\n",
"\n",
"### Core Functions Overview\n",
"\n",
"FScanpy provides several main functions for PRF prediction:\n",
"\n",
"#### 1. `predict_prf()` - Universal Prediction Function\n",
"```python\n",
"# Single sequence prediction\n",
"results = predict_prf(sequence=\"ATGCGT...\", window_size=3, ensemble_weight=0.4)\n",
"\n",
"# Multiple sequences prediction \n",
"results = predict_prf(sequence=[\"seq1\", \"seq2\"], window_size=3)\n",
"\n",
"# DataFrame region prediction\n",
"results = predict_prf(data=df_with_399bp_column, ensemble_weight=0.4)\n",
"```\n",
"\n",
"#### 2. `plot_prf_prediction()` - Prediction with Visualization\n",
"```python\n",
"# Basic plotting\n",
"results, fig = plot_prf_prediction(sequence=\"ATGCGT...\")\n",
"\n",
"# Custom parameters\n",
"results, fig = plot_prf_prediction(\n",
" sequence=\"ATGCGT...\",\n",
" window_size=1,\n",
" short_threshold=0.65,\n",
" long_threshold=0.8,\n",
" ensemble_weight=0.4,\n",
" save_path=\"plot.png\"\n",
")\n",
"```\n",
"\n",
"#### 3. `PRFPredictor` Class Methods\n",
"```python\n",
"predictor = PRFPredictor()\n",
"\n",
"# Sliding window prediction\n",
"results = predictor.predict_sequence(sequence, window_size=3, ensemble_weight=0.4)\n",
"\n",
"# Region prediction\n",
"results = predictor.predict_regions(sequences_399bp, ensemble_weight=0.4)\n",
"\n",
"# Single position prediction\n",
"result = predictor.predict_single_position(fs_period_33bp, full_seq_399bp)\n",
"\n",
"# Plot prediction\n",
"results, fig = predictor.plot_sequence_prediction(sequence)\n",
"```\n",
"\n",
"#### 4. Utility Functions\n",
"```python\n",
"from FScanpy.utils import fscanr, extract_prf_regions\n",
"\n",
"# Detect PRF sites from BLASTX\n",
"prf_sites = fscanr(blastx_df, mismatch_cutoff=10, evalue_cutoff=1e-5)\n",
"\n",
"# Extract sequences around PRF sites\n",
"prf_sequences = extract_prf_regions(mrna_file, prf_sites)\n",
"```\n",
"\n",
"### Parameter Guidelines\n",
"\n",
"- **ensemble_weight**: 0.4 (default, balanced), 0.2-0.3 (conservative), 0.7-0.8 (sensitive)\n",
"- **window_size**: 1 (detailed), 3 (standard), 6-9 (fast)\n",
"- **short_threshold**: 0.1 (default), 0.2-0.3 (stricter filtering)\n",
"- **Display thresholds**: 0.3-0.8 for visualization filtering\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 📦 Environment Setup and Data Loading"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"ename": "ImportError",
"evalue": "cannot import name 'PRFPredictor' from 'FScanpy' (unknown location)",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[3], line 6\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mmatplotlib\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mpyplot\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mplt\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;66;03m# Import FScanpy related modules\u001b[39;00m\n\u001b[0;32m----> 6\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mFScanpy\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m PRFPredictor, predict_prf, plot_prf_prediction\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mFScanpy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mdata\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m get_test_data_path, list_test_data\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mFScanpy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mutils\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m fscanr, extract_prf_regions\n",
"\u001b[0;31mImportError\u001b[0m: cannot import name 'PRFPredictor' from 'FScanpy' (unknown location)"
]
}
],
"source": [
"# Import necessary libraries\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# Import FScanpy related modules\n",
"from FScanpy import PRFPredictor, predict_prf, plot_prf_prediction\n",
"from FScanpy.data import get_test_data_path, list_test_data\n",
"from FScanpy.utils import fscanr, extract_prf_regions\n",
"\n",
"print(\"✅ Environment setup complete!\")\n",
"print(\"📋 Available test data:\")\n",
"list_test_data()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Load and Explore Test Data\n",
"\n",
"First, load the real test data provided by FScanpy to understand the data structure."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"📁 数据文件路径:\n",
" BLASTX数据: /mnt/lmpbe/guest01/FScanpy-package-main/FScanpy/data/test_data/blastx_example.xlsx\n",
" mRNA序列: /mnt/lmpbe/guest01/FScanpy-package-main/FScanpy/data/test_data/mrna_example.fasta\n",
" 验证区域: /mnt/lmpbe/guest01/FScanpy-package-main/FScanpy/data/test_data/region_example.csv\n",
"\n",
"🧬 BLASTX数据概览:\n",
" 数据形状: (1000, 14)\n",
" 列名: ['DNA_seqid', 'Pep_seqid', 'pident', 'length', 'mismatch', 'gapopen', 'qstart', 'qend', 'sstart', 'send', 'evalue', 'bitscore', 'qframe', 'sframe']\n",
" 唯一序列数: 704\n",
"\n",
"📊 BLASTX数据示例:\n",
" DNA_seqid Pep_seqid pident length evalue qframe\n",
"0 MSTRG.9998.1 CAMPEP_0196994412 68.27 104 1.000000e-33 2\n",
"1 MSTRG.9996.1 CAMPEP_0197017426 49.16 297 3.000000e-79 2\n",
"2 MSTRG.9994.1 CAMPEP_0197009206 98.31 354 0.000000e+00 2\n",
"3 MSTRG.9993.1 CAMPEP_0168331218 51.67 60 2.000000e-37 2\n",
"4 MSTRG.9993.1 CAMPEP_0168331218 45.45 88 2.000000e-37 3\n"
]
}
],
"source": [
"# Get test data paths\n",
"blastx_file = get_test_data_path('blastx_example.xlsx')\n",
"mrna_file = get_test_data_path('mrna_example.fasta')\n",
"region_file = get_test_data_path('region_example.csv')\n",
"\n",
"print(f\"📁 Data file paths:\")\n",
"print(f\" BLASTX data: {blastx_file}\")\n",
"print(f\" mRNA sequences: {mrna_file}\")\n",
"print(f\" Validation regions: {region_file}\")\n",
"\n",
"# Load BLASTX data\n",
"blastx_data = pd.read_excel(blastx_file)\n",
"print(f\"\\n🧬 BLASTX data overview:\")\n",
"print(f\" Data shape: {blastx_data.shape}\")\n",
"print(f\" Column names: {list(blastx_data.columns)}\")\n",
"print(f\" Unique sequences: {blastx_data['DNA_seqid'].nunique()}\")\n",
"\n",
"# Display first few rows\n",
"print(\"\\n📊 BLASTX data examples:\")\n",
"display_cols = ['DNA_seqid', 'Pep_seqid', 'pident', 'length', 'evalue', 'qframe']\n",
"print(blastx_data[display_cols].head())"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🎯 验证区域数据概览:\n",
" 数据形状: (3, 8)\n",
" 列名: ['FS_period', '399bp', 'fs_position', 'DNA_seqid', 'label', 'source', 'FS_type', 'dataset']\n",
" 数据来源: {'EUPLOTES': 3}\n",
"\n",
"📋 验证区域数据示例:\n",
" fs_position DNA_seqid label source FS_type\n",
"0 16.0 MSTRG.18491.1 0 EUPLOTES negative\n",
"1 16.0 MSTRG.4662.1 0 EUPLOTES negative\n",
"2 16.0 MSTRG.14742.1 0 EUPLOTES negative\n",
"\n",
"📈 标签分布:\n",
"label\n",
"0 3\n",
"Name: count, dtype: int64\n",
"\n",
"🔬 FS类型分布:\n",
"FS_type\n",
"negative 3\n",
"Name: count, dtype: int64\n"
]
}
],
"source": [
"# Load validation region data\n",
"region_data = pd.read_csv(region_file)\n",
"print(f\"🎯 Validation region data overview:\")\n",
"print(f\" Data shape: {region_data.shape}\")\n",
"print(f\" Column names: {list(region_data.columns)}\")\n",
"print(f\" Data sources: {region_data['source'].value_counts().to_dict()}\")\n",
"\n",
"print(\"\\n📋 Validation region data examples:\")\n",
"display_cols = ['fs_position', 'DNA_seqid', 'label', 'source', 'FS_type']\n",
"print(region_data[display_cols].head())\n",
"\n",
"# Statistical analysis\n",
"print(f\"\\n📈 Label distribution:\")\n",
"print(region_data['label'].value_counts())\n",
"print(f\"\\n🔬 FS type distribution:\")\n",
"print(region_data['FS_type'].value_counts())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. FScanR Analysis - Identify Potential PRF Sites from BLASTX\n",
"\n",
"Use the FScanR algorithm to analyze BLASTX results and identify potential programmed ribosomal frameshift sites."
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🔍 运行FScanR分析...\n",
"参数设置: mismatch_cutoff=10, evalue_cutoff=1e-5, frameDist_cutoff=10\n",
"\n",
"✅ FScanR分析完成\n",
"检测到的潜在PRF位点数量: 24\n",
"\n",
"📊 FScanR结果概览:\n",
" 列名: ['DNA_seqid', 'FS_start', 'FS_end', 'Pep_seqid', 'Pep_FS_start', 'Pep_FS_end', 'FS_type', 'Strand']\n",
" 涉及的序列数: 16\n",
" 链方向分布: {'+': 16, '-': 8}\n",
" FS类型分布: {1: 16, -1: 7, -2: 1}\n",
"\n",
"🎯 FScanR结果示例:\n",
" DNA_seqid FS_start FS_end Pep_seqid Pep_FS_start \\\n",
"0 MSTRG.9380.1 3797 3802 CAMPEP_0197017206 1137 \n",
"1 MSTRG.9431.1 4136 4192 CAMPEP_0197016790 657 \n",
"3 MSTRG.9432.1 848 904 CAMPEP_0197016790 753 \n",
"4 MSTRG.9582.1 302 304 CAMPEP_0197003180 214 \n",
"5 MSTRG.961.1 1536 1533 CAMPEP_0197017908 590 \n",
"\n",
" Pep_FS_end FS_type Strand \n",
"0 1138 1 + \n",
"1 675 1 + \n",
"3 2 1 - \n",
"4 214 1 + \n",
"5 19 -1 - \n"
]
}
],
"source": [
"# Run FScanR analysis\n",
"print(\"🔍 Running FScanR analysis...\")\n",
"print(\"Parameter settings: mismatch_cutoff=10, evalue_cutoff=1e-5, frameDist_cutoff=10\")\n",
"\n",
"fscanr_results = fscanr(\n",
" blastx_data,\n",
" mismatch_cutoff=10,\n",
" evalue_cutoff=1e-5,\n",
" frameDist_cutoff=10\n",
")\n",
"\n",
"print(f\"\\n✅ FScanR analysis complete!\")\n",
"print(f\"Number of potential PRF sites detected: {len(fscanr_results)}\")\n",
"\n",
"if len(fscanr_results) > 0:\n",
" print(f\"\\n📊 FScanR results overview:\")\n",
" print(f\" Column names: {list(fscanr_results.columns)}\")\n",
" print(f\" Number of sequences involved: {fscanr_results['DNA_seqid'].nunique()}\")\n",
" print(f\" Strand orientation distribution: {fscanr_results['Strand'].value_counts().to_dict()}\")\n",
" print(f\" FS type distribution: {fscanr_results['FS_type'].value_counts().to_dict()}\")\n",
" \n",
" print(\"\\n🎯 FScanR results examples:\")\n",
" print(fscanr_results.head())\n",
"else:\n",
" print(\"⚠️ No PRF sites detected, may need to adjust parameters\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Sequence Extraction - Extract Sequences Around PRF Sites\n",
"\n",
"Extract sequence fragments around PRF sites identified by FScanR from mRNA sequences."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"📝 从mRNA序列中提取PRF位点周围序列...\n",
"\n",
"✅ 序列提取完成!\n",
"成功提取的序列数量: 24\n",
"\n",
"📏 序列长度验证:\n",
" 399bp序列长度分布: {399: 24}\n",
" 平均长度: 399.0\n",
"\n",
"🧬 提取序列示例:\n",
"序列 1: MSTRG.9380.1\n",
" FS位置: 3797-3802\n",
" 链方向: +\n",
" FS类型: 1\n",
" 序列片段: AAGGAGTTTGAAGAAGAACAGGAAAAACAAGAGAAAGAGAGAAAGGAGAA...NNNNNNNNNNNNNNNNNNNN\n",
"\n",
"序列 2: MSTRG.9431.1\n",
" FS位置: 4136-4192\n",
" 链方向: +\n",
" FS类型: 1\n",
" 序列片段: CAAGTATCTGAGTGGGAGGGAGACACAGGTGTTGATCAAACCCCATTCCC...ATAATGACGGAGGCTTCAGA\n",
"\n",
"序列 3: MSTRG.9432.1\n",
" FS位置: 848-904\n",
" 链方向: -\n",
" FS类型: 1\n",
" 序列片段: AGAAAGGATGGTACTGAAAATCAACGAAGTACTTTCACATTTTAGAAAGA...GCTGAGAACGATATTGACAA\n",
"\n"
]
}
],
"source": [
"# Extract sequences around PRF sites\n",
"if len(fscanr_results) > 0:\n",
" print(\"📝 Extracting sequences around PRF sites from mRNA sequences...\")\n",
" \n",
" prf_sequences = extract_prf_regions(\n",
" mrna_file=mrna_file,\n",
" prf_data=fscanr_results\n",
" )\n",
" \n",
" print(f\"\\n✅ Sequence extraction complete!\")\n",
" print(f\"Number of successfully extracted sequences: {len(prf_sequences)}\")\n",
" \n",
" if len(prf_sequences) > 0:\n",
" print(f\"\\n📏 Sequence length validation:\")\n",
" seq_lengths = prf_sequences['399bp'].str.len()\n",
" print(f\" 399bp sequence length distribution: {seq_lengths.value_counts().to_dict()}\")\n",
" print(f\" Average length: {seq_lengths.mean():.1f}\")\n",
" \n",
" print(\"\\n🧬 Extracted sequence examples:\")\n",
" for i, row in prf_sequences.head(3).iterrows():\n",
" print(f\"Sequence {i+1}: {row['DNA_seqid']}\")\n",
" print(f\" FS position: {row['FS_start']}-{row['FS_end']}\")\n",
" print(f\" Strand orientation: {row['Strand']}\")\n",
" print(f\" FS type: {row['FS_type']}\")\n",
" print(f\" Sequence fragment: {row['399bp'][:50]}...{row['399bp'][-20:]}\")\n",
" print()\n",
" else:\n",
" print(\"❌ Sequence extraction failed\")\n",
"else:\n",
" print(\"⚠️ Skipping sequence extraction - no FScanR results\")\n",
" prf_sequences = pd.DataFrame()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. FScanpy Prediction - Machine Learning Model Analysis\n",
"\n",
"Use FScanpy's machine learning models to predict PRF probabilities for the extracted sequences."
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🤖 FScanpy预测器初始化完成\n",
"\n",
"🎯 对 24 个FScanR识别的序列进行预测...\n",
"\n",
"📊 FScanR+FScanpy预测结果:\n",
" DNA_seqid FS_start FS_type Short_Probability Long_Probability \\\n",
"0 MSTRG.9380.1 3797 1 0.239192 0.087024 \n",
"1 MSTRG.9431.1 4136 1 0.326807 0.356356 \n",
"2 MSTRG.9432.1 848 1 0.310908 0.159746 \n",
"3 MSTRG.9582.1 302 1 0.272451 0.223354 \n",
"4 MSTRG.961.1 1536 -1 0.263269 0.046773 \n",
"\n",
" Ensemble_Probability \n",
"0 0.147891 \n",
"1 0.344536 \n",
"2 0.220211 \n",
"3 0.242993 \n",
"4 0.133372 \n"
]
}
],
"source": [
"# Initialize predictor\n",
"predictor = PRFPredictor()\n",
"print(\"🤖 FScanpy predictor initialization complete\")\n",
"\n",
"# Predict FScanR identified sequences\n",
"if len(prf_sequences) > 0:\n",
" print(f\"\\n🎯 Predicting {len(prf_sequences)} sequences identified by FScanR...\")\n",
" \n",
" fscanr_predictions = predictor.predict_regions(\n",
" sequences=prf_sequences['399bp'],\n",
" ensemble_weight=0.4 # Balanced configuration\n",
" )\n",
" \n",
" # Merge results\n",
" fscanr_predictions = pd.concat([\n",
" prf_sequences.reset_index(drop=True),\n",
" fscanr_predictions.reset_index(drop=True)\n",
" ], axis=1)\n",
" \n",
" print(\"\\n📊 FScanR+FScanpy prediction results:\")\n",
" result_cols = ['DNA_seqid', 'FS_start', 'FS_type', 'Short_Probability', 'Long_Probability', 'Ensemble_Probability']\n",
" print(fscanr_predictions[result_cols].head())"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🧪 对 3 个验证区域进行预测...\n",
"\n",
"📊 验证区域预测结果:\n",
" DNA_seqid label source Short_Probability Long_Probability \\\n",
"0 MSTRG.18491.1 0 EUPLOTES 0.368610 0.144442 \n",
"1 MSTRG.4662.1 0 EUPLOTES 0.229811 0.053352 \n",
"2 MSTRG.14742.1 0 EUPLOTES 0.454152 0.345118 \n",
"\n",
" Ensemble_Probability \n",
"0 0.234109 \n",
"1 0.123936 \n",
"2 0.388732 \n"
]
}
],
"source": [
"# Predict validation region data\n",
"print(f\"\\n🧪 Predicting {len(region_data)} validation regions...\")\n",
"\n",
"validation_predictions = predict_prf(\n",
" data=region_data.rename(columns={'399bp': 'Long_Sequence'}),\n",
" ensemble_weight=0.4\n",
")\n",
"\n",
"print(\"\\n📊 Validation region prediction results:\")\n",
"result_cols = ['DNA_seqid', 'label', 'source', 'Short_Probability', 'Long_Probability', 'Ensemble_Probability']\n",
"print(validation_predictions[result_cols].head())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Sequence-level Prediction and Visualization\n",
"\n",
"Select a specific mRNA sequence and use the built-in plot_prf_prediction function for complete sliding window prediction and visualization."
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🧬 选择演示序列: MSTRG.9127.1\n",
"序列长度: 256 bp\n",
"序列前100bp: TGGCCTTCTTACTTGGAAGTCCCCAAGGATCATCTTGGCCATCCTTGCTTTCTTCATGGCTAGATTCTACCTCCTCCCATAATTGTGTGAAACAAGTAAC...\n",
"\n",
"🎯 使用plot_prf_prediction进行序列预测和可视化...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/mnt/lmpbe/guest01/FScanpy-package-main/FScanpy/predictor.py:335: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.\n",
" plt.tight_layout()\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 39044 (\\N{CJK UNIFIED IDEOGRAPH-9884}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 27979 (\\N{CJK UNIFIED IDEOGRAPH-6D4B}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 27010 (\\N{CJK UNIFIED IDEOGRAPH-6982}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 29575 (\\N{CJK UNIFIED IDEOGRAPH-7387}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 28909 (\\N{CJK UNIFIED IDEOGRAPH-70ED}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 22270 (\\N{CJK UNIFIED IDEOGRAPH-56FE}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 31227 (\\N{CJK UNIFIED IDEOGRAPH-79FB}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 30721 (\\N{CJK UNIFIED IDEOGRAPH-7801}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 20998 (\\N{CJK UNIFIED IDEOGRAPH-5206}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 24067 (\\N{CJK UNIFIED IDEOGRAPH-5E03}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 38598 (\\N{CJK UNIFIED IDEOGRAPH-96C6}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 25104 (\\N{CJK UNIFIED IDEOGRAPH-6210}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 26435 (\\N{CJK UNIFIED IDEOGRAPH-6743}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 37325 (\\N{CJK UNIFIED IDEOGRAPH-91CD}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 24207 (\\N{CJK UNIFIED IDEOGRAPH-5E8F}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 21015 (\\N{CJK UNIFIED IDEOGRAPH-5217}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 20301 (\\N{CJK UNIFIED IDEOGRAPH-4F4D}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 32622 (\\N{CJK UNIFIED IDEOGRAPH-7F6E}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 36807 (\\N{CJK UNIFIED IDEOGRAPH-8FC7}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 28388 (\\N{CJK UNIFIED IDEOGRAPH-6EE4}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 38408 (\\N{CJK UNIFIED IDEOGRAPH-9608}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 20540 (\\N{CJK UNIFIED IDEOGRAPH-503C}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 30340 (\\N{CJK UNIFIED IDEOGRAPH-7684}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 32467 (\\N{CJK UNIFIED IDEOGRAPH-7ED3}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 26524 (\\N{CJK UNIFIED IDEOGRAPH-679C}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 65288 (\\N{FULLWIDTH LEFT PARENTHESIS}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 26465 (\\N{CJK UNIFIED IDEOGRAPH-6761}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 24418 (\\N{CJK UNIFIED IDEOGRAPH-5F62}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n",
"/home/guest01/.conda/envs/tf200/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: Glyph 65289 (\\N{FULLWIDTH RIGHT PARENTHESIS}) missing from font(s) Liberation Sans.\n",
" fig.canvas.print_figure(bytes_io, **kw)\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABRcAAALmCAYAAADYLKN3AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8ekN5oAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB3P0lEQVR4nOzdd5hV1b0/4M8MvYmAHUUS7IgRG/ZCxBqCKd6AsSCxBcHY9UaNJvYoMYJ6FQtKNGo0Yosltti9RpMIErFiQ6IIDIq0YWZ+f/DjXMcBxA0yDL7v8+TJnHXWPuu798w65/hh7b3LampqagIAAAAA8BWV13cBAAAAAEDDJFwEAAAAAAoRLgIAAAAAhQgXAQAAAIBChIsAAAAAQCHCRQAAAACgEOEiAAAAAFCIcBEAAAAAKES4CAAAAAAU0ri+CwAAGpa//e1vufnmm2u1XXXVVRk9enQuv/zyWu3nnXdeOnTosMDXOeigg/L8889n0KBBGTx48AL79OnTJ+PGjcv555+fH/7wh6X2v/zlL/nTn/6UN954IxUVFWndunU23XTTHHnkkdlmm22SJD179syECRMWuS/zx55fy+eVlZWlbdu22WabbXLMMcdk/fXXr7P9gw8+mD/96U8ZO3ZsPv3007Rq1Spdu3bNj3/84+y7776LHDtJ/vnPf+aSSy7JSy+9lKZNm2bnnXfOL3/5yzrHbMqUKTnllFPyxBNP5Oqrr87OO+9c6/nKysrccMMNGTVqVCZMmJBVV101u+++e44++ui0bt36S4/HD37wg1xwwQWLrPX666/PxRdfnF69euWSSy5ZZN/bb789Dz30UOlxhw4dct555yVJTj311IwaNapW/yZNmmTttddO7969c/jhh6dp06ZJkmHDhuWyyy6r8/otW7bMJptskp/97Gfp2bNnqX1h/ef78Y9/nKOPPjq//vWva7Uff/zx6dy5c4455pha7f379892222XE044IdOnTy+1f+9730vv3r1z3nnn5Z133im1b7fddunfv3+GDx+eF198sdS+wQYb5IQTTljocVlac+qLvurrzp49e4HHZsMNN1ys8QCAbybhIgDwlbz//vs544wzsvbaaydJLrzwwiTJ5MmTM2DAgPTo0SNJcuONN2bmzJmLfK2WLVtm1KhRGTRoUMrKymo9N27cuLz77rt1trniiity2WWXZdCgQTnjjDPSsmXLvPvuu7nqqqvys5/9LCNHjkz37t1z++23p6qqqrTd97///fTo0SOnnXZarfHn69q1a6666qrS46qqqrz11lu55JJLcsABB+Suu+7KWmutlSSpqanJqaeemvvvvz8DBgzI8ccfn5VXXjkfffRR7rnnnpxwwgn5+9//nrPOOmuh+z5u3LgcfPDB2XHHHXPLLbeksrIyp5xySo466qjceuutKS+fd4LJ888/nxNOOCFt2rRZ6GtdeOGFue2223LmmWdmq622yssvv5wzzjgjH330UYYMGZIkdY5HkkydOjU/+clPst122y30tSsqKnLqqadm7Nixadas2UL7fd6bb75Z61jO/xuZr3379rn77rtLjz/55JM8++yzufjii/Pmm2/md7/7Xa3+jz76aClwrKmpyX/+85/84Q9/yMCBA3PZZZdl9913X2j/z2vRokU++OCD7LnnnqWw+rHHHktFRUXmzp2bTTfdtBR0v/baa3n55ZeTJKuttlrpOH722We57rrrkiSNGjVa4H5OnTp1ge0LOy5Lc0593ld93RkzZizw2AAALIpwEQCoN1tvvXWeeOKJPPfcc3UCrlGjRmXrrbfO448/Xqv9xhtvzL777puBAweW2tZaa61sscUWOfDAA/Ovf/0r3bt3T/v27WttV15enubNm2fVVVddYC2NGzeu89waa6yRLl26ZOedd86f/vSnHHvssUmSP/7xj7nzzjszfPjw7LLLLqX+HTt2TPfu3bPOOuvk2muvzaGHHpp11113geNdd911admyZYYMGVIKOS+55JL06dMnTzzxRHbdddckycUXX5yDDjoom222WQ455JA6rzN9+vTccsstOeqoo0qhUKdOnfLqq6/mqquuyplnnpmVVlqpzvGYP94GG2yQ73//+wusMUnuvffezJgxI3feeWf233//hfb7KsrLy2sd61VXXTVdunTJlClTcvnll+fkk0/OGmusUXp+lVVWqRVsrrbaarnwwgvz8ssv57rrrqsTLn6xPwAAXx/XXAQA6k379u3TvXv33HHHHbXa586dm3vuuafWKa/zzZo1K3PmzKnT3rRp0/zpT3/KoYceulRrXH311dO+ffv85z//KbWNGDEiO++8c61g8fP69++fJ554YqHBYpKMHTs2m266aa3VkxtttFE6duyYp556qtR20UUX5YgjjqizsnO+Vq1a5YknnsiAAQNqta+22mqpqalZ6Eq30aNHZ9SoUTnttNMW+tpJsssuu2TEiBGLfSrukthoo42SJB988MGX9i0vL88GG2xQ6/fyTXXqqafm1FNPre8yAIBvKOEiAFCvvve97+Whhx6qdU27J598Mp988kn23HPPOv133nnnPPDAAzn++OPz97//fYFB49I0ZcqUTJ06tXRK9MSJE/Pee+8tNFhM5l2vcf5pzQvTuHHjNGrUqE57+/bta13Hb1EB5fyx2rdvXyukTOadGrzGGmtk9dVXX+B2Q4cOzc4775zNNttska+/zjrrLLDOr8Pbb7+dJFlzzTUXq/9bb71V+r0AAFA/hIsAQL3aZ599Mnfu3PzlL38ptY0aNSo77rhj2rVrV6f/2Wefnb333jv33XdfDjzwwGy99dbp379/rr/++qV+fbj3338/p5xySlq0aJEf//jHSZIPP/wwyeIHYAvzrW99K//+978zd+7cUtvs2bPz9ttv57PPPlui177xxhvz1FNP5cQTT1zg86+88kqefPLJHHHEEUs0ztJSWVmZp556Ktddd1322GOPLz2206ZNy+9+97u89tprOeigg5ZRlQAALIhrLgIA9apdu3bZaaedcscdd+QnP/lJKioq8uijj9a5Cch8bdq0ye9///t88MEHefzxx/P3v/89zz//fJ599tlcccUVueqqq9K9e/evXMeYMWNqbVdVVZXZs2dnq622yvXXX19aITd/RWJ1dXWt7UePHl3nmoi9e/fOb37zmwWOd+CBB+b+++/Peeedl+OOOy6VlZU599xzU15ensaNi39Fu/7663PBBRfkqKOOSu/evRfY54YbbkjXrl2z5ZZbFh5nSUyePLnWsZ49e3YaN26cPn36LPD03m233bbW4xkzZqRz58658MILF7i69Yv95zv11FML/W0sjz6/H/NX7z744IOlts+H9QAAXyfhIgBQ777//e/n2GOPzZtvvpnnnnsuTZo0WeD1Fj9vrbXWSr9+/dKvX79UV1fnr3/9a0477bScddZZueuuu75yDRtuuGEuvfTS0uNHHnkkF110UU488cR85zvfqTVukrz33nu1tt9oo41y5513lh6feOKJizxle6uttsqFF16Ys88+OzfffHOaN2+eQw45JNtuu+2XnlK9IDU1Nbnoooty3XXX5YQTTsjhhx++wH6VlZV55JFH0r9//688xtKy8sor59Zbby09nn8znQXd4TlJbrvttjRp0iTJvNPSf/azn+VHP/pR9ttvvy/t/3nt27fPxIkTl3wHlgOf/1u7+OKLk6TWStXVVlttWZcEAHxDCRcBgHrXs2fPtGnTJvfdd1+efvrp9OrVKy1atFhg308++SQrrbRSrbby8vLstdde+cc//pEbb7wxNTU1i7xJyYI0bdq01vUN+/fvn/vvvz+nn356Ro0aVQq+VllllWywwQb561//WusmKl/cvnnz5l865n777Zd99tknkydPTocOHdK0adPsvffe6dOnz1eqPZkXMI0cOTK//e1vF3n35+effz6ffPJJ6W7U9aFRo0Zfei3Jz1tnnXVKd39ed911c/DBB+eyyy7LHnvskc6dOy+y/xetKOHi549fq1at6rQBACwrrrkIANS7Zs2aZc8998x9992Xf/3rXws9nfevf/1rtt566zz77LMLfP7999/Pqquu+pWDxQUpLy/PWWedlfHjx+fKK6+s9dwRRxyRf/7zn/nzn/+8wG2nT59eujbjwrz22mv585//nKZNm2bNNddM06ZNM3bs2IwfPz69evX6SrWOGjUq1113XS6++OJFBotJ8txzz6VFixbZZJNNvtIYy5NBgwalXbt2OeOMM1JTU1Pf5QAAfKMJFwGA5UKfPn3y1ltvpUOHDtl+++0X2GfXXXdN9+7d84tf/CLXX399XnnllXzwwQf55z//mV/96ld55JFHMmjQoKVWU9euXdOvX78MHz48b7zxRqm9d+/eOeSQQ3L66afnN7/5Tf71r39l4sSJ+fe//53rr78+++67b6ZNm5Yf/vCHpW2GDBmSAw44oPR44sSJ+eUvf5lLL7007733Xp577rkcd9xx+a//+q906dIlybzrPk6aNCmTJk3KtGnTksxbuTm/LZl3/cELLrgg++yzT7bccsvSc/P/N2vWrFr79NZbb2XttddeaAC71157ZcSIEaXHFRUVpdeafx3Khb32stKqVav88pe/zPPPP5/bbrutXmoAAGAep0UDAF/ZaaedVjrt980338wpp5ySJPnd736XlVdeOUkyYcKEr3Tq7dZbb52OHTumZ8+eadSo0QL7NG3aNCNGjMgf/vCH3H333fmf//mfTJ8+PSuttFI222yzXHPNNdlpp52WaN++6Nhjj82DDz6Y0047LTfffHPpeoi//OUvs/POO+emm27KwIED88knn6R169b59re/nZ/+9Kfp169f2rRpU3qdSZMm5Z133ik93mWXXXLOOefk2muvzTXXXJMOHTrkRz/6UX7+85+X+kycODHf/e53a9VzwgknlH5+9dVX8/LLL6eioiL33ntv7r333jr1n3/++bVCzmnTpqV169YL3d/x48dn8uTJpceDBw/O888/X3r8n//8J4888sgCX/vzjjzyyNLPS/su3kmy5557Zuedd85FF12UXXfd9StdY/CGG24o3fxk8uTJOemkk5Ik9957b15++eUkyWeffVbat2effba0P1VVVaVrcL722mu19nN+DZMnT67V/vm/54UdlyWZUxdccMFC9/Wrvu7Cjg0AwMKU1TiXBAAAAAAowGnRAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAgAbq4Ycfzt57752ZM2cuVv877rgjO+yww9dcFcvK3//+9+yyyy6ZMmVKfZcCAHyDNa7vAgAAGoIHHnggDz74YOnxsccemw8//DA333xzqe3ggw9OmzZtcvnll5favve976V79+45++yzS23bb7999t9//xx33HGlto033jhHHHFEzj777FJYtPrqq+fUU09dYD2TJk3KaaedlquvvjotWrRIkjz++OO5+uqr89prr2XGjBlZc801s//+++fwww9PWVnZ0jkQX3D77benZ8+ead++/Zf2ra6uzqWXXpp77703n3zySTbbbLOcddZZWWeddRbYf9y4cbngggvy8ssvp2XLltlzzz1z0kknpWnTpl861rBhw/Lkk0/mT3/601fep6/bhAkT8utf/zovvfRSWrZsmX322ScnnHBCyssX/O/+N998c66//vp89NFH6dSpUwYPHpzdd989W2+9dXr16pUzzjij1t8cAMCyJFwEAFgM7777bi655JIkyf/+7/9m6tSpef/99/PLX/4yq666at5///289NJLWWWVVXLIIYdk8803T5IMHz48G264YXbffffsu+++pbbk/wLFz7etvvrqOeOMM2q1Lci1116bbt26ZbPNNkuS/Otf/8rgwYNz7rnnZvfdd0/Tpk3zz3/+M7/4xS9SU1OTI488cqkfk6qqqlxwwQXp3r172rdvn9GjR6dbt24LDTJvuumm3HPPPbn66quz+uqr55JLLsnRRx+du+66q842n332WQ477LD86Ec/yvDhw/Pee+/l8MMPT7t27TJw4MClvi9Ly+TJkzNjxoyFBqZJMnjw4HTt2jUPP/xwJk+enCOPPDKrrLJKDj300Dp9H3zwwQwZMiRXXXVVNttss9x555059thjc//992edddbJEUccke9+97sZO3Zsunbt+nXuGgDAAjktGgCggZk7d25uu+22/Nd//Vep7fnnn8/aa6+d3r17p0WLFmnUqFG22mqrDB06NFtvvXWt7R966KF897vfTbdu3XLyySensrIyybyVhZdffnl69eqVzTbbLD/4wQ/y7LPPlrbr2bNn/ud//iff/e53c+aZZ2abbbbJp59+mj59+uSyyy7LFVdckV69emX48OH5+OOP69R96623pn///unSpUtat26d4447Lm+++WZeeumlOn0nT56cnXbaKYMHD07Tpk3TpUuX7LnnnnnhhReWyjGcNm1aTj755Oy4447p3r17jjjiiLz//vtJkvfffz8bbrhhnn766ey3337ZfPPN07dv39LzSXLFFVdk6623znbbbZfrr78+hx56aIYNG5aKior07ds3P/vZz/LXv/41c+fOrTXumDFjMm7cuJx44olp06ZNOnfunP79++fWW29dYJ2zZs3K8ccfny233DJNmjTJ/vvvn1atWuVf//pXkmS11VbLbrvtlltuuWWpHBcAgK9KuAgA0MCMGTMmn332WbbZZptS27e+9a2MHz8+t912W+bMmVNq33LLLbPFFluUHn/22Wd58cUXc8899+TWW2/Nfffdl8ceeyzJvJWFt912Wy677LK88MIL6d27dwYOHJjJkyeXtv/LX/6S6667LmeddVbuuuuuJMldd92VQYMG5corr8ywYcPy/vvvZ999980xxxyTZ555JjU1NZk1a1beeOONbLLJJqXXat26ddZdd92MGTOmzj526tQp559/fho3/r8TbSZOnJjVV199KRzB5PTTT8+kSZNy991358knn0zz5s1z7LHH1uozcuTIXHXVVfnb3/6WGTNm5JprrkkyL5y98sor8z//8z955JFH8uabb2bs2LFJki5duuSxxx7Lj370o9x4443Zddddc8kll5SCybFjx6Zjx45p27ZtaZyuXbtm/PjxmT59ep06+/TpkwMOOKD0+JNPPslnn31W6zj06NEjzz333FI5LgAAX5VwEQCggXnjjTey+uqrZ+WVVy617b777hkwYEB+/etfp0ePHjn00EMzfPjwTJgwoda2s2fPzuDBg9OyZctssskm+fa3v53x48cnmXf9xAMOOCAbbrhhmjZtmgEDBqRFixb529/+Vtp+p512yrrrrrvQU5833njj/OY3v8mjjz6aHXbYIRdffHH69euXadOmpaamplaoliRt27bN1KlTv3SfH3nkkTz22GMZMGDAYh6lhauoqMhDDz2UY489Nu3bt0/r1q1zzDHHZMyYMXnvvfdK/fr161c6zjvuuGPefPPNJPOubbnjjjtmq622SsuWLXPyySdn1qxZpe2aNm2affbZJyNHjswf/vCHzJ49O/vvv39uvvnmVFRUZKWVVqpzDJJ86XGoqanJ6aefnu985zu1guX1118/7777bq0aAACWFeEiAEADM3Xq1DohXVlZWU466aQ8/fTTOffcc9O5c+fccsst2WOPPXLnnXeW+rVr1y6tWrUqPW7evHlppeP777+fLl261HrdTp061QooO3bsuFg1VldXZ86cOZkzZ06aNGlSaq+pqVns/Zzvr3/9a0488cT89re/zfrrr/+Vt/+iDz74IDU1NbX2tVOnTklSa1/XXnvt0s8tWrTI7Nmzk8y7mc7nj8P805sXpLKyMnPmzEl1dXUaNWqUpNgxqKyszIknnpg33ngjl156aa3n2rVrl+TLw0kAgK+DG7oAADRAC1s52LZt2+yzzz7ZZ599UlNTk1/96le58MILs99++y1yuyS1Tqde2FjzA7KFGT16dG655ZY88sgj2WmnnXLWWWdlq622yuzZs1NeXp6Kiopa/SsqKtKhQ4eFvt6tt96aiy++OMOGDcuOO+64yLEX18L2M6m9rws7VtXV1bVO105S607Pc+bMyf33359bbrkl//nPf/LjH/84d999d1ZfffX86U9/WuAxKCsrW+gdt2fNmpWBAwdm5syZuemmm0ph4hfrLBJaAgAsKSsXAQAamHbt2tUJqK655ppapy8n80KnHXfcMbNmzVqs4KlTp0556623So/nzp2bd955Z5F3Pv68ww8/PKeccko22GCDPPjgg7n44ouz1VZbJUmaNWuW9ddfv3RtwmTe9QPffffd0h2vv+iBBx7IJZdckpEjRy61YDFJaX8+v6/zf56/gnFROnTokA8++KD0ePr06aVTy994443suuuuuf/++3PkkUfmkUceydFHH126RuKmm26aiRMnZsqUKaXtx4wZk/XWW6/WitL5ampqctxxx6Vx48a5/vrr6wSLSUqvtbBwEgDg6yRcBABoYNZbb7189NFHmTZtWqltxowZOe200/L4449n1qxZqa6uzquvvprhw4enZ8+ei1yxOF+fPn3yxz/+MW+++WbmzJmTK6+8MlVVVenZs+cC+zdv3jxJ8vbbb2f69Ok5/vjjc//996d///61rgc5X79+/TJy5Mi8+eabmT59ei6++OJsvPHG6datW5JkyJAhueCCC5Ikn376ac4666xcdNFF2XjjjRc4/l577VXo7tEdOnTIjjvumEsvvTQVFRWZNm1afv/736dHjx5Zc801v3T7bbfdNk888URGjx6dWbNm5be//W3pWLRv3z533HFHrrzyyuy66661VjQmySabbJJu3bplyJAhmT59et58882MGDEi/fr1W+B+3XPPPaVToZs1a7bAel5//fV06tSpVAMAwLLktGgAgMVQWVmZ4447LkkyefLkHH/88UmSM888M82aNcvMmTPTu3fvJMmll15aCtfmB2N/+MMf8vDDDydJaRXbvffem1deeSVJStclfOaZZ0ptn376aY444og6tXTr1i0tW7bM888/n169eiVJBg8enLZt2+aSSy7Je++9lzlz5mSNNdbI3nvvnYEDBy7WPg4YMCBTp07N4Ycfnk8++SQbb7xxRo4cWecGJPOtssoq2XPPPfOLX/wiffv2zVNPPZV33323Tr+OHTvmoYceSt++fTNp0qQcdNBB+eyzz9KjR49cdtllpX6TJk0qXdfwkUceydSpUxdY+/y7S48fP36RNzEZPXp0Kbic74c//GF+/etf58ILL8yvf/3r7L333ikvL892222X888//8sPUpLvf//7efnll3PwwQenbdu2OeaYY/LKK6+krKws48ePz0EHHbTA7QYOHJhBgwZl6NChOeOMM7LDDjukdevW6du3b607Qo8fPz4zZsxIkvz5z3/OhAkTat3AJZkXBJ9zzjlJkueffz7bbrvtYtUOALC0ldW4OAsAQINzwQUX5K233srw4cPru5R6c+mll2a33XZb6GnVX6c5c+akadOmpce77bZbBg4cmP3333+Z1jFp0qT07Nkzt9xyS7p27bpMxwYASJwWDQDQIP3sZz/LSy+9VFrF903097//PRtttFG9jLv11ltn9OjRqaqqyh133JFJkyZlu+22W+a1XH311dl5550FiwBAvbFyEQCggXr44YczZMiQjBo1yvX2lrHrr78+I0eOzJQpU7LOOuvkF7/4RXbfffdlWsMLL7yQE044IaNGjXIzFwCg3ggXAQAAAIBCnBYNAAAAABSyQt4teu7cuZk2bVqaNWuW8nL5KQAAAAB8FdXV1Zk9e3batm2bxo0XHiGukOHitGnT8vbbb9d3GQAAAADQoHXu3DkdOnRY6PMrZLjYrFmzJPN2vunoR+s832iznZd1ScutuX8YUqet8UEnLN62N/6u7rYHHr/ENX0dqp6+u05box2+Xw+VwPKt6l+P1WlrtPluX++Yj91ed8zdfvy1jtnQVD10c63HjXr1W7ztHrixTlujvQ780u3m/s+v67Q1/vmZizXm3Jsvrbttv1986XZV/1zA53X3nos35h9/X3fMA4798jH/cn3dMfftv1hjFv1cqXrijrrb7fzDxRvzf++ru22Pfb50u7l3Dq/T1ni/IxZvzGfvrTvmdt/78u3+/Wzd7TZZvDspVz23gP3c9sv3s+h2SVL19wfrbrv1nl++3TP31N1u+96LNWZR9fE+3dB88e9vsf/2XlnA3+3Gy/4O4Ivri3+3i/M3myRVY5+u09ao6w5LpaaFjlnwvaS+fPG9erHfpx+7rU5bo932Xyo1LU+qXvt7nbZGG2xdD5XA129JvqNWvfxU3W033fHLtxv3fN3tNtpmscZcqlq0ycyZM/P222+XcraFWSHDxfmnQrdo0SLNqmbVeb5RC3dTnG/uJx/XaWu8mMdnSbZd1qrmTK/T5u8A6qqqh/fMqtmfLvMxG5qqWZ/Uery4x6dq5rQ6bYuz7dypH9ZpW+zPhk8nF9q2qmpmnbbF3c+in0dVMyoKj1n0c6VqdvHPo6rKGYW2nfvZ1Dpti/v7rJrzWaExq6pnF9ouSaoqC45ZcLt52xY7tkWPz5Koj/fphuaLf3+L/XdQPadO2/J8bL/4d7v4+1l8fhZVH3NlSXzxvXqxj+2sb8Z3mqqayjptK+J+QrJk31GLvt9W1Swnn0ctW5Z+/LJLDrogIQAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAACikcX0X8HWorq5OksycOTNVjZrXeb7RzFnLuqTl1tyVVqnT1ngxj8+SbLusVTVtXafN3wHUVR/vmVXN2izzMRuaquYr1Xq8uMenqkXbOm2Ls+3cdqvXaVvsz4Y2HQptW9WoRZ22xd3Pop9HVS1XLjxm0c+VqmbFP4+qmrQstO3cVu3qtC3u77OqaatCY1aVNyu0XZJUNSk4ZsHt5m1b7NgWPT5LwnfbL/fFv7/F/jsob1qnbXk+tl/8u138/Sw+P4uqj7myJL74Xr3Yx7b5N+M7TVVZkzptK+J+QrJk31GLvt9WlS0vn0dNMnPmzCT/l7MtTFlNTU3NsihpWZo8eXLefvvt+i4DAAAAABq0zp07p0OHuosI5lshw8W5c+dm2rRpadasWcrLnfkNAAAAAF9FdXV1Zs+enbZt26Zx44Wf/LxChosAAAAAwNfPsj4AAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhwkUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAwGI588wz87Of/SzV1dX1Xcpyb/To0enRo0def/31+i4FAOBrVVZTU1NT30UAAHzTDRs2LK+99lrKysqSJOuvv34GDx6cAw88MO3bt0+STJgwIZdeemkmTJiQoUOHpkOHDqXthw4dmrPOOitTpkxJksycOTN77713fvjDH2bvvffO+uuvnyR54403ct999+WOO+7Ifffdl5YtWyZJOnTokDPPPHOh9d1555256KKLcu+99+a1115bpuPPnDkzF154YZ544olUVFRkvfXWy6BBg7LzzjsvtN7p06fnt7/9bR588MHMnj07G220UX75y19ms802W+g2I0eOzJ/+9KdMmDAha665Zvbff/8ceuihC+3/eX//+99z8MEHZ+DAgRk8eHCS5Lrrrsstt9ySu+66Ky1atFis1wEAaGga13cBAADMc8kll6Rx43lfz4YNG5Yk2XLLLXPcccclSe64445S36OPPjrbb799rb5t27bNWWedlSR5//338/zzzydJ9thjj9JrzO+bJGeccUbWXXfdOu1fNGPGjFx88cU5/PDD065du2U+/m9+85uMHj06V199ddZee+2MGjUqAwcOzF133ZUuXboscJvjjz8+lZWVufPOO9OmTZtcffXVueSSS3LttdemvLzuyTt33nlnLrnkkgwbNizbbLNNRo8enSOPPDKtW7fO/vvvv9DakuSzzz7LqaeemlatWtVqP/DAA/OHP/whN9xwQ4466qhFvgYAQEPltGgAABbprrvuyqeffpqf/OQny3zsadOm5Z577snRRx+dLl26pFmzZunbt2/WW2+9/PGPf1zgNqNHj85TTz2Vc889N2uuuWZat26d4447LiNGjCgFi1dccUV69uxZ2mbkyJHp06dPdtxxxzRt2jRbbbVVfvSjH2XkyJFfWuP555+fjTfeOBtvvHGt9qZNm+anP/1pRowYkaqqqiU4CgAAyy/hIgAAi/TEE09k6623rpdTe8eOHZvKyspsvvnmtdo322yz/Otf/1rgNs8++2zWXHPN/O1vf8vuu++ebbbZJocffnjefvvtUp+BAwfm0UcfTZLMmTMn48aNW+AYr7/+ej777LOF1vf444/n4YcfLq3Y/KIdd9wxFRUVGTNmzJftKgBAgyRcBABgkcaNG5dNNtmkXsaefw3Htm3b1mpv165d6bkvmjhxYj7++OOMHTs2d9xxR+6+++7MnTs3RxxxRObMmVOnf0VFRaqqqhY4Rk1NzULHqaioyGmnnZZf/epXWWWVVRbYZ4MNNkijRo3yyiuvfOm+AgA0RMJFAAAWacqUKaVrLTYENTU1mTNnTk477bSstNJKWWONNfLLX/4y77zzTl544YWlNs6vf/3rbLHFFtlnn30W2qe8vDxt27ZdaEAJANDQuaELAADLrfl3pK6oqKh1w5SpU6cudLXgaqutliZNmpTuRJ0knTp1SpJ89NFHdfqvvPLKady4cSoqKmq1T506NWVlZbXuij3fX/7ylzz33HP5y1/+8pX3CQBgRWLlIgAAi9S+fftMnTq1XsbedNNN07Rp07z00ku12v/xj3+ke/fuC9xmww03zOzZs/Paa6+V2t55550kydprr12nf9OmTdO1a9c613B88cUXs9FGG9UKKee75ZZb8tlnn2XvvfdOjx490qNHj/zjH//INddckx/84AelftXV1Zk2bVqDWvkJAPBVCBcBAFikDTfcsN6uGdimTZv86Ec/ymWXXZa33norM2fOzLXXXpt33303P/3pT5MkH374Yfbaa6/SKc+77LJL1ltvvfzmN7/JRx99lClTpuSCCy7IxhtvnC222GKB4/Tv3z933XVXnn766cyZMydPP/10Ro0alUMPPbTU55BDDsmIESOSJJdeemn++te/5q677ir9b9NNN03fvn0zfPjw0javvfZaqqqq6txJGgBgReG0aACA5cRxxx2XsrKyJMn666+fZN7quWOOOSZJMmHChGyzzTZJkssvvzy33HJLre2nTZtW6jtz5szsvffeSZK//vWvGT9+fJLkjTfeyODBg5MkZ599dmlV3oJO/Z1vl112yYUXXphZs2alefPmy3z8X/7yl/ntb3+bAw88MJ9++mk23njjXHvttVl33XWTJJWVlRk/fnxmzJiRJGnSpEmuueaanHPOOdlrr71SU1OTnXbaKRdeeGHKy+f92/oVV1yR22+/vXTH6H322SeffPJJzjrrrEycODFrrrlmTjnllPTp06dUx3vvvZfJkycnmbea84uaNm2a1q1bZ9VVVy21PfXUU1l55ZWz2WabLXT/AAAasrKampqa+i4CAIDl14wZM9KrV68ceeSROfjgg+u7nAajsrIye+65Z/bff//8/Oc/r+9yAAC+Fk6LBgBgkVq2bJnjjz8+w4cPr3PTExbupptuSnl5eQ455JD6LgUA4Gtj5SIAAIvlV7/6VSZOnJjhw4eXTt9mwUaPHp3DDz88f/jDH7LBBhvUdzkAAF8b4SIAAAAAUEi9nxb95JNPZvvtt89xxx23yH7V1dW55JJL8t3vfjdbb711fvazn+W9995bRlUCAAAAAF9Ur+Hi1VdfnXPOOad0p79Fuemmm3LPPfdk+PDheeyxx9K5c+ccffTRsfASAAAAAOpH4/ocvFmzZrn99ttz7rnnZvbs2Yvse+utt6Z///7p0qVLkuS4445Ljx498tJLL2XzzTev1Xfu3LmZNm1amjVrlvLyel+cCQAAAAANSnV1dWbPnp22bdumceOFR4j1Gi4efPDBi9Vv1qxZeeONN7LJJpuU2lq3bp111103Y8aMqRMuTps2LW+//fZSrBQAAAAAvnk6d+6cDh06LPT5eg0XF9e0adNSU1OTtm3b1mpv27Ztpk6dWqd/s2bNkszb+ebNmydJampqMn369LRu3drdDaEBMoehYTOHoWEzh6HhM4+hYauPOTxr1qy8/fbbpZxtYRpEuDjf4l5fcf6p0C1atEjLli1L286dOzetWrXyRgoNkDkMDZs5DA2bOQwNn3kMDVt9zOH543zZJQcbxAUJV1555ZSXl6eioqJWe0VFxSKXZQIAAAAAX58GES42a9Ys66+/fsaOHVtq++STT/Luu+9ms802q8fKAAAAAOCba7kNFz/88MPstddeee+995Ik/fr1y8iRI/Pmm29m+vTpufjii7PxxhunW7du9VwpAAAAAHwz1es1F+cHg3Pnzk2SPPzww0mSMWPGpLKyMuPHj8+cOXOSJH379s2kSZNy0EEH5bPPPkuPHj1y2WWX1U/hAAAAAED9hotjxoxZ6HNrr712Xn311dLjsrKyHHPMMTnmmGOWRWkAAAAAwJdYbk+LBgAAAACWb8JFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUEjj+i6gIZkzZ04efvjhPPbYY5k0aVKqqqrquyQagKZNm2a99dbLXnvtle7du9d3OQAAAABLjXBxMc2ZMycnnHBCnnzyyay//vpZa621Ul5u4Sdfbs6cOXnkkUfypz/9Kaeeemp+8pOf1HdJAAAAAEuFcHEx3XPPPXnqqadyxhln5Dvf+U59l0MDU1NTk2uvvTa//e1v893vfjerrLJKfZcEAAAAsMQsvVtMjz76aDbddFPBIoWUlZXlJz/5SWpqavL444/XdzkAAAAAS4VwcTFNnDgxnTp1qu8yaMDatGmTDh065D//+U99lwIAAACwVAgXF1N1dbVrLLLEGjVq5EZAAAAAwArDNReXwJ///Oe89dZbSZK11147/fr1y69//eu0bNky1dXV6d27d6ZPn146DbampiYnn3xyrrvuukyePDlJ0rVr1+yzzz6LHKempiY333xznnzyyVRXV6eysjJdunTJUUcdlQ4dOuShhx7K/fffn9/97ndLvE8PPPBAevXqlUaNGi2y34wZM3LppZfmjTfeSHl5eXbbbbcccMABC+x733335d57701lZWVWWmmlDBw4MF26dFnk6y/NfVpS7777boYNG5apU6emadOmOeigg7LddtvV6VdZWZlrrrkm//rXvzJ37tx8+9vfzjHHHJM2bdrUQ9UAAAAAXz/h4hJo2rRpTjrppCTzbviSJFtssUV69+6dDz/8MO+8806mTZuWQYMGpUWLFqU+q666agYMGFBru0W599578/zzz+eiiy5K69atM3fu3Fx55ZUZMmRIzjvvvKW2P1VVVbnmmmvSs2fPTJs2LXfccUf23nvvdOzYsU7fG264IU2bNs3VV1+dWbNm5YQTTsi3vvWtOqHbyy+/nD/+8Y8ZOnRo2rdvn1GjRuWSSy7JZZddttTqXlL/+Mc/8vbbb6dXr14LDAIvuOCC9OnTJ3vuuWfefffdnHTSSdlwww3Tvn37Wv1uu+22TJgwIZdddlnKy8tz/vnn549//GOOPPLIZbUrAAAAAMuUcHE58cwzz+TJJ5/MKaecUue59957L506dUrr1q2TJI0bN85hhx2WysrKWv1GjhyZZ555JnPmzMmgQYOyxRZbpKamJrfeemsee+yxJMlqq62WgQMHZs0118xNN92Ujz76KBMnTsxGG22Uf//735k5c2YGDRqU448/PmussUbOPffctG/fPvvss0969OhRWtH4+OOP5+yzz055eXlatmyZnj175m9/+1udcLFdu3Y59dRTS0Hclltumeuvvz41NTUpKysrdKw+++yzXHXVVXnllVdSXl6erl275ogjjkjz5s1z6qmnZptttskLL7yQCRMmpGPHjjnjjDPSokWLvPTSS/n973+fxo0bZ5tttslbb72Vnj17Zuutt86YMWMyePDgbLbZZtlnn32y0UYbJUnefvvtTJo0Kb169UqSdOrUKRtuuGGeeeaZfO9736tV1+abb56dd945TZo0SZJ07949Tz/9dKF9BAAAAGgIXERwObH99tsvMFhMkm222SZ/+9vfMnTo0Dz//PP57LPP0rx581qr7N5+++107949V155Zb73ve/lxhtvTDIvtHzkkUdy0UUX5aqrrspGG22U3//+96Xt/vd//zcnnnhiBgwYUFqFedlll2WjjTbK9773vVxxxRXZf//987e//S2HHXZYbrnllnzyySf59NNPs8Yaa5ReZ6211sqECRPq1N6xY8dsuummpcfPPfdcNtxww8LBYjIvRJ01a1auvPLKXH755fnoo49y++23l55/+umnc8YZZ+Taa6/NlClT8vTTT6eqqipDhgzJIYcckquvvjqdOnXKK6+8kiRZeeWVS+2bb755rr766hxzzDF5+umnM2HChKy++uq1rre55pprLnBfN9lkk6y99tq19nXjjTcuvJ8AAAAAyzvhYgOw1VZb5bzzzsuMGTPy+9//Pn379s3pp59eut5jMm9FYrdu3ZIk3/72t/Pxxx8nmRce7rbbbllppZWSJPvuu29efvnlzJo1K0nSpUuXrLbaaosc/zvf+U6OP/749OzZM7fffntmz56dZN5p4fM1bdq09JoL87//+78ZNWpUBg4c+BWPQN3X6d27dxo1apTGjRtnzz33zIsvvlh6ftttt02LFi3SuHHjdOrUKZMmTcqECRNSUVGRnXbaKUnSq1evtGjRotbrNmnSJD179sx///d/Z5VVVslDDz2U2bNnl1YiztesWbMv3dfrr78+U6dOzY9//OMl2lcAAACA5ZnTohuITTfdtLQC8O23386tt96aM888M9dff32SpGXLlqW+5eXlpTsST506NRtssEHpufmrHadNm5YkpdBxYSZMmJC//OUveeqpp7L11lvnwgsvTPPmzZMks2fPTrNmzZIkc+bMKbUvyIMPPpibbropv/nNb9K5c+evsOd1VVRUpG3btqXHbdq0SUVFRelxq1atSj83atQo1dXVmT59elq1alU6rbu8vDwdOnSo9bqjR4/OvffemzfeeCO777579txzz4wbN64Ups43e/bsOsHkfFVVVbn88sszfvz4nHfeeQvtBwAAALAiEC42AC+++GI22GCDUjDYuXPnDBw4MH379q0Vqi1Iu3bt8umnn5Yez/955ZVXXuR2n332Wc4///xMmTIle++9d6688spaAWbbtm0zYcKEUjj5/vvvp1OnTgt8rYceeii33357Lrzwwqy55ppftrtfauWVV84nn3xSevzpp5+mXbt2i9ymZcuWmTVrVqqrq1NeXp6amppMmTIlSfL666/nkksuSbt27bLvvvvmlFNOKYWQ66yzTj788MNUVVWV2iZMmJAddthhgeMMHTo006ZNy/nnn7/IsBUAAABgReC06AbgrrvuyvDhwzNz5swkSXV1dR555JGsvfbadVbffVGPHj3y6KOPZvr06UmSu+++O1tssUVpxeHnzQ/P5vft27dvrrjiivTu3btWsJgkPXv2zKhRo1JdXZ1p06blwQcfTM+ePeu85sSJEzNixIicffbZSyVYnL9Pf/nLX1JdXZ3Kysrcf//92XbbbRe5TceOHdO8efM899xzSZJHHnmkdGpz8+bNc9ppp+Xcc8/N9ttvXzoOybwbuHTs2DH33ntvkmTcuHF5/fXXs/3229cZ47HHHss777yT0047TbAIAAAAfCNYubgEPvzww1x00UVJ/u9U3EcffbR0Ku0ee+yRZN5qtvLy8kyZMiW9e/fO66+/XtpurbXWSrLou0Wfcsopuf7663PMMcekrKwsc+fOzYYbbpizzjrrS2vcYYcdMmHChJx44ompqanJWmutlV/84hcL7Nu+fft069YtRx55ZI488shce+21dfo0b948I0aMyIEHHphhw4bliCOOSHl5efbZZ59stdVWSZJ77rknH374YQ477LDcf//9mT17ds4888xar/OrX/0qzZo1y2mnnZbLLrusznUNk2T8+PE58sgja7Wde+65OeSQQ3LllVfm5z//eZJ5d2Xeb7/9FnkcmjRpksGDB+eaa67JjTfemO233z6dO3dOWVlZ3nrrrVx55ZV1ttliiy1y0kkn5ZRTTsmwYcNy7733plmzZjn55JNLp2UPGTIkW2+9dXbeeefcfffd+eijjzJo0KDSa7Ru3TpDhgxZZG0AAAAADVVZTU1NTX0XsbTNmDEjr7zySjbeeOPSiruamppMmzYtbdu2LXSn4v322y/dunXLIYccsrTL/UY755xzctpppy3R3aO/ipqamtJYhx9+eA477LD06NFjmYydJIMGDco+++yTY445ZpmNuaJY0jkM1C9zGBo2cxgaPvMYGrb6mMMLytcWxGnR1Ju5c+dmp512WmaT4qSTTsr999+fZN7pzZMnT8566623TMYGAAAAWBE5LZp607hx4+yyyy7LbLwjjzwyw4YNy6hRo9KoUaMce+yxX3rNSgAAAAAWTrjIN8Z6662XSy+9tL7LAAAAAFhhCBeXwJ///Oe89dZbSZK11147/fr1y69//eu0bNky1dXV6d27d6ZPn57HH388ybzz408++eRcd911mTx5cpKka9eu2WeffRY5Tk1NTW6++eY8+eSTpTskd+nSJUcddVQ6dOiQhx56KPfff39+97vfLfE+PfDAA+nVq1etOybP9/TTT+fGG29MZWVl2rdvn8GDB2edddap06+qqirXXHNNnnnmmTRq1Cg77LBDBgwY8KWnPx966KE5+uijSzeGWZbefffdDBs2LFOnTk3Tpk1z0EEHZbvttlvkNsOHD8+zzz6bESNGLKMqAQAAAJYvwsUl0LRp05x00klJ5t0hOZl3h+HevXvnww8/zDvvvJNp06Zl0KBBadGiRanPqquumgEDBtTablHuvffePP/887nooovSunXrzJ07N1deeWWGDBmS8847b6ntz/xQsGfPnnXCxY8//jiXXnppLrrooqy77rp58MEH89vf/jbDhg2r8zp//vOf88477+Saa67J3Llzc9555+Xtt9/Ot771raVW69J2wQUXpE+fPtlzzz3z7rvv5qSTTsqGG26Y9u3bL7D/uHHj8vzzzy/jKgEAAACWL8LF5cQzzzyTJ598Mqecckqd595777106tQprVu3TjLvWoWHHXZYKisra/UbOXJknnnmmcyZMyeDBg3KFltskZqamtx666157LHHkiSrrbZaBg4cmDXXXDM33XRTPvroo0ycODEbbbRR/v3vf2fmzJkZNGhQTjjhhEyePLlU0zPPPJNNNtkk6667bpJk9913z/Dhw/Puu++mU6dOter461//msGDB6dJkyZp0qRJzj777CU+Pv/7v/+bP/zhD6msrEyzZs1y6KGHpnv37hk9enSuvPLK7Lbbbnnssccyffr0DBgwILvuumsqKytz6aWX5p///GdWW2217Lbbbhk1alRGjBiRjz/+OKeddlouu+yyTJgwIZMmTUqvXr2SJJ06dcqGG26YZ555Jt/73vfq1FJZWZlhw4bl0EMPzTXXXLPE+wYAAADQUAkXlxPbb799tt9++wU+t8022+Q3v/lNGjdunG233TZdu3ZNq1at0rx581Kft99+O4ceemgOPvjg3HHHHbnxxhuzxRZb5JlnnskjjzySIUOGZKWVVspNN92U3//+97nwwguTzAvthg4dmtVWWy0ffvhhBgwYkMsuuyxNmzYt1ZUkEyZMyJprrlkar1GjRll99dXz/vvv1woXZ86cmYkTJ+a9997Lddddl9mzZ2f33XfPj3/848LH5uOPP85vf/vbXHjhhVlvvfUyevTonHPOObnuuuuSJB988EFWWWWVXHHFFXnssccycuTI7LrrrvnrX/+at99+O9ddd13mzp2b//7v/y695iqrrJKrrrqqtG+rr756ysv/7+bpa665ZiZMmLDAem655ZZ0797dnaYBAACAb7zyL+9Cfdtqq61y3nnnZcaMGfn973+fvn375vTTTy9d7zGZtyKxW7duSZJvf/vb+fjjj5PMCw932223rLTSSkmSfffdNy+//HJmzZqVJOnSpUtWW221L61h9uzZadKkSa22Zs2alV5nvs8++yzJvNWWv/vd73LmmWfmzjvvzLPPPltw75N//vOfWX/99Uth3mabbZb27dvnlVdeSZKUl5dnt912SzJv3ydNmpQkGTNmTLbffvs0a9YsrVq1yu67775E+5Yk48ePz1NPPZUDDzyw8P4AAAAArCiEiw3EpptumlNPPTV//OMfM2zYsLRp0yZnnnlmqqqqkiQtW7Ys9S0vLy+1T506tRQsJkmbNm2SJNOmTUuSWs8tSvPmzTN79uxabbNnz06LFi1qtc0/dXuPPfZIo0aNsuaaa2bHHXfMP/7xj6+yu7VMnTo1bdu2rdW20korpaKiIklq1VBeXp7q6uokyfTp02vt36qrrrrA12/WrNli7VtVVVWGDh2an//857VWjQIAAAB8UwkXG4AXX3wxn376aelx586dM3DgwEyZMqUUsC1Mu3btam07/+eVV175K9Ww9tpr54MPPig9njt3bj788MM611ts3rx5Vl555cycObPUVl5evsC7Ty+uL+5DknzyySdp167dIrdr2bJlZsyYUXo8/w7dX7TOOuvkww8/LAWyybxTpb94J+z33nsvH3zwQS699NIceuihOemkk/Lxxx/n0EMPrVMfAAAAwDeBcLEBuOuuuzJ8+PBSYFddXZ1HHnkka6+9djp06LDIbXv06JFHH30006dPT5Lcfffd2WKLLdKsWbM6fecHgPP7ft7222+fV199Na+//nrpdTp16pSOHTvW6durV6+MGjUqVVVVmTZtWp555plstdVWX22nP6d79+557bXXSqeB//Of/8y0adOyySabLHK7jTfeOM8++2wqKyszY8aM0k1tvmj+ftx7771J5t0J+vXXX69zDczOnTvn1ltvzYgRIzJixIhcdNFFWWWVVTJixIjSilAAAACAbxI3dFkCH374YS666KIkSatWrZIkjz76aMaNG5fZs2dnjz32SJIMHTo05eXlmTJlSnr37p3XX3+9tN1aa62VZNF3iz7llFNy/fXX55hjjklZWVnmzp2bDTfcMGedddaX1rjDDjtkwoQJOfHEE1NTU5O11lorv/jFLxbYt3379unWrVuOPPLIHH/88ampqSnV1L59+5x44on5/e9/nzlz5mSVVVbJySefXNr2yCOPzK9//eusscYa6du3b4YNG5YBAwakWbNm2XfffUvh4vXXX5+2bdvmBz/4wQJrGDZsWK1Tjrfbbrv0798/J598ci655JLMmTMnLVu2zOmnn17rVPAF2WuvvTJ27NgcfvjhWWONNbLTTjvl7rvvTpJad4tu0qRJTjnllAwbNiz33ntvmjVrlpNPPrl0KvaQIUOy9dZbZ+edd/7S4w0AAADwTVJWU1NTU99FLG0zZszIK6+8ko033rgUQNXU1GTatGlp27ZtysrKvvJr7rfffunWrVsOOeSQpV3uN8qrr76al156Kf/1X/+1TMarqakp/b7/9re/5Y477sjQoUOXydgLMmjQoOyzzz455phj6q2GhmpJ5zBQv8xhaNjMYWj4zGNo2OpjDi8oX1sQp0WzTM2aNSs9e/ZcJmO9+OKLOeqoozJjxoxUVVXl8ccf/9JTqQEAAABYfE6LXkxlZWWluxBT3He+851lNtYWW2yRHj16ZNCgQSkvL8+3v/3tHHDAActs/AWprq5OeblMHwAAAFgxCBcX0yqrrJKJEyfWdxl8BWVlZRkwYEAGDBhQ36UkSWbOnJkpU6Z86U14AAAAABoKS6gW0y677JLRo0dn/Pjx9V0KDdRf/vKXVFVVZZdddqnvUgAAAACWCisXF1OfPn3ywAMP5LTTTsuWW26Zjh07plGjRvVdFg3AnDlz8u9//zuvvvpqBgwYULpDOAAAAEBDJ1xcTG3atMkVV1yR22+/PY899lieeOIJ12BksTRt2jTrrbdeDj300Oy55571XQ4AAADAUiNc/ApWWmml5eoafgAAAABQn1xzEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAACqnXcHHChAk54ogj0qNHj+y222656KKLUl1dXadfdXV1hg4dmp49e6Z79+7p3bt37rvvvnqoGAAAAACYr3F9Dj548OB07do1Dz/8cCZPnpwjjzwyq6yySg499NBa/W6++ebcdtttueGGG7LuuuvmiSeeyKBBg/Ltb387G220UT1VDwAAAADfbPW2cnHMmDEZN25cTjzxxLRp0yadO3dO//79c+utt9bpO3bs2Gy55Zb59re/nUaNGmW33XbLyiuvnFdffbUeKgcAAAAAknpcuTh27Nh07Ngxbdu2LbV17do148ePz/Tp09O6detS+6677pqzzjorr7zySrp06ZInn3wyM2fOzDbbbLPIMWpqalJTU1Pr5/mPgYbFHIaGzRyGhs0chobPPIaGrT7m8OKOVW/hYkVFRVZaaaVabfODxqlTp9YKF/fYY4+88sor2W+//ZIkLVq0yIUXXpg111xzkWNMnz49lZWVSeYdkBkzZiRJysrKltZuAMuIOQwNmzkMDZs5DA2feQwNW33M4dmzZy9Wv3q95uLiJqB33nln7rzzztx2223ZcMMN8+yzz+aEE07Immuumc0222yh27Vu3TotW7asNVbbtm29kUIDZA5Dw2YOQ8NmDkPDZx5Dw1Yfc3h+mPll6i1cbN++fSoqKmq1VVRUpKysLO3bt6/VfuONN+YnP/lJKUjcdddds+222+buu+9eZLhYVlZW64DPf+yNFBomcxgaNnMYGjZzGBo+8xgatmU9hxd3nHq7ocumm26aiRMnZsqUKaW2MWPGZL311kurVq1q9a2urk5VVVWttjlz5iyTOgEAAACABau3cHGTTTZJt27dMmTIkEyfPj1vvvlmRowYkX79+iVJ9tprr7zwwgtJkp49e+b222/PuHHjMnfu3Dz11FN59tln893vfre+ygcAAACAb7x6vebi0KFDc8YZZ2SHHXZI69at07dv3xxwwAFJkvHjx5fO7T7yyCMzd+7cHH300ZkyZUo6duyYc845J9ttt119lg8AAAAA32j1Gi6uscYaufrqqxf43Kuvvlr6uUmTJjn22GNz7LHHLqPKAAAAAIAvU2+nRQMAAAAADZtwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgI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AADQMCzVG7oAAAAAAN8cX+mGLqNHj17k81OnTl0qRQEAAAAAy7/FDhePPvroTJkyZZF9jjrqqCUuCAAAAABoGBY7XJw+fXomTZr0ddYCAAAAADQgix0ujhw5MieccMJC7xSdJOecc0722WefpVIYAAAAALB8+0rXXFxrrbUW2adFixZLXBAAAAAA0DAsdriYJJMnT17oczU1NZk9e/YSFwQAAAAANAyLHS7269cvd9xxxyL7bL755ktaDwAAAADQQCx2uHjLLbfkJz/5ySL7XHHFFTn88MOXuCgAAAAAYPn3la65uM022yyyzy233LLEBQEAAAAADUP50nyxsrKypflyAAAAAMBybLFXLu65554ZPnz4QgPEmpqadOnSZakVBgAAAAAs375SuAgArHju6N270HY/vOeepVwJAADQ0CzV06IBAAAAgG8O4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAACmlc3wWwbN3Ru3eh7X54zz1LuRIAAAAAGjrhIoulaCiZCCYBAAAAVlROiwYAAAAAChEuAgAAAACFCBcBAAAAgEJccxEAaDDcmAwAAJYvVi4CAAAAAIVYuQgAFPJVVxFWVlamSZMmSawkBACAFYWViwAAAABAIfUaLk6YMCFHHHFEevTokd122y0XXXRRqqurF7nNhx9+mO7du2fYsGHLqEoAAAAAYEHq9bTowYMHp2vXrnn44YczefLkHHnkkVlllVVy6KGHLnSbc845J40aNVqGVQIAAAAAC1JvKxfHjBmTcePG5cQTT0ybNm3SuXPn9O/fP7feeutCt3n88cfzxhtvZNddd112hQIAAAAAC1RvKxfHjh2bjh07pm3btqW2rl27Zvz48Zk+fXpat25dq/+sWbPym9/8Jueee27uvPPOxRqjpqYmNTU1tX6e/5hlxzFnaTCHoYGbP3drapKysmU+l713wJLxOQwNn3kMDVt9zOHFHavewsWKioqstNJKtdrmB41Tp06tEy5efvnl2XzzzbPtttsudrg4ffr0VFZWJpl3QGbMmJEkKSsrW8LqG675x2NZmjZt2jIfkxWPOQxfn2X12VBVVVX6uehnQ9FafRbBkvE5DA2feQwNW33M4dmzZy9Wv3q95uLiJqBvvPFGbrvtttxzzz1f6fVbt26dli1b1hqrbdu23+g30iZNmizzMT+/OhWKMofh67NMPhv+/xxu0rhxUlZW+LOhaK0+i2DJ+ByGhs88hoatPubw/DDzy9RbuNi+fftUVFTUaquoqEhZWVnat29faqupqclZZ52VwYMHZ9VVV/1KY5SVldU64PMfeyNdthxvlhZzGBqw+fP2////sp7H3jdgyfkchobPPIaGbVnP4cUdp97CxU033TQTJ07MlClTSmHimDFjst5666VVq1alfh988EH+/ve/5/XXX8/QoUOTzEtOy8vL8+ijj2bUqFH1Uj8AAAAAfNPVW7i4ySabpFu3bhkyZEj++7//Ox9++GFGjBiRAQMGJEn22muvnHPOOenevXsef/zxWtuef/75WWONNXLYYYfVR+kAAAAAQOr5motDhw7NGWeckR122CGtW7dO3759c8ABByRJxo8fnxkzZqRRo0ZZY401am3XokWLtG7d+iufJg0AAAAALD31Gi6uscYaufrqqxf43KuvvrrQ7S644IKvqyQAAAAAYDGV13cBAAAAAEDDJFwEAAAAAAoRLgIAAAAAhdTrNRcBAFh67ujdu/C2P7znnqVYCQAA3xRWLgIAAAAAhQgXAQAAAIBChIsAAAAAQCHCRQAAAACgEOEiAAAAAFCIcBEAAAAAKES4CAAAAAAUIlwEAAAAAAoRLgIAAAAAhTSu7wIAAL5ud/TuXXjbH95zz1KsBAAAVixWLgIAAAAAhQgXAQAAAIBChIsAAAAAQCHCRQAAAACgEDd0AYAVwJLcsAQAAKAoKxcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIgbugDAcsSNWQAAgIbEykUAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKcbdoAAAA+Aru6N270HY/vOeepVwJQP2zchEAAAAAKES4CAAAAAAU4rRoAJaZoqcQJU4jAgAAWB5ZuQgAAAAAFGLlIgDA18BKXYDl25K8TwPwf6xcBAAAAAAKES4CAAAAAIUIFwEAAACAQoSLAAAAAEAhbugCALAILvgPAAALJ1xkhVT0PwTdnRMAAABg8TktGgAAAAAoxMpFAAD4EktyerwzIwCAFZlwEQAAgKXC5YkAvnmEiwAACAQAAChEuAgAQL2ojztxC0MBAJYu4SIAwHKmPkI3AAAowt2iAQAAAIBChIsAAAAAQCHCRQAAAACgEOEiAAAAAFCIG7oAAPCN4WY5AABLl3ARAABgObUkgfgP77lnKVYCAAvmtGgAAAAAoBDhIgAAAABQiHARAAAAAChEuAgAAAAAFCJcBAAAAAAKcbdoAGCZW5K7nwIAwNJW9PvpD++5ZylX0vAIFwG+oZYk3PEBCgAAQCJcBACA5ZJ/BAIAGgLXXAQAAAAAChEuAgAAAACFrNCnRT967LGZM3Fi6XFlZWWaNGmywL5OHQEAAACAr8bKRQAAAACgEOEiAAAAAFCIcBEAAAAAKGSFvuYiAAAAAHxd7ujdu/C2K8r9P6xcBAAAAAAKsXIRAABgBVR0Nc2KspIGgGVDuAgAQGFLcioQLCnhGQDUP6dFAwAAAACFWLkIAAB8o1jxCABLj5WLAAAAAEAhwkUAAAAAoBDhIgAAAABQiGsuAgAAAMAy9lWvAVxZWZkmTZosd9cAtnIRAAAAACjEykUAAACgDndWBxaHlYsAAAAAQCHCRQAAAACgEOEiAAAAAFBIvYaLEyZMyBFHHJEePXpkt912y0UXXZTq6uoF9r355puz5557pnv37unTp08efvjhZVwtAAAAAPB59RouDh48OKuvvnoefvjhjBgxIg8//HBuuOGGOv0efPDBDBkyJOedd16ef/75HHjggTn22GPz3nvv1UPVAAAAAEBSj+HimDFjMm7cuJx44olp06ZNOnfunP79++fWW2+t03fWrFk5/vjjs+WWW6ZJkybZf//906pVq/zrX/9a9oUDAAAAAEmSxvU18NixY9OxY8e0bdu21Na1a9eMHz8+06dPT+vWrUvtffr0qbXtJ598ks8++yyrr7764g9YU/N//19WtoCna77aDrDYGtKxbUi1ftPU1NSU/kf9q4/fg999A/cln8OwIlsR3jN9Ds/zTdn/FeFvdnlVn/u5LOfxN+X3CcvU575PL6s5trjj1Fu4WFFRkZVWWqlW2/ygcerUqbXCxc+rqanJ6aefnu985zvZZpttFjnG3LlzU1lZWXpcVVW10L7Tpk1b3NIbtM8fj2WlPo5t0f38pvwdNEQ1NTWZMWNGkqRMMLFULMn7QdG5Uh9jNjT18T69rCzqcxhWZCvCe+by+jm8rN8zG9L32iWxJPvZkL6HN7Rju6SKzOOG9PuEJdUQvofP/z69rObY7NmzF6tfvYWLyVf/14zKysqceuqpeeONNzJy5Mgv7d+4ceNUN2kyf7AkSZPGjRe4YuLzKyhXZE3mH49lqD6ObdH9/Kb8HTRE898v2rZtu1z9R01DtiTvB0XnSn2M2dDUx/v0MvEln8OwIlsR3jOX18/hZf2e2ZC+1y6JJdnPhvQ9vKEd2yVVZB43pN8nLKnl/nv4575PL6s5Nv8fJL5MvYWL7du3T0VFRa22ioqKlJWVpX379nX6z5o1KwMHDszMmTNz0003pV27dl9twPlvngt5E12eviStaBrSsW1ItX4TlZWVlf5H/aqP34HfewP3JZ/DsCJbUd4zfQ5/cz6LVpS/2eVRfe/nsprH9b2fsEL63PfpZTXHFneceruhy6abbpqJEydmypQppbYxY8ZkvfXWS6tWrWr1rampyXHHHZfGjRvn+uuv/+rBIgAAAACw1NVbuLjJJpukW7duGTJkSKZPn54333wzI0aMSL9+/ZIke+21V1544YUkyT333JM33ngjl156aZo1a1ZfJQMAAAAAn1Ov11wcOnRozjjjjOywww5p3bp1+vbtmwMOOCBJMn78+NK53X/+858zYcKEOjdw6dOnT84555xlXjcAAAAAUM/h4hprrJGrr756gc+9+uqrpZ9vuOGGZVUSAAAAALCY6u20aAAAAACgYRMuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgEKEiwAAAABAIcJFAAAAAKAQ4SIAAAAAUIhwEQAAAAAoRLgIAAAAABQiXAQAAAAAChEuAgAAAACFCBcBAAAAgELqNVycMGFCjjjiiPTo0SO77bZbLrroolRXVy+w78iRI7Pnnntmiy22SL9+/fLyyy8v42oBAAAAgM+r13Bx8ODBWX311fPwww9nxIgRefjhh3PDDTfU6ffoo49m2LBh+e1vf5tnnnkmu+22W4466qjMmDGjHqoGAAAAAJKkcX0NPGbMmIwbNy4jRoxImzZt0qZNm/Tv3z833HBDDj300Fp9b7311vzwhz/Md77znSTJYYcdlpEjR+axxx7LvvvuW+e1569+bLzKKv/XWFOT8qqqNG7UKCkrq7PNZ599thT3bvnVdM01l/mY9XFsi+7nN+XvoCGqqanJrFmz0rhx45QtYA7z1S3J+0HRuVIfYzY09fE+vUx8yecwrMhWhPfM5fVzeFm/Zzak77VLYkn2syF9D29ox3ZJFZnHDen3CUtquf8e/rnv08tqjs2aNStJFnqW8XxlNTU1NcuioC+65ZZbcu211+ahhx4qtY0ePTr7779/XnzxxbRu3brUvuOOO+akk05Knz59Sm2HH354unTpklNPPbXOa0+ePDlvv/3211o/AAAAAKzoOnfunA4dOiz0+XpbuVhRUZGVVlqpVlvbtm2TJFOnTq0VLlZUVJSe+3zfqVOnLvC127Ztm86dO6dZs2YpL3fPGgAAAAD4KqqrqzN79uw6mdwX1Vu4mMxblv119G3cuPEiE1UAAAAAYNE+v/hvYeptWV/79u1TUVFRq62ioiJlZWVp3759rfZ27dotsO8X+wEAAAAAy069hYubbrppJk6cmClTppTaxowZk/XWWy+tWrWq03fs2LGlx1VVVfn3v/9dusELAAAAALDs1Vu4uMkmm6Rbt24ZMmRIpk+fnjfffDMjRoxIv379kiR77bVXXnjhhSRJv379cuedd+Zf//pXZs6cmf/5n/9J06ZNs+uuu9ZX+QAAAADwjVev11wcOnRozjjjjOywww5p3bp1+vbtmwMOOCBJMn78+MyYMSNJsvPOO+f444/Psccem8mTJ6dbt24ZPnx4mjdvXp/lAwAAAMA3WlnNV7lTCgAAAADA/1evKxe/ThMmTMhzzz2XNm3aZI011shmm21W3yUBAF9QU1OTsrKy+i4DKMgchoanuro61dXVadz4/+IAcxkajuVxDq+QKxdfffXVHH744dlkk03y4Ycf5tNPP82PfvSj/PznP6/v0oACpkyZko8++iht2rRJu3bt0rJly/ouCSho/PjxeeCBB/Lpp59mq622yo477pimTZvWd1nAYjKHoWF74403MnLkyLz//vvZbrvtsuWWW2aLLbZIUv/hBPDlltc5vMKFi9OnT8+AAQOy55575mc/+1kmTpyY5557LmeccUb69u2b008/vb5LBL6CcePG5Re/+EXat2+f9957LzvuuGP69OmT7bbbrr5LA76i119/PYcccki233771NTU5MEHH8yPfvSj7LfffunevXt9lwd8CXMYGrY333wzBxxwQH7yk5+kefPmeeKJJ9KiRYv07NkzBx10UBIBIyzPluc5vMKdFl1eXp5mzZpl6623TpKsueaa+cEPfpBVVlklgwcPTtOmTXPyySfXc5XA4pg0aVIGDRqUn/70pzn00EPzwAMP5PHHH8+ZZ56Zk046Kb169arvEoHFNGvWrFx22WUZMGBADjvssCTJT37yk1x00UWpqKjI7Nmzs+2229ZzlcDCmMPQsNXU1OTuu+/O/vvvn+OPPz5Jsu++++bWW2/NqFGjMnv27Bx22GGCRVhOLe9zuLxeRv0aVVVV5d13382LL75YaqupqclOO+2Uiy++OH/84x8zatSoeqwQWFwff/xxOnXqlEMPPTRJstdee+Wwww7LrrvumrPPPjuPPvpoPVcILK7mzZunoqIi5eXzvnpUV1dnm222yRlnnJGPP/44t99+e9555516rhJYGHMYGraysrK88847+eCDD0pt6667bg455JDsvPPOefjhh3PPPffUY4XAoizvc3iFCxfbtGmTgQMH5pprrsnDDz+cZN4voaamJrvuumsOOeSQPPXUU5k9e3ZWsDPCYYUxYcKEjBs3LmVlZXnuuefyj3/8o/Rcly5dctBBB6VXr14ZPnx4Xn311XqsFFhcs2fPTqdOnfLee+9l5syZKS8vT01NTTbbbLOccMIJefHFF3P//fcnic9nWI7MmDEjc+fOTWVlZTp16pT333/fHIYGavfdd8+kSZPyz3/+s9S2+uqr58c//nE6d+6cJ598MlVVVfVYIfBFH3/8cf7zn/8kSXr16pWPP/54uZzDK1y4mMxbGrrHHnvksssuy9/+9rdSe+PGjbPuuuvmgw8+SKNGjSz5huXQK6+8kv333z8ffPBBNtpoo3zve9/LTTfdVGs1xDrrrJM+ffqkRYsWeeONN+qxWmBRpk2blgkTJuSTTz5Js2bN0rt379x2223585//nOT//vFviy22yAknnJAbbrghH330kc9nWE68/PLL+elPf5qPP/44TZo0yXe/+93cdtttueOOO5KYw7C8++ijj/Lss89m7NixmTJlSrbddttUVVXljjvuyFtvvVXqt/baa6dv376577778u9//7seKwY+79///nf69u2biRMnJkk22GCDVFVVZdSoUcvdHF4hw8XWrVvn5z//eTbddNNceOGF+ctf/lL6kvPJJ5+kdevWqaysrOcqgS8aN25cDj744PTv3z89e/ZMkuy5556ZNGlSbr311rz33nulvptttlk6dOhQWqEMLF/GjRuXAw88MCeccEL22muvXHLJJfnWt76V888/P+eee25uu+22Whec3mSTTbLeeuulefPm9Vw5kMybwwMGDEivXr2yxhprJEl23XXXnHjiieYwNADjxo3LAQcckMsvvzyDBw/OL37xi7z99ts566yz8vTTT+eGG27IuHHjSv0333zz9OjRI02aNKnHqoH5xo0bl0MOOSQ//vGPSzdNW3/99XPUUUctl3N4hbuhy3yrrbZajj/++Nxwww055ZRTcscdd6Rly5Z59tlnM3LkyLRo0aK+SwQ+Z/6b55FHHpnDDjsslZWVeeSRR9KiRYs0b94877zzTm644Yb069cvXbp0SZLSf+wAy5cPP/wwRx11VA488MDsv//+ueeee/LYY4/lhBNOyPHHH59zzz03v/zlL/Pxxx9n9913z/rrr58nn3wyFRUVqa6uru/y4Rtv/j/2HX744Tn88MNTU1OT999/P02bNs0hhxySZs2a5YwzzsikSZPSq1cvcxiWM1OmTMlxxx2XAw88MP37988LL7yQRx55JP3798/ll1+eyy+/PCeeeGI++eST7Lbbbvne976XG264Ia+99lratWtX3+XDN978/zY+/PDDc8QRR6S6ujrPP/98mjZtmu7du+f3v/99Tj311OVqDpfVfAMuijJ69Og8++yzadmyZXbYYYd8+9vfru+SgM+prKzMPvvskxYtWuTuu+9OVVVV+vTpk7KysjRu3Dj/+c9/UllZme233z7vvvtudtlll1RWVubmm2/OLbfckg033LC+dwH4nGeeeSYjR47MlVdeWWp77rnncvvtt+e9997Lueeem3feeScXX3xxkmTVVVfNq6++mmuvvTabbrppfZUNZF4osffee2f33XfPueeem8rKyhx00EGZO3du3n333Xz3u9/NgAED8sEHH+SCCy5IYg7D8ub999/PqaeemmHDhpWChmnTpuWPf/xjLr300lx33XVZe+21M3z48Dz66KNZZ511MmnSpAwdOtQchno2ffr07LHHHtl8881zxRVXZM6cOfmv//qv1NTUZMqUKWnatGl+85vfpFOnTrnqqqvyyCOPpFOnTvU+h78R4SKw/Bs9enQOOeSQ0n/AVFRU5LzzzsukSZMy7v+1d7cxVZd/HMc/4AkMRBBRSMFKlCMqpA+o8IERdsNKp1NIW5NiRW7NzKyoOa2FrnJaimg2oqWAiKLiRIXKu9hYscyKuea9eYC4EQSBxLg5/B8Yv794H5K/g75fjw7XdfjxPWxfdvic67p+hw9r3rx5Cg8PV2RkpHbt2iVfX1+98MILGjFihNmlA7hMYWGhZs+efUX4f/DgQaWlpUmSli1bprKyMlVUVKihoUHBwcEaPHiwWSUD+IfNZlNqaqqqqqoUHx+vzMxMtbW1ad68efrxxx9VWFiompoaJSUlqampSWVlZfQw4GDKy8v11FNPKTk5WREREcZ4U1OTUlNTtWnTJqWmpspqtaqyslKNjY3q16+fvL29zSsagCE7O1uJiYlasmSJDh06pOrqai1evFhHjx7V/v379cUXX+irr77SI4884jA9TLgIwGEUFxcrNjZW/v7+WrlyZadVxunp6crJyVFW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",
"text/plain": [
"<Figure size 1600x800 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"📊 序列预测结果统计:\n",
" 预测位点总数: 85\n",
" 高概率位点 (>0.8): 0\n",
" 中概率位点 (0.4-0.8): 6\n",
" 最高预测概率: 0.475\n"
]
}
],
"source": [
"# Select a sequence for demonstration\n",
"from Bio import SeqIO\n",
"\n",
"# Read the first mRNA sequence for demonstration\n",
"mrna_sequences = list(SeqIO.parse(mrna_file, \"fasta\"))\n",
"demo_seq = mrna_sequences[0] # Select the first sequence\n",
"\n",
"print(f\"🧬 Selected demonstration sequence: {demo_seq.id}\")\n",
"print(f\"Sequence length: {len(demo_seq.seq)} bp\")\n",
"print(f\"First 100bp of sequence: {str(demo_seq.seq)[:100]}...\")\n",
"\n",
"# Use built-in plot_prf_prediction function for prediction and visualization\n",
"print(f\"\\n🎯 Using plot_prf_prediction for sequence prediction and visualization...\")\n",
"\n",
"sequence_results, fig = plot_prf_prediction(\n",
" sequence=str(demo_seq.seq),\n",
" window_size=3,\n",
" short_threshold=0.2,\n",
" long_threshold=0.2,\n",
" ensemble_weight=0.6,\n",
" title=f\"PRF Prediction Results for Sequence {demo_seq.id} (Bar Chart + Heatmap)\",\n",
" figsize=(16, 8),\n",
" dpi=150\n",
")\n",
"\n",
"plt.show()\n",
"\n",
"print(f\"\\n📊 Sequence prediction result statistics:\")\n",
"print(f\" Total predicted sites: {len(sequence_results)}\")\n",
"print(f\" High probability sites (>0.8): {(sequence_results['Ensemble_Probability'] > 0.8).sum()}\")\n",
"print(f\" Medium probability sites (0.4-0.8): {((sequence_results['Ensemble_Probability'] >= 0.4) & (sequence_results['Ensemble_Probability'] <= 0.8)).sum()}\")\n",
"print(f\" Highest prediction probability: {sequence_results['Ensemble_Probability'].max():.3f}\")"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🔝 Top 5 预测位点:\n",
" 1. 位置 96: \n",
" - Short概率: 0.288\n",
" - Long概率: 0.755\n",
" - 集成概率: 0.475\n",
" - 密码子: TAA\n",
" 2. 位置 12: \n",
" - Short概率: 0.606\n",
" - Long概率: 0.177\n",
" - 集成概率: 0.434\n",
" - 密码子: TTG\n",
" 3. 位置 15: \n",
" - Short概率: 0.493\n",
" - Long概率: 0.329\n",
" - 集成概率: 0.428\n",
" - 密码子: GAA\n",
" 4. 位置 18: \n",
" - Short概率: 0.369\n",
" - Long概率: 0.510\n",
" - 集成概率: 0.426\n",
" - 密码子: GTC\n",
" 5. 位置 105: \n",
" - Short概率: 0.248\n",
" - Long概率: 0.671\n",
" - 集成概率: 0.418\n",
" - 密码子: ACT\n",
"\n",
"📊 可视化分析完成!\n",
"图表包含热图和条形图展示了整个序列的PRF预测概率分布。\n"
]
}
],
"source": [
"# Print top predicted site probabilities\n",
"if sequence_results['Ensemble_Probability'].max() > 0.3:\n",
" top_predictions = sequence_results.nlargest(5, 'Ensemble_Probability')\n",
" print(f\"\\n🔝 Top 5 predicted sites:\")\n",
" for i, (_, row) in enumerate(top_predictions.iterrows(), 1):\n",
" print(f\" {i}. Position {row['Position']}: \")\n",
" print(f\" - Short probability: {row['Short_Probability']:.3f}\")\n",
" print(f\" - Long probability: {row['Long_Probability']:.3f}\")\n",
" print(f\" - Ensemble probability: {row['Ensemble_Probability']:.3f}\")\n",
" print(f\" - Codon: {row['Codon']}\")\n",
"else:\n",
" print(\"\\n💡 No high-probability PRF sites detected in this sequence\")\n",
"\n",
"print(\"\\n📊 Visualization analysis complete!\")\n",
"print(\"The chart contains heatmaps and bar charts showing the PRF prediction probability distribution across the entire sequence.\")"
]
},
{
"cell_type": "raw",
"metadata": {
"vscode": {
"languageId": "raw"
}
},
"source": [
"## 📖 Complete Function Reference\n",
"\n",
"### All Available Functions and Methods\n",
"\n",
"#### Core Prediction Functions\n",
"\n",
"**1. `predict_prf(sequence=None, data=None, window_size=3, short_threshold=0.1, ensemble_weight=0.4, model_dir=None)`**\n",
"- **Purpose**: Universal prediction function for both sliding window and region-based analysis\n",
"- **Input modes**: \n",
" - Single/multiple sequences → sliding window prediction\n",
" - DataFrame with 'Long_Sequence'/'399bp' column → region prediction\n",
"- **Key parameters**:\n",
" - `ensemble_weight`: Short model weight (0.0-1.0, default: 0.4)\n",
" - `window_size`: Scanning step size (default: 3)\n",
" - `short_threshold`: Filtering threshold (default: 0.1)\n",
"\n",
"**2. `plot_prf_prediction(sequence, window_size=3, short_threshold=0.65, long_threshold=0.8, ensemble_weight=0.4, title=None, save_path=None, figsize=(12,8), dpi=300)`**\n",
"- **Purpose**: Prediction with built-in visualization (3-subplot layout: FS site heatmap, prediction heatmap, bar chart)\n",
"- **Returns**: (prediction_results_df, matplotlib_figure)\n",
"- **Visualization features**: \n",
" - Black bars with alpha=0.6\n",
" - 'Reds' colormap for heatmaps\n",
" - Height ratios [0.1, 0.1, 1] for subplots\n",
"\n",
"#### PRFPredictor Class Methods\n",
"\n",
"**3. Class initialization: `PRFPredictor(model_dir=None)`**\n",
"- Loads HistGradientBoosting (short, 33bp) and BiLSTM-CNN (long, 399bp) models\n",
"- Uses ensemble weighting for final predictions\n",
"\n",
"**4. `predictor.predict_sequence(sequence, window_size=3, short_threshold=0.1, ensemble_weight=0.4)`**\n",
"- **Purpose**: Sliding window analysis of complete sequences\n",
"- **Process**: Scans sequence with specified window size, applies both models\n",
"\n",
"**5. `predictor.predict_regions(sequences, short_threshold=0.1, ensemble_weight=0.4)`**\n",
"- **Purpose**: Batch prediction for pre-defined 399bp regions\n",
"- **Input**: List/Series of 399bp sequences\n",
"- **Efficient**: Direct region analysis without sliding window\n",
"\n",
"**6. `predictor.predict_single_position(fs_period, full_seq, short_threshold=0.1, ensemble_weight=0.4)`**\n",
"- **Purpose**: Single position analysis\n",
"- **Inputs**: 33bp sequence (fs_period) + 399bp sequence (full_seq)\n",
"- **Returns**: Dictionary with individual and ensemble probabilities\n",
"\n",
"**7. `predictor.plot_sequence_prediction(...)`** \n",
"- **Purpose**: Class method version of plot_prf_prediction()\n",
"- **Same parameters** as standalone function\n",
"\n",
"#### Utility Functions\n",
"\n",
"**8. `fscanr(blastx_output, mismatch_cutoff=10, evalue_cutoff=1e-5, frameDist_cutoff=10)`**\n",
"- **Purpose**: Detect PRF sites from BLASTX alignment results\n",
"- **Input**: DataFrame with BLASTX columns (qseqid, sseqid, pident, length, mismatch, gapopen, qstart, qend, sstart, send, evalue, bitscore, qframe, sframe)\n",
"- **Output**: PRF sites with FS_start, FS_end, FS_type, Strand information\n",
"\n",
"**9. `extract_prf_regions(mrna_file, prf_data)`**\n",
"- **Purpose**: Extract 399bp sequences around detected PRF sites\n",
"- **Inputs**: FASTA file path + FScanR results DataFrame\n",
"- **Handles**: Strand orientation (reverse complement for '-' strand)\n",
"\n",
"#### Data Access Functions\n",
"\n",
"**10. `get_test_data_path(filename)`**\n",
"- **Purpose**: Get path to built-in test data files\n",
"- **Available files**: 'blastx_example.xlsx', 'mrna_example.fasta', 'region_example.csv'\n",
"\n",
"**11. `list_test_data()`**\n",
"- **Purpose**: Display all available test data files\n",
"\n",
"### Usage Pattern Examples\n",
"\n",
"#### Pattern 1: Quick Single Sequence Analysis\n",
"```python\n",
"from FScanpy import predict_prf, plot_prf_prediction\n",
"\n",
"# Simple prediction\n",
"results = predict_prf(sequence=\"ATGCGT...\")\n",
"\n",
"# With visualization \n",
"results, fig = plot_prf_prediction(sequence=\"ATGCGT...\")\n",
"```\n",
"\n",
"#### Pattern 2: Batch Sequence Analysis\n",
"```python\n",
"sequences = [\"seq1\", \"seq2\", \"seq3\"]\n",
"results = predict_prf(sequence=sequences, ensemble_weight=0.5)\n",
"```\n",
"\n",
"#### Pattern 3: BLASTX Pipeline\n",
"```python\n",
"from FScanpy.utils import fscanr, extract_prf_regions\n",
"\n",
"# Step 1: Detect PRF sites\n",
"prf_sites = fscanr(blastx_df)\n",
"\n",
"# Step 2: Extract sequences\n",
"prf_sequences = extract_prf_regions(fasta_file, prf_sites)\n",
"\n",
"# Step 3: Predict probabilities\n",
"results = predict_prf(data=prf_sequences)\n",
"```\n",
"\n",
"#### Pattern 4: Custom Analysis with PRFPredictor\n",
"```python\n",
"from FScanpy import PRFPredictor\n",
"\n",
"predictor = PRFPredictor()\n",
"\n",
"# Method chaining for different analysis types\n",
"seq_results = predictor.predict_sequence(sequence)\n",
"region_results = predictor.predict_regions(sequences_399bp)\n",
"single_result = predictor.predict_single_position(seq_33bp, seq_399bp)\n",
"```\n",
"\n",
"### Parameter Optimization Guide\n",
"\n",
"**Ensemble Weight Selection:**\n",
"- `0.2-0.3`: Conservative (high specificity, favor long model)\n",
"- `0.4-0.6`: Balanced (recommended default)\n",
"- `0.7-0.8`: Sensitive (high sensitivity, favor short model)\n",
"\n",
"**Window Size Selection:**\n",
"- `1`: High resolution, every position (slow but detailed)\n",
"- `3`: Standard resolution (balanced speed/detail) \n",
"- `6-9`: Low resolution, faster analysis\n",
"\n",
"**Threshold Guidelines:**\n",
"- `short_threshold`: 0.1-0.3 (controls efficiency by filtering low-probability candidates)\n",
"- Display thresholds: 0.3-0.8 (controls visualization, higher = cleaner plots)\n",
"- Classification threshold: 0.5 (standard binary classification cutoff)\n",
"\n",
"### Output Interpretation\n",
"\n",
"**Main Result Columns:**\n",
"- `Short_Probability`: HistGradientBoosting model prediction (0-1)\n",
"- `Long_Probability`: BiLSTM-CNN model prediction (0-1)\n",
"- `Ensemble_Probability`: **Final prediction** (weighted combination)\n",
"- `Position`: Sequence position (sliding window mode)\n",
"- `Codon`: Codon at position (sliding window mode)\n",
"\n",
"**Ensemble Probability Interpretation:**\n",
"- `> 0.8`: High confidence PRF site\n",
"- `0.5-0.8`: Moderate confidence PRF site \n",
"- `0.3-0.5`: Low confidence, worth investigating\n",
"- `< 0.3`: Unlikely to be PRF site\n",
"\n",
"### Best Practices\n",
"\n",
"1. **For exploration**: Use `window_size=1, ensemble_weight=0.4`\n",
"2. **For screening**: Use `window_size=3, ensemble_weight=0.4, short_threshold=0.2`\n",
"3. **For validation**: Use region-based prediction with known sequences\n",
"4. **For visualization**: Adjust `short_threshold` and `long_threshold` in plotting functions to control display density\n",
"\n",
"This demo covers all major FScanpy functionalities. For detailed parameter descriptions and advanced usage, please refer to the complete tutorial documentation.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 📝 Analysis Summary\n",
"\n",
"### 🎯 Key Findings\n",
"1. **Data Quality**: Test dataset contains real BLASTX alignment results and validation regions\n",
"2. **FScanR Performance**: Successfully identified potential PRF sites from BLASTX results\n",
"3. **Model Performance**: Short and Long models each have advantages in different scenarios\n",
"4. **Prediction Results**: Ensemble model provides more stable prediction performance\n",
"5. **Visualization**: Built-in plotting functions generate clear heatmaps and bar charts\n",
"\n",
"### 🔧 Best Practices\n",
"- **Data Preprocessing**: Ensure BLASTX results are in correct format\n",
"- **Parameter Settings**: Use default ensemble weights (0.4:0.6) for balanced performance\n",
"- **Result Interpretation**: When using FScanpy for whole sequence prediction, don't use 0.5 as threshold, but compare relative probabilities across positions\n",
"- **Visualization**: Use plot_prf_prediction function to generate standardized plots\n",
"\n",
"### 📚 Usage Recommendations\n",
"1. **Threshold Selection**: Adjust probability thresholds based on application scenarios\n",
"2. **Result Validation**: Validate prediction results with biological knowledge\n",
"3. **Performance Optimization**: Use reasonable sliding window sizes for large-scale data\n",
"4. **Visualization Parameters**: Adjust figsize and dpi for optimal display"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "tf200",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}