diff --git a/calc/vol.py b/calc/vol.py new file mode 100644 index 0000000..bda8b41 --- /dev/null +++ b/calc/vol.py @@ -0,0 +1,33 @@ +# Ellipsoid Volume +from typing import List + +def prostate_vol(x: List[float]): + """Calculate prostate volume (ml) and diagnosis""" + v = ellipsoid_list(x) + if v < 25: + dx = "Normal" + elif v == 25: + dx = "Normal or Prominent" + elif v < 40: + dx = "Prominent" + elif v == 40: + dx = "Prominent or Enlarged" + else: + dx = "Enlarged" + + return f"Prostate vol: {round(v, 2)} ml ({dx})" + + + +def ellipsoid(d1, d2, d3): + """Calculate ellipsoid volume from 3 perpendicular **diameters**""" + import math + volume = (4/3) * math.pi * d1/2 * d2/2 * d3/2 + return volume + +def ellipsoid_list(x: List[float]): + """Calculate ellipsoid volume from 3 perpendicular **diameters** (List input)""" + try: + return ellipsoid(*x) + except TypeError: + raise TypeError(f"{x} must be a list of three floats") \ No newline at end of file diff --git a/dev/calc-dev.ipynb b/dev/calc-dev.ipynb index bcb7fe8..6a6af4f 100644 --- a/dev/calc-dev.ipynb +++ b/dev/calc-dev.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Mean Calc" + "### Mean Calc" ] }, { @@ -52,6 +52,124 @@ "print(calc_mean([1.1, 1.2]))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prostate Vol" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "def prostate_vol(x: List[float]):\n", + " \"\"\"Calculate prostate volume (ml) and diagnosis\"\"\"\n", + " v = ellipsoid_list(x)\n", + " if v < 25:\n", + " dx = \"Normal\"\n", + " elif v == 25:\n", + " dx = \"Normal or Prominent\"\n", + " elif v < 40:\n", + " dx = \"Prominent\"\n", + " elif v == 40:\n", + " dx = \"Prominent or Enlarged\"\n", + " else:\n", + " dx = \"Enlarged\"\n", + " \n", + " print(f\"Prostate vol: {round(v, 2)} ml ({dx})\")" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "def ellipsoid(d1, d2, d3):\n", + " \"\"\"Calculate ellipsoid volume from 3 perpendicular **diameters**\"\"\"\n", + " import math\n", + " volume = (4/3) * math.pi * d1/2 * d2/2 * d3/2\n", + " return volume\n", + "\n", + "def ellipsoid_list(x: List[float]):\n", + " try: \n", + " return ellipsoid(*x)\n", + " except TypeError:\n", + " raise TypeError(f\"{x} must be a list of three floats\")" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Prostate vol: 3.14 ml (Normal)\n" + ] + } + ], + "source": [ + "v1 = [1, 2, 3]\n", + "ellipsoid(*v1)\n", + "prostate_vol(v1)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "[1, 'A', 'B'] must be a list of three floats", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[29], line 9\u001b[0m, in \u001b[0;36mellipsoid_list\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m: \n\u001b[0;32m----> 9\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mellipsoid\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m:\n", + "Cell \u001b[0;32mIn[29], line 4\u001b[0m, in \u001b[0;36mellipsoid\u001b[0;34m(d1, d2, d3)\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mmath\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m volume \u001b[38;5;241m=\u001b[39m \u001b[43m(\u001b[49m\u001b[38;5;241;43m4\u001b[39;49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mmath\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpi\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43md1\u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43md2\u001b[49m\u001b[38;5;241m/\u001b[39m\u001b[38;5;241m2\u001b[39m \u001b[38;5;241m*\u001b[39m d3\u001b[38;5;241m/\u001b[39m\u001b[38;5;241m2\u001b[39m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m volume\n", + "\u001b[0;31mTypeError\u001b[0m: can't multiply sequence by non-int of type 'float'", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[35], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# ellipsoid_list([\"A\", 1, 2])\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m# prostate_vol([1, 2, 3, 4])\u001b[39;00m\n\u001b[0;32m----> 3\u001b[0m \u001b[43mprostate_vol\u001b[49m\u001b[43m(\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mA\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mB\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[30], line 3\u001b[0m, in \u001b[0;36mprostate_vol\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mprostate_vol\u001b[39m(x: List[\u001b[38;5;28mfloat\u001b[39m]):\n\u001b[1;32m 2\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Calculate prostate volume (ml) and diagnosis\"\"\"\u001b[39;00m\n\u001b[0;32m----> 3\u001b[0m v \u001b[38;5;241m=\u001b[39m \u001b[43mellipsoid_list\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m v \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m25\u001b[39m:\n\u001b[1;32m 5\u001b[0m dx \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNormal\u001b[39m\u001b[38;5;124m\"\u001b[39m\n", + "Cell \u001b[0;32mIn[29], line 11\u001b[0m, in \u001b[0;36mellipsoid_list\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m ellipsoid(\u001b[38;5;241m*\u001b[39mx)\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m:\n\u001b[0;32m---> 11\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mx\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m must be a list of three floats\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mTypeError\u001b[0m: [1, 'A', 'B'] must be a list of three floats" + ] + } + ], + "source": [ + "# ellipsoid_list([\"A\", 1, 2])\n", + "# prostate_vol([1, 2, 3, 4])\n", + "prostate_vol([1, \"A\", \"B\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3.141592653589793" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ellipsoid_list(v1)" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/main.py b/main.py index 51b8954..c0a36a7 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ import datetime from flet import Container, Column, Row, ResponsiveRow, Page -from module import AppDesignCT, AppSpineHtLoss, AppMean +from module import AppDesignCT, AppSpineHtLoss, AppMean, AppVol from designcter._utils import dash_if_blank @@ -37,7 +37,7 @@ def change_tab(e): ) tab_1 = AppDesignCT() - tab_2 = ft.Column(controls=[AppMean(), AppSpineHtLoss()]) + tab_2 = ft.Column(controls=[AppMean(), AppVol(), AppSpineHtLoss()]) ## Default tab_1.visible = True; tab_2.visible = False #ft.Text("Tab 2",size=30,visible=False) diff --git a/module/__init__.py b/module/__init__.py index a7b524d..78991bb 100644 --- a/module/__init__.py +++ b/module/__init__.py @@ -1,3 +1,4 @@ from .designct_app import AppDesignCT from .mean_app import AppMean -from .spine_ht_loss_app import AppSpineHtLoss \ No newline at end of file +from .spine_ht_loss_app import AppSpineHtLoss +from .vol_app import AppVol \ No newline at end of file diff --git a/module/man.py b/module/man.py index c716980..6649adf 100644 --- a/module/man.py +++ b/module/man.py @@ -1,3 +1,4 @@ class Manual: mean_app = f"""Use spaces or comma to separate numbers (e.g. 1.1 1.2)""" - spine_ht_loss_app = f"""Input height in centimeter and use two values to calculate mean, separated by spaces or comma (e.g. 10 12)""" \ No newline at end of file + spine_ht_loss_app = f"""Input height in centimeter and use two values to calculate mean, separated by spaces or comma (e.g. 10 12)""" + vol_app = f"""Input perpendicular diameters (cm) in 3 planes, separated by spaces or comma (e.g. 4.4 4.5 4.6)""" \ No newline at end of file diff --git a/module/vol_app.py b/module/vol_app.py new file mode 100644 index 0000000..e7b4056 --- /dev/null +++ b/module/vol_app.py @@ -0,0 +1,64 @@ +import flet as ft +from flet import Container, Column, Row, ResponsiveRow +import pyperclip + +from calc.vol import prostate_vol +from calc._utils import parse_str_to_num_or_list, read_markdown_file +from module.man import Manual + +class AppVol(ft.UserControl): + def __init__(self): + super().__init__() + + ## Input Diameters + self.input_numbers_text = ft.TextField( + label="Diameters in 3 planes (cm)", hint_text="e.g. 4.4 4.5 4.6" + ) + + # Button + self.btn = ft.ElevatedButton(text="Generate", on_click=self.button_gen_clicked) + self.btn_copy = ft.IconButton(icon=ft.icons.CONTENT_COPY, icon_size=20, tooltip="Copy output", on_click=self.button_cp_clicked) + + # Output text + txt_size = 14 # 14 + self.output_text_field = ft.TextField(label="Prostate Volume", value="", multiline=True, read_only=False, text_size=txt_size) + + def build(self): + rr = ResponsiveRow( + controls=[ + ft.Text("Prostate Volume", theme_style=ft.TextThemeStyle.TITLE_LARGE), + Column(col={"sm": 6}, + controls = [ + self.input_numbers_text, + # Manual + ft.Text(Manual.vol_app, theme_style = ft.TextThemeStyle.BODY_SMALL, color=ft.colors.GREY), + # ft.Markdown(Manual.mean_app, selectable=True) + ], alignment=ft.MainAxisAlignment.START), + Column(col={"sm": 6}, + controls = [ + self.output_text_field, + Row([self.btn, self.btn_copy], alignment=ft.MainAxisAlignment.START), + ], alignment=ft.MainAxisAlignment.START) + ] + ) + lv = ft.ListView(controls=[rr], expand=1, spacing=5, padding=10, auto_scroll=False) + return lv + + def button_cp_clicked(self, e): + pyperclip.copy(self.output_text_field.value) + + def button_gen_clicked(self, e): + self._log() + self.output_text_field.value = self.calc() + self.output_text_field.focus() + self.output_text_field.update() + + def _log(self): + print("Btn clicked") + print(f"Input: {self.input_numbers_text.value}") + print(self.calc()) + + def calc(self): + numbers = parse_str_to_num_or_list(self.input_numbers_text.value) + mean = prostate_vol(numbers) + return mean \ No newline at end of file